[译]Effective Kotlin系列之考虑使用原始类型的数组优化性能(五)

本文主要是介绍[译]Effective Kotlin系列之考虑使用原始类型的数组优化性能(五),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

翻译说明:

原标题: Effective Kotlin: Consider Arrays with primitives for performance critical processing

原文地址: https://blog.kotlin-academy.com/effective-kotlin-use-sequence-for-bigger-collections-with-more-than-one-processing-step-649a15bb4bf

原文作者: Marcin Moskala

Kotlin底层实现是非常智能的。在Kotlin中我们不能直接声明原始类型(也称原语类型)的,但是当我们不像使用对象实例那样操作一个变量时,那么这个变量在底层将转换成原始类型处理。例如,请看以下示例:

var i = 10
i = i * 2
println(i)

上述的变量声明在Kotlin底层是使用了原始类型int.下面这是上述例子在Java中的内部表达:

// Java
int i = 10;
i = i * 2;
System.out.println(i);

上述使用int的实现到底比使用Integer的实现要快多少呢? 让我们来看看。我们需要在Java中定义两种方式函数声明:

public class PrimitivesJavaBenchmark {public int primitiveCount() {int a = 1;for (int i = 0; i < 1_000_000; i++) {a = a + i * 2;}return a;}public Integer objectCount() {Integer a = 1;for (Integer i = 0; i < 1_000_000; i++) {a = a + i * 2;}return a;}
}

当你测试这两种方法的性能时,您会发现一个巨大的差异。在我的机器中,使用Integer需要4905603ns, 而使用原始类型需要316954ns(这里是源码,自己检查运行测试)这少了15倍!这是一个巨大的差异!

怎么会产生如此之大的差异呢? 原始类型比对象类型更加轻量级。在内存中原始类型的变量仅仅存储是一个数值而已,它们没有面向对象那一整套的内存分配过程。当你看到这种差异时,你应该感到庆幸,因为在Kotlin底层实现会尽可能使用原始类型,而且这种底层的优化我们甚至毫无察觉。但是你也应该知道有些情况底层编译器是不会转化成原始类型来做优化处理的:

  • 可空类型不能是原始类型。编译器是很智能的,尽管是可空类型,可是当它检测到你没有对可空类型变量设置null值时,然后它还是会使用原始类型处理的。如果编译不能确定最终检测结果,那么它将默认使用非原始类型。请记住,这是代码性能关键部分因可空性引入的额外成本。
  • 原始类型不能用于泛型类型参数。

第二个问题显得尤为重要,因为我们在大部分场景下很少会对代码中数值做处理,但是我们经常会对集合中的元素做操作。可是问题来了,泛型类型参数不能使用原始类型,但是每个泛型集合都只能使用非原始类型了。例如:

  • Kotlin中的List<Int>等价于Java中的List<Integer>(注意下: 这个地方有点问题,纠正下原文作者的一个小错误,实际上是Kotlin中的MutableList<Int>等价于Java中的List<Integer>,但是作者这里主要想表明在Kotlin中作为泛型类型参数Int类型情况下等同于Java中的包装器类型Integer而不是原始类型int)
  • Kotlin中的Set<Double>等价于Java中的Set<Double>(注意下: 这个地方有点问题,纠正下原文作者的一个小错误,实际上是Kotlin中的MutableSet<Double>等价于Java中的Set<Double>,但是作者这里主要想表明在Kotlin中作为泛型类型参数Double类型情况下等同于Java中的包装器类型Double而不是原始类型double)

当我们需要操作数据集合,这将是一笔很大的性能开销。但是也是有解决方案的, 因为Java集合允许使用原始类型。

// Java
int[] a = { 1,2,3,4 };

如果在Java中可以使用原始类型的数组,那么在Kotlin也是可以使用原始类型的数组的。为此,我们需要使用一种特殊的数组类型来表示具有不同原始类型的数组:
IntArrayLongArrayShortArrayDoubleArrayFloatArray或者CharArray. 让我们使用IntArray,看看与List <Int>相比对代码的性能影响:

open class InlineFilterBenchmark {lateinit var list: List<Int>lateinit var array: IntArray@Setupfun init() {list = List(1_000_000) { it }array = IntArray(1_000_000) { it }}@Benchmarkfun averageOnIntList(): Double {return list.average()}@Benchmarkfun averageOnIntArray(): Double {return array.average()}
}

尽管差异不是特别大,但是也是差异也是非常明显的。例如,因为在底层实现上IntArray是使用原始类型的,所以IntArray数组的average()函数会比List<Int>集合运行效率高了约25%左右。(这里是源码,自己检查运行测试)

具有原始类型的数组也会比集合更加轻量级。进行测量时,您会发现IntArray上面分配了400000016个字节,而List<Int>分配了2000006944个字节。大概是5倍的差距。

正如你所看到那样,使用具有原始类型的变量或者数组都是优化性能关键部分一种手段。它们需要分配的内存更少,并且处理的速度更快。尽管原始类型数组在大多数情况下作了优化,但是默认情况下可能更多是使用集合而不是数组。因为集合相比数据更加直观和更经常使用。但是你也必须记住原始类型的变量和原始类型数组带来的性能优化,并且在合适的场景中使用它们。

译者有话说

这篇Effective Kotlin系列的文章比较简单,但是也很重要。它指出了我们经常会忽略的原始类型数组。相信很多人都习惯于使用集合,甚至有的人估计都没怎么用过Kotlin中的IntArray、LongArray、FloatArray等,平时不管是什么场景都使用集合一梭哈。这也很正常,因为集合基本上可以替代数组出现所有场景,而且集合使用起来更加直观和方便。但是之前的你可能不知道原来原始类型的数组可以在某些场景替代集合反而可以优化性能。所以原始类型的数组是有一定应用场景的,那么从读了这篇文章起,请一定要记住这个优化点。关于这篇文章我还想再补充几点哈:

  • 1、解释下文章中的原始类型

请注意: 文章中的原始类型(原语类型或基本数据类型)实际上不是Kotlin中的Int、Float、Double、Long等这些类型,原始类型实际上它不对应一个类,就像我们常在Java中说的String不是原始类型,而是引用类型。实际这里原始类型就是指Java中的int、double、float、long等非引用类型。为什么说Kotlin中的Int不是原始类型,实际上它更是一种引用类型,一起来看Int的源码:

public class Int private constructor() : Number(), Comparable<Int> {companion object {public const val MIN_VALUE: Int = -2147483648public const val MAX_VALUE: Int = 2147483647@SinceKotlin("1.3")public const val SIZE_BYTES: Int = 4@SinceKotlin("1.3")public const val SIZE_BITS: Int = 32}

可以明显看出实际上Int是在Kotlin中定义的一个类,它属于引用类型,不是原始类型。所以我们平时在Kotlin中是不能直接声明原始类型的,而所谓原始类型是Kotlin编译器在底层做的一层内部表达。在Kotlin中声明Int类型,实际上底层编译器会根据具体使用情况,智能推测出是将Int表达为包装器Integer还是原始类型int。如果不信,请看下面这个解释的源码论证。

  • 2、解释下文章中的这句话 “尽管是可空类型,可是当它检测到你没有对可空类型变量设置null值时,然后它还是会使用原始类型处理的,如果设置null就当做非原始类型处理”

把上面那句话说的通俗就是,声明一个可空类型Int?变量,如果没有对它做赋值null的操作,那么编译器在底层实现会把这个Int?类型使用原始类型int,如果有赋值null操作就会使用包装器类型Integer.一起来看个例子

//kotlin定义的源码
fun main(args: Array<String>) {var number: Int?number = 2println(number)
}
//反编译后的Java代码public static final void main(@NotNull String[] args) {Intrinsics.checkParameterIsNotNull(args, "args");int number = 2;//可以明显看到number变量使用的是int原始类型System.out.println(number);}

如果把上述例子改为赋值为null

//kotlin定义的源码
fun main(args: Array<String>) {var number: Int? = nullnumber = 2println(number)
}
//反编译后的Java代码public static final void main(@NotNull String[] args) {Intrinsics.checkParameterIsNotNull(args, "args");Integer number = (Integer)null;//这里number变量是使用了Integer包装器类型number = 2;int var2 = number;System.out.println(var2);}

通过上述代码的对比,可以发现Kotlin编译器是非常智能的,这也就是解释了虽然在Kotlin定义的是Int,但是会根据不同的使用情况,最终转换成结果也不一样的,所以使用的时候一定要做到心里有数。

  • 关于使用原始类型数组的建议

其实我们大多数情况下还是使用集合的,因为数组使用具有局限性。那么什么时候使用原始类型数组呢?
元素的类型应该是Int、Float、Double、Long等这些类型,并且长度还是固定的,这种情况更多考虑是原始类型数组来替代集合的使用,因为它效率更高。其他非这种场景还是建议使用集合。

Kotlin系列文章,欢迎查看:

Effective Kotlin翻译系列

  • [译]Effective Kotlin系列之使用Sequence来优化集合的操作(四)
  • [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)
  • [译]Effective Kotlin系列之遇到多个构造器参数要考虑使用构建器(二)
  • [译]Effective Kotlin系列之考虑使用静态工厂方法替代构造器(一)

原创系列:

  • Jetbrains开发者日见闻(三)之Kotlin1.3新特性(inline class篇)
  • JetBrains开发者日见闻(二)之Kotlin1.3的新特性(Contract契约与协程篇)
  • JetBrains开发者日见闻(一)之Kotlin/Native 尝鲜篇
  • 教你如何攻克Kotlin中泛型型变的难点(实践篇)
  • 教你如何攻克Kotlin中泛型型变的难点(下篇)
  • 教你如何攻克Kotlin中泛型型变的难点(上篇)
  • Kotlin的独门秘籍Reified实化类型参数(下篇)
  • 有关Kotlin属性代理你需要知道的一切
  • 浅谈Kotlin中的Sequences源码解析
  • 浅谈Kotlin中集合和函数式API完全解析-上篇
  • 浅谈Kotlin语法篇之lambda编译成字节码过程完全解析
  • 浅谈Kotlin语法篇之Lambda表达式完全解析
  • 浅谈Kotlin语法篇之扩展函数
  • 浅谈Kotlin语法篇之顶层函数、中缀调用、解构声明
  • 浅谈Kotlin语法篇之如何让函数更好地调用
  • 浅谈Kotlin语法篇之变量和常量
  • 浅谈Kotlin语法篇之基础语法

翻译系列:

  • [译]Kotlin中内联类的自动装箱和高性能探索(二)
  • [译]Kotlin中内联类(inline class)完全解析(一)
  • [译]Kotlin的独门秘籍Reified实化类型参数(上篇)
  • [译]Kotlin泛型中何时该用类型形参约束?
  • [译] 一个简单方式教你记住Kotlin的形参和实参
  • [译]Kotlin中是应该定义函数还是定义属性?
  • [译]如何在你的Kotlin代码中移除所有的!!(非空断言)
  • [译]掌握Kotlin中的标准库函数: run、with、let、also和apply
  • [译]有关Kotlin类型别名(typealias)你需要知道的一切
  • [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?
  • [译]Kotlin中的龟(List)兔(Sequence)赛跑

实战系列:

  • 用Kotlin撸一个图片压缩插件ImageSlimming-导学篇(一)
  • 用Kotlin撸一个图片压缩插件-插件基础篇(二)
  • 用Kotlin撸一个图片压缩插件-实战篇(三)
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

这篇关于[译]Effective Kotlin系列之考虑使用原始类型的数组优化性能(五)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1111950

相关文章

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

MyBatisPlus如何优化千万级数据的CRUD

《MyBatisPlus如何优化千万级数据的CRUD》最近负责的一个项目,数据库表量级破千万,每次执行CRUD都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊MyBatisPl... 目录背景一、MyBATis Plus 简介二、千万级数据的挑战三、优化 CRUD 的关键策略1. 查

ModelMapper基本使用和常见场景示例详解

《ModelMapper基本使用和常见场景示例详解》ModelMapper是Java对象映射库,支持自动映射、自定义规则、集合转换及高级配置(如匹配策略、转换器),可集成SpringBoot,减少样板... 目录1. 添加依赖2. 基本用法示例:简单对象映射3. 自定义映射规则4. 集合映射5. 高级配置匹

Spring 框架之Springfox使用详解

《Spring框架之Springfox使用详解》Springfox是Spring框架的API文档工具,集成Swagger规范,自动生成文档并支持多语言/版本,模块化设计便于扩展,但存在版本兼容性、性... 目录核心功能工作原理模块化设计使用示例注意事项优缺点优点缺点总结适用场景建议总结Springfox 是

嵌入式数据库SQLite 3配置使用讲解

《嵌入式数据库SQLite3配置使用讲解》本文强调嵌入式项目中SQLite3数据库的重要性,因其零配置、轻量级、跨平台及事务处理特性,可保障数据溯源与责任明确,详细讲解安装配置、基础语法及SQLit... 目录0、惨痛教训1、SQLite3环境配置(1)、下载安装SQLite库(2)、解压下载的文件(3)、

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图