[译]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

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传