4个代码生成库的性能比较

标签: 代码 性能 | 发表时间:2014-07-22 18:44 | 作者:
出处:http://it.deepinmind.com

在本系列的 第一篇文章中,我们介绍了Java的强类型及动态类型系统 。结论就是这个类型系统让你可以写出表述性强,健壮的应用程序,但是它限制了框架API与用户类型协作的能力。我们还知道了为什么Java的反射API并不总是与用户类型交互的最佳方式。为了将这点解释清楚,我们还分析了一个简单的安全库的实现,它使用了反射API,但却破坏了类型安全,为了保留用户类型,我们使用了代码生成的方式。

在文章的第二部分,我们分析了不同的代码生成库,并重点介绍了一下我自己开发的一个库, Byte Buddy。然后我们基于这个库来实现了一个简单的安全框架。

本文是最后一篇,我们想比较一下不同的库实现之间的性能差别。如果你还没读过前面两部分,最好先看一遍再继续阅读本文。我保证,我会等你看完再继续往下讲(注:这是哄小孩子吗:-))

初识代码生成器

总的来说,好的API并不是一个优秀的代码生成库的唯一条件。代码库的运行时性能可能是一个更重要的因素,尤其是当生成的代码在运行的程序中处于一个比较关键的位置的时候。关于代码生成库的性能,坊间有着诸多传闻,不过我还没找到关于任何一项技术的靠谱的基准测试。

在Java中进行微基准测试并不是一件容易的事情 。如果你要测量一个指定的代码块的执行时间,你通常不知道你测量的到底是什么。Java代码在执行的时候,JIT编译器通常都会介入,最极端的情况下,它可能会擦除掉被测量的代码。

然而在过去的几年里,有几个聪明的家伙想出了一些办法来欺骗JIT编译器,并基于这些想法实现了一些微基准测试的库。我个人最喜欢的是 Java Microbenchmarking Harness,它是随着Open JDK发布的一款工具。

在进行数据测量之前,有必要先回答一个问题:基准测试的目标和关注点是什么?很明显,有些任务使用某个库处理起来可能会更高效些,而另一些任务则可能花的时间就要更长一点。

除此之外,代码生成库通常会牺牲创建类的时间来减少生成类的方法调用的时间。当我们在讨论下面这些数据的时候,应当时刻牢记这点。

看下这些数据吧

在记住我们前面说的东西的同时,我们先来看一个直接比较不同任务的运行时间的JMH基准测试的原始数据。下表中的数据是指每个操作所需要的纳秒数,空格中是采样的标准误差。

Byte Buddy cglib javassist JDK proxy
使用stub方法实现接口 153.800 (0.394) 804.000 (1.899) 706.878 (4.929) 973.650 (1.624)
调用子方法 0.001 (0.000) 0.002 (0.000) 0.009 (0.000) 0.005 (0.000)
继承类调用父类方法 172.126 (0.533) 1480.525 (2.911) 625.778 (1.954) -
2290.246 (7.034)
调用父类方法 0.002 (0.000) 0.019 (0.000) 0.027 (0.000) -
0.003 (0.000)

第一行显示的是库生成这18个不同接口的空实现所需的时间。在这些生成的运行时类的基础之上,第二行显示的是调用这个生成类实例中的方法所需要的时间。

在这次测试中,Byte Buddy以及cglib的性能最好,因为这两个库你都能将返回值硬编码到生成类中,而javassist和JDK代理都只允许注册一个相应的回调函数。

这样我们可以得出第一个粗略的结论,这就是运行时类的方法实现越具体的话性能越好。听起来显然应该是这样,但其实不然,因为JIT编译器可能会优化这两种方法的性能。

类继承的情况如何

上表中的第三行显示的是继承一个包含18个方法的类所需要的时间。这次并不是创建一个方法存根,而是重写了方法,并调用了它父类的实现。

你可能已经注意到了,Byte Buddy列出了两个测量值,而第二个斜体的数字明显要更大。两个数值代表的是实现父方法调用的两种不同的实现方式。

正如上周所提到的,JVM只允许在同一个实例内进行父方法的调用。因此,调用super方法的最简单的方式就是在拦截方法里进行父方法的调用,这个拦截方法在第一次测试的时候已经实现好了。

但这个方法的灵活性不够,比方说它并不能根据条件来进行调用。为了克服这一限制,Byte Buddy允许你创建一个类似内部类的东西。在本文的前一部分中我们就介绍过了这种方法,在那篇文章中我们生成了一个实现了Callable接口的代理类。

对于任何调用而言,内部类的实例是通过方法中的一个参数所对应的注解注入到拦截方法里的。正如你所看到的,这种创建了一个额外的类的方式,跟其它使用相同策略的库相比,调用super方法所消耗的时间大大减少了。

与此同时,为每个方法生成一个专门的类会带来生成子类的额外开销。cglib和javassist都选择了一种折中的方案来解决这一问题,它们省掉了创建额外类的开销,代价就是每次父方法调用都会增加额外的开销。

结束语:都是为了提升性能

这里有许多值得讨论的东西,不过与此同时,这也是个结束这次代码生成简介的重要时刻。我希望这次概述能帮助你认识到代码生成其实并没有什么神秘的,这并不是只有大型框架才能使用的。有一个顺手的库的话,即使是很小的项目,你也可以使用代码生成来完成切面关注的漂亮的API,而不用增加显式的依赖关系。

现在Java 8已经开始逐渐流行起来,它的新的元空间不再严格限制Java应用包含的类的数量了。有了这些之后,就没有什么能再束缚住你的手脚了,放手去干吧。

原创文章转载请注明出处: 4个代码生成库的性能比较

英文原文链接

相关 [代码 性能] 推荐:

Java 代码性能优化

- - IT瘾-geek
代码 优化,一个很重要的课题. 可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢. 这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗. 没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了. 代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的.

也谈JavaScript代码性能优化

- 可乐加糖 - Fdream's Blog
差不多两年前写了个选择器whiz,除在DOM查找方面做了许多优化工作之外,还在代码优化上做了很多工作,一直没有分享. 抽空总结一下,基本上在jQuery、Mootools和YUI的源码里面都可以看到这些写法. 有些是已经在网上分享很多遍了,众所周知的,也有一些可能写了多年的JavaScript的开发人员也不一定想得到的.

查看php代码的性能——xhprof

- - cloudfly
今天是头脑风暴,想测试一下现在做得项目的执行效率. google了一下,发现了xhprof,试了一试,感觉真是个神器啊. 就是facebook放出的一个开源的,用来测试php代码性能的工具. 在网上也搜到了Xdebug,但是都说特别耗资源. 而xhprof是个轻量级的,而且用户体验也相当不错. 官方地址: http://pecl.php.net/package/xhprof.

编写高性能的Lua代码

- - 九点 科技
Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面,尤其是游戏. 像《魔兽世界》的插件,手机游戏《大掌门》《神曲》《迷失之地》等用Lua来写游戏逻辑. 所以大部分时候我们不需要去考虑性能问题. Knuth有句名言:“过早优化是万恶之源”. 其意思就是过早优化是不必要的,会浪费大量时间,而且容易导致代码混乱.

4个代码生成库的性能比较

- - Java译站
在本系列的 第一篇文章中,我们介绍了Java的强类型及动态类型系统. 结论就是这个类型系统让你可以写出表述性强,健壮的应用程序,但是它限制了框架API与用户类型协作的能力. 我们还知道了为什么Java的反射API并不总是与用户类型交互的最佳方式. 为了将这点解释清楚,我们还分析了一个简单的安全库的实现,它使用了反射API,但却破坏了类型安全,为了保留用户类型,我们使用了代码生成的方式.

Android性能优化篇:从代码角度进行优化

- - 移动开发 - ITeye博客
关注微信号:javalearns   随时随地学Java. 通常我们写程序,都是在项目计划的压力下完成的,此时完成的代码可以完成具体业务逻辑,但是性能不一定是最优化的. 一般来说,优秀的 程序员在写完代码之后都会不断的对代码进行重构. 重构的好处有很多,其中一点,就是对代码进行优化,提高软件的性能.

使用Benchmark.js和jsPerf分析代码性能

- - SegmentFault 最新的文章
前端开发中,掌握好浏览器的特性进行有针对性的性能调优是一项基本工作,同时,比较不同代码的执行速度也是一项关键的工作. 比如,当我们想比较 RegExp的 test方法和 String对象的 indexOf方法查找字符串谁的速度更快的话, js代码在不同的浏览器,不同的操作系统环境运行的效率可能是不一样的,这就是为什么我们需要对其进行基准测试,在做基准测试方面,我们可以使用 Benchmark.js和使用 jsPerf(一个基于 JSLitmus的基准测试库).

不改一行代码定位线上性能问题

- - crossoverJie's Blog
最近时运不佳,几乎天天被线上问题骚扰. 前几天刚解决了一个 HashSet 的并发问题,周六又来了一个性能问题. 我们提供出去的一个 OpenAPI 反应时快时慢,快的时候几十毫秒,慢的时候几秒钟才响应. 由于这种也不是业务问题,不能直接定位. 所以尝试在测试环境复现,但遗憾的测试环境贼快. 中途有抱着侥幸心里让运维查看了 Nginx 里 OpenAPI 的响应时间,想把锅扔给网络.

编写高性能 Java 代码的最佳实践

- - 码农网 » JAVA开发
摘要:本文首先介绍了负载测试、基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践. 最后研究了JVM特定的调优技巧、数据库端的优化和架构方面的调整. 在这篇文章中,我们将讨论几个有助于提升Java应用程序性能的方法. 我们首先将介绍如何定义可度量的性能指标,然后看看有哪些工具可以用来度量和监控应用程序性能,以及确定性能瓶颈.

JVM 性能调优实战之:使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码

- - ImportNew
本文是《 JVM 性能调优实战之:一次系统性能瓶颈的寻找过程》 的后续篇,该篇介绍了如何使用 JDK 自身提供的工具进行 JVM 调优将 TPS 由 2.5 提升到 20 (提升了 7 倍),并准确定位系统瓶颈:我们应用里静态对象不是太多、有大量的业务线程在频繁创建一些生命周期很长的临时对象,代码里有问题.