为了账号安全,请及时绑定邮箱和手机立即绑定

JVM Advent Calendar:将Kotlin性能与Graal和C2进行比较

标签:
Java


首先,让我们谈谈测试方法:

所有 测试 都 在 我的 笔记本 上 运行i7  2.0 Ghz  16 Gb  Ubuntu  18.4  和 OpenJdk  11

graal  VM  选项:

- Xms6g  - Xmx6g  - XX:+ UseParallelOldGC  - XX:+ UnlockExperimentalVMOptions  - XX:+ UseJVMCICompiler

c2  VM  选项:

- Xms6g  - Xmx6g  - XX:+ UseParallelOldGC

我 运行 一个 测试 称为 PerformanceTest  在 每个 项目中 ,其 执行 一 结束 ,以 结束 标杆 不断。在 这种 方式 的 编译器 不能 优化 的 代码 为 一个 特定的 情况下。于是 我 选择 了 更快的 结果,假设 它 是 在 一个 没有 GC  和 OS  暂停。所有的 测试 均 运行 单- 线程。

以下是结果:

正如你所看到的,Graal使用Kotlin进行编译在小板上的速度明显更快,并且随着大板随机播放的速度稍快一点。

我怀疑这是因为随着更大的主板内存管理,它占用了大量的运行时间。在任何情况下,增量都是受欢迎的,特别是考虑到我通常在较小的板上玩。

为了测试我的理论,我创建了一个新项目,其中包含一些经典算法的实现,以查看性能的差异。你可以在这里找到它。

目前,有两种算法:Mandelbrot Set生成器和Knapsack解算器。

Mandelbrot集

该Mandelbrot集可能是你见过的最有名的分形-即使你不知道这个名字。

在数学上,它被定义为复平面中所有点的集合,其中函数z < - z ^ 2 + c在迭代时不会发散。生成Set迭代复杂平面上某些点的函数并从中创建图像非常容易。

由于这里的目标是性能而不是图形,我使用文本图形保持简单。

让我们从查看Mandelbrot Set的代码开始。

数据 类 Complex(val  r:Double,val  i:Double){

    操作员 欢乐 时光(其他:复杂)=

        复杂(

            r  =  这个。r  *  其他。r  -  这个。我 *  其他。我,

            我 =  这个。我 *  其他。r  +  这个。r  *  其他。一世

        )

    操作员 乐趣 加(其他:复杂)=

        复杂的(- [R  =  此。[R  +  等。[R ,我 =  此。我 +  其他。我)

    操作员 乐趣 减去(其他:复杂)=

        复杂的(- [R  =  此。[R  -  其他。ř,我 =  此。我 -  其他。我)

    fun  squared()=  this  *  this

    有趣的是 squaredModule()=  r  *  r  +  i  *  i

    有趣的 双。toComplex()=  Complex(r = this,i = 0.0)

}

有趣的 mandelSet(initZ:Complex,c:Complex,maxIter:Int):Int {

    var  z  =  initZ

    (1.。MAXITER)。forEach {

        z  =  z。平方()+  c

        如果(ž。squaredModule()> =  4)

            归还 它

    }

    return  maxIter

}

您可以在此处看到如何使用运算符重载和数据类来表示复数,这样可以真正简化代码并使其更易于理解。

一旦我们在Complex类中定义了对复数进行操作的规则,该  mandelSet 函数只需要检查操作z < - z ^ 2 + c是否“转义”,并且万一,经过多次迭代后,它将超过4的门槛。

在这里,您可以在AsciiArt中呈现的输出中看到Mandelbrot Set的特征心形图:

背包问题

该背包问题可以通过多种方式来定义。想象一下,作为一个刚刚闯入手表店的小偷。如果您没有超过背包中可携带的最大重量,您可以偷走任意数量的手表。

作为一个实用的小偷,你绝对想要优化你带来的手表的价值。每只手表都有价格和重量。因此,您需要找到具有给定重量的最大总价的手表组。

实际应用包括优化CNC应用的削减和材料以及分配广告预算的营销策略。

例如,让我们看一下只有三只手表的商店,定义如下:

val  shop  =  背包。商店(

    观察(重量 =  1,价格 =  1),

    观察(重量 =  3,价格 =  2),

    观看(重量 =  1,价格 =  3)

如果我们的最大重量为1,那么我们最好选择第三只手表,而不是第一只手表,因为价值更高。

如果我们的最大权重为3,我们可以选择数字2(价格2)或数字1和3(价格1 + 3)。在这种情况下,最好选择1和3,即使它们的总重量小于最大值。

这些是这个商店的完整解决方案:

assertEquals(3,selectWatches(shop,maxWeight  =  1))

assertEquals(4,selectWatches(shop,maxWeight  =  2))

assertEquals(4,selectWatches(shop,maxWeight  =  3))

assertEquals(5,selectWatches(shop,maxWeight  =  4))

assertEquals(6,selectWatches(shop,maxWeight  =  5))

如您所见,随着可用手表数量的增加,可能的选择数量变得非常非常快。这是一个经典的NP-Hard问题。

要在合理的时间内解决它,我们需要作弊并使用动态编程。我们可以使用针对每组手表的已经优化的解决方案构建地图,因此,我们可以避免每次重新计算它们。

通用算法基于基于递归的穷举搜索。这是解决它的Kotlin代码,在memoization函数和最大值的递归搜索中分开。

typealias  Memoizer  =  MutableMap < String,Int >

有趣的 priceAddingElement(备忘录:Memoizer,shop:Set < Watch >,选择:Set < Watch >,maxWeight:Int,priceSum:Int):Int  =

    商店。过滤 { !(它 在 选择中)&&  它。重量 <=  maxWeight }

        。地图 {

            selectWatches(

                备忘录,

                商店,

                maxWeight  -  它。重量,

                选择 +  它,

                priceSum  +  它。价格)}

        。过滤 { it  >  priceSum }

        。max()?:priceSum

有趣的 selectWatches(备忘录:Memoizer,shop:Set < Watch >,maxWeight:Int,choice:Set < Watch >,priceSum:Int):Int  =

    memoization(memo,generateKey(choice)){

        priceAddingElement(备忘录,商店,选择,maxWeight,priceSum)}

private  fun  memoization(memo:Memoizer,key:String,f :()- >  Int):Int  =  when(val  w  =  memo [ key ]){

        null  - >  f()。还 { memo [ key ] =  it }

        否则 - >  w

    }

我真的很喜欢Kotlin如何让你清楚地表达意图,而不必重复自己。如果您不了解Kotlin,我希望这段代码可以吸引您,并在某一天尝试它。

基准

现在,您正在等待的部分,让我们比较Graal与优秀的'C2编译器'的性能。

让我们记住,Graal是用Java编写的,并且正在利用编译器领域的新研究,但它仍然相对年轻。另一方面,C2非常好地调整和成熟。

第一个惊喜是Mandelbrot的例子:

说实话,我没想到性能会有这么大的差异。Graal比C2快约18%!只是为了确定,我用稍微不同的公式再次尝试并收到了相同的结果。Graal在编写Kotlin的计算时非常精彩。

而现在,更令人惊讶的是,背包测试:

在这里,Graal慢了54%!

做一些分析,我发现我的代码大部分时间都花在了为memoization生成密钥的函数上。

为了确保我是正确的,我订购了套装,然后将其转换为字符串。这是很多不必要的工作,它依赖于  HashSet Java实现。

所以,我改变了方法来生成密钥:

private  fun  generateKey(choice:Set < Watch >):String  =

  选择。sortedBy { “$ {it.price}  -  $ {it.weight}” }。toString()

对此:

private  fun  generateKey(choice:Set < Watch >):String  =

   选择。地图 { 它。hashCode()}。sorted()。joinToString(“”)

新功能更快,因为它对手表的哈希值进行排序,这些哈希值是唯一的,然后将它们连接成一个字符串。

请注意,我们不能简单地使用Set的哈希值,因为可能存在哈希冲突。我实际上尝试过并验证它开始发出错误的结果。

可以为Set创建更安全的散列方法,但这里的目标不是最大限度地优化算法,而是编写高效且清晰的Kotlin代码。

现在,让我们来看看惯用的Kotlin的结果:

在这里,Graal再次明显比C2快,总的来说,新的密钥生成器比以前的实现快得多。

我对这些结果的猜测是,C2经过大量优化(使用内在函数等)用于典型的Java用法,而Graal擅长编译小方法和轻量级对象,这是典型的惯用Kotlin。

 我希望这篇文章能激励更多Kotlin开发人员使用Graal!快乐的编码!

©著作权归作者所有:来自51CTO博客作者萤火的萤的原创作品,如需转载,请注明出处,否则将追究法律责任


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消