java动态脚本执行效率对比评测

Posted 阿里巴巴淘系技术团队官网博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java动态脚本执行效率对比评测相关的知识,希望对你有一定的参考价值。

本文作者针对实际场景及需求特性,经过安全性,易用性等综合评估,再结合工作场景选择javascript、lua、原生java进行性能测评。

背景

因工作需要,需要对java引入动态脚本的支持,当前可实现的动态脚本可选择的空间非常多,但是由于工作特性,作者需要满足一些特征(后面详述),于是把希望在网上看能否找到一些信息。网上针对脚本对比评测的文章有很多,包括涉及类似javascript、groovy、python、ruby等各种脚本,但是大部分为单一测试,且符合当前工作需求的评测较少。组合测试的又不太具有针对性,故作者结合工作场景有针对性的对部分动态脚本进行一期简单的性能评测。

需求分析

既然是特定工作业务场景,这里需要满足执行的脚本符合如下特征:

  1. 具有简单的逻辑判断能力:即动态脚本需要有,包括if语句,for循环等机制。这基本干掉了所有的表达式语言,包括优秀的google的aviator。当然如果仅仅只是简单的表达式解析,它是一个很好地选择

  2. 具有极高的安全稳定特性:这里不仅是需要被生产环境验证过的脚本引擎,更多的我需要脚本的功能只停留在数据处理及基本数据的构建上,不需要其他任何功能的实现,包括构建对象,各种IO及底层服务的调用。这基本干掉了类似groovy等经典的脚本语言,因为存在安全问题的同时,如何确保对内存的消耗有效回收又是另外不可忽略的风险(有很多使用了groovy出现OOM的案例)。

  3. 对性能的要求:虽然动态脚本本身都不是主推性能的,但是在生产环境,高并发是无法绕开的话题,能够在有限的条件下尽可能的满足高效性能也是重要考虑因素。

确定评测选手

针对实际场景及需求特性,经过从安全性,易用性等综合评估,最终选了3个具有代表性的选手进行对比评测:

选手1:最原生态

javascript

该动态脚本为java原生提供的能力,在「官方」「原生」等关键词的加持下,一直被认为有着非常优秀的性能条件,是不可忽略的对手

选手2:最轻量级

lua

业界普遍认为最轻量级的脚本语言。在「小」中做了最优的权衡,是所有实用性语言中规模最小的一种。因为它的小,被普遍用在移动端(含j2me)、游戏的动态脚本执行部分。同时又是因为它的快,又被普遍用在服务端领域(如nginx)中。

选手3:原生java

通过与原生java做对比比较,我们看看动态脚本与原生java到底有多大差距

评测备注

注意:每个脚本语言都有自身的优点和缺点,比如有的更贴近java语法,学习成本更低;有的附属设施更完善,应用场景更丰富;有的对资源消耗更少等。这些都不在本次的评测范围,本次仅仅只考虑对性能的一个对比,是在特定环境下的特定比较,不做整体好坏判断。

评测脚本内容

评测的脚本很简单,主要做这些事:

  1. 进行千万级的的for循环操作

  2. 进行不断的累加操作

  3. 进行简单的逻辑判断

  4. 进行字符串累加操作(部分场景)

最终看看各个脚本执行完成的时间,判断最终性能。

javascript脚本:

function test()
  var a=0;
  for(i=0; i<=10000000; i++)
    if(a<i)
      a++;
    ;
  ; 
  return a

lua脚本:

a = 0
for i=0,10000000,1 do
  if(i > a) then
    a=a + 1
  end
end
return a

纯java代码:

int a = 0;
for(int i = 0;i<=10000000;i++)
  if(i > a)
    a++;
  

测评环境

java:

测试情况

试验1

我们首先在1000w循环量级下跑下各个脚本情况,得到下表(单位ms)


javascript

lua

纯java

第一次

647

1374

10

第二次

689

1360

10

第三次

685

1392

8

第四次

686

1474

9

第五次

702

1433

10

第六次

699

1430

10

第七次

690

1594

10

第八次

731

1419

10

第九次

698

1455

9

第十次

674

1473

10

平均

690

1440

9.6

可以看出javascript在速度上有较大的优势,基本是lua的两倍以上

试验2

实际操作中大部分时候还是会进去字符串操作,那这里再增加以下字符串累加操作试试:

javascript:c=c+'c'

lua:c=c .. 'c'

纯java:c+='c'

加上了字符串的处理以后,其他不变,进行测试,情况就大不一样了:


javascript

lua

纯java

第一次

1829

x

116

第二次

1854

x

127

第三次

1841

x

148

第四次

1866

x

134

第五次

1839

x

132

第六次

1788

x

127

第七次

1824

x

117

第八次

1871

x

120

第九次

1821

x

145

第十次

1839

x

122

平均

1837


129

经过测试发现,即使纯java开销也不小。而此次lua直接超时(超过1min)

试验3

通过减少循环次数,最终在10w量级的for循环中跑出了结果


javascript

lua

纯java

第一次

545

1268

6

第二次

611

1298

6

第三次

619

1252

7

第四次

566

1289

7

第五次

584

1309

7

第六次

564

1378

6

第七次

591

1336

6

第八次

616

1286

7

第九次

579

1259

6

第十次

583

1286

7

平均

585

1296

6.5

整体看仍然javascript具有较大的优势,是lua的近2倍

试验4

最后在实际生产中,我们往往还需要对脚本引擎进行初始化,这也需要消耗大量资源,我们将初始化次数放到一起进行测试看看效果怎么样:

for循环1w次,内部循环10次,不装配字符串。js代码如下(其他代码类似,故省略)

public static void main(String[] args) throws Exception 
  long now = System.currentTimeMillis();
  for(int i = 0; i<10000;i++)
      jsScript();
  
  System.out.println(System.currentTimeMillis() - now);



  private static void jsScript() throws Exception 
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByExtension("js");


        engine.eval("function test()var a=0;for(i=0;i<=10;i++) if(a<i)a++;;; return a");
        Invocable inv = (Invocable) engine;
        String value = String.valueOf(inv.invokeFunction("test"));


js核心是构造这两个对象:

ScriptEngineManager mgr = new ScriptEngineManager();

ScriptEngine engine = mgr.getEngineByExtension("js");

lua是这个:

Globals globals = JsePlatform.standardGlobals();

纯java因为是宿主代码,不需要初始化

最终得到结果如下:


javascript

lua

纯java

第一次

18758

1353

1

第二次

18948

1329

1

第三次

19214

1483

1

第四次

18781

1446

1

第五次

18815

1441

1

第六次

18929

1495

1

第七次

18782

1444

1

第八次

18860

1412

1

第九次

19046

1471

1

第十次

18353

1375

1

平均

18848

1425

1

结果大跌眼镜,加入初始化构造后,javascript反而比lua慢了不少,而且有近9倍的差距。

那是不是每次使用的时候都要初始化对象呢?,通过查看:

https://stackoverflow.com/questions/30140103/should-i-use-a-separate-scriptengine-and-compiledscript-instances-per-each-threa

以及对应官网:

https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/api.html#BABHIFEF

发现js引擎不需要每次重复注册,只需要更新bindings即可。

        ScriptContext newContext = new SimpleScriptContext();

       newContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);

       Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);

       engine.setContext(newContext);

同理,lua也可以将这个单独拎出来

Globals globals = JsePlatform.standardGlobals();

试验5


javascript

lua

纯java

第一次

3028

321

1

第二次

3477

308

1

第三次

3147

321

1

第四次

3160

321

1

第五次

3570

322

1

第六次

3331

314

1

第七次

3594

410

1

第八次

3431

318

1

第九次

3356

339

1

第十次

3573

340

1

平均

3367

331

1

这里javascript执行效率低于其他两个的原因主要是有个编译字节码的过程。

写在最后

▐  结论

简单说说最终结论

  1. 不要偷懒。所有脚本引擎不要从头构建引擎对象,虽然这样简单粗暴。但是效率上也是有近5~6倍的差距

  2. 如果你的脚本相对比较复杂,里面有大量的for循环以及字符串处理。推荐使用javascript。它在处理复杂脚本优势很明显,当然这全靠他内部会编译成java字节码给到jvm执行的功劳(注意java6里的js不是同一个引擎,不会编译字节码,慢很多)。

  3. 如果你的脚本相对比较简单,没有大量的for循环等语句,那么lua是比较好的选择,占用资源更少,通用性更高。

  4. 无论是何种脚本语言,它的性能都是纯java的百分之一以上,除非必要,使用脚本语言一定要慎重。

▐  参考文档

https://blog.csdn.net/fuhanghang/article/details/124723417

https://blog.51cto.com/fengbohaishang/1080126

https://www.iteye.com/topic/361794

https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/api.html#BABHIFEF

https://www.chrismoos.com/2010/03/24/groovy-scripts-and-jvm-security/

https://stackoverflow.com/questions/30140103/should-i-use-a-separate-scriptengine-and-compiledscript-instances-per-each-threa

团队介绍

我们是大淘宝技术-行业与运营工作台团队,我们立足于对天猫淘宝行业商业价值理解,基于数字化驱动的商家运营、商品运营、内容运营策略,构建垂直行业消费者导购、交易、物流、服务创新产品,助力垂直行业端到端用户体验提升及客户生意增长

【团队招聘】:行业JAVA开发工程师
【工作地点】:杭州
简历投递邮箱:lulu.ll@alibaba-inc.com,欢迎来撩~

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

以上是关于java动态脚本执行效率对比评测的主要内容,如果未能解决你的问题,请参考以下文章

Gradle入门笔记

初学Python

Python 开发语言简介

编程语言之间的差异

Python学习之路——编程语言介绍

开发之路