翻译说明:

原标题: 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系列之考虑使用原始类型的数组优化性能(五)相关推荐

  1. [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)

    简述: 不知道是否有小伙伴还记得我们之前的Effective Kotlin翻译系列,之前一直忙于赶时髦研究Kotlin 1.3中的新特性.把此系列耽搁了,赶完时髦了还是得踏实探究本质和基础,从今天开始 ...

  2. Kotlin系列之集合和函数式API完全解析-上篇

    简述: 今天带来的是Kotlin浅谈系列的第八讲,这讲我们一起来聊聊Kotlin这门语言对函数式编程的支持.我们都知道在kotlin这门语言中函数荣升成为了一等公民,所以在支持函数式编程的方面,Kot ...

  3. Kotlin系列之基础语法

    标签: Kotlin      Kotlin基础语法 目录: 一.包的声明 二.函数的定义 三.常量和变量 四.注释 五.字符串模板 六.使用条件表达式 七.NULL检查机制 八.类型检测以及自动类型 ...

  4. Kotlin系列之Lambda编译成字节码过程完全解析

    简述: 今天带来的是Kotlin浅谈系列第七弹,上篇博客我们聊到关于Kotlin中的lambda表达式的一些语法规则和基本使用.然而我们并没有聊到Kotlin的lambda表达式的本质是什么?我们都知 ...

  5. Kotlin系列之let、with、run、apply、also函数的使用

    标签: Kotlin      常用技巧 目录: 一.回调函数的Kotin的lambda的简化 二.内联扩展函数之let 三.内联函数之with 四.内联扩展函数之run 五.内联扩展函数之apply ...

  6. Kotlin系列之Lambda表达式(1)

    今天开始后续的几篇Kotlin的文章会介绍Kotlin中Lambda表达式相关的内容. 什么是Lambda表达式 在Java8中引入了Lambda表达式,这是最令Java开发者激动和期待的一个功能.那 ...

  7. [译]C# 7系列,Part 7: ref Returns ref返回结果

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/12/12/c-7-series-part-7-ref-returns/ 背景 有两种方法可以将一个值传 ...

  8. [译]C# 7系列,Part 10: Spanlt;Tgt; and universal memory management Spanlt;Tgt;和统一内存管理

    原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/25/c-7-series-part-10-spant-and-universal-memory- ...

  9. [原创]商城系统下单库存管控系列杂记(二)(并发安全和性能部分延伸)

      商城系统下单库存管控系列杂记(二)(并发安全和性能部分延伸)     前言   参与过几个中小型商城系统的开发,随着时间的增长,以及对系统的深入研究和测试,发现确实有很多值得推敲和商榷的地方(总有 ...

最新文章

  1. ChineseCalendar类[转]
  2. 容器技术Docker云计算实战 镜像操作大总结
  3. windows2003权限如何配置
  4. mysql的引双向链表_一分钟掌握MySQL的InnoDB引擎B+树索引
  5. 错误Cannot resolve org.springframework.data:spring-data-redis:2.2.6 RELEASE
  6. 怎么做一张优雅的数据源监控报表
  7. red hat linux 改ip,Red Hat Enterprise Linux 7(RHEL7)配置静态IP地址
  8. 在编程的路上遇见另一个自己
  9. 成功编译无错MAC OpenJDK8:Terminating app due to uncaught exception ‘NSInternalInconsistencyException‘
  10. Axure原型设计工具--产品经理必备
  11. 利用python构建信用卡评分
  12. python nltk 8 分析句子结构
  13. Android 如何判断萤石云视频是否可以播放
  14. #惊奇建模主仆见证了 Hobo 的离别
  15. 做人最大的无知,是错把平台当本事(深度好文)
  16. 「领域驱动设计」DDD,六边形架构,洋葱架构,整洁架构,CQRS的整合架构
  17. 海康威视主码流和子码流的区别
  18. openlayers3(五)根据坐标点画圆、线、多边形
  19. P3975 [TJOI2015]弦论 第K小子串
  20. 【单双节锂电池6-8.4V升压9V,12V,24V快充PD升压系统解决方案】

热门文章

  1. facenet源码使用记录
  2. wifi 中间人攻击_什么是中间人攻击?该如何防止?
  3. auto和decltype
  4. mysql atlas分表_Atlas 分表功能
  5. C++之常引用对象只能调用常成员函数、重载为成员函数和友元函数的区别
  6. 通过DuplicateHandle防止文件删除
  7. 阿里云搭建web应用超详细全套完整图文教程!菜鸟也能看懂!
  8. python qq空间登录_模拟登录QQ空间
  9. Three.js 解决纹理渲染后模型为黑色
  10. python爬虫实践之爬取美女图片