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 = 0for i=0,10000000,1 do  if(i > a) then    a=a + 1  endendreturn a


纯java代码:

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

image.png


试验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/124723417https://blog.51cto.com/fengbohaishang/1080126https://www.iteye.com/topic/361794https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/api.html#BABHIFEFhttps://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


团队介绍


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

相关文章
|
7月前
|
Java
java实现动态验证码源代码——绘制验证码的jsp
java实现动态验证码源代码——绘制验证码的jsp
|
7月前
|
Java 关系型数据库 MySQL
Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
【4月更文挑战第12天】Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
710 3
|
5月前
|
运维 监控 算法
java实现一个动态监控系统,监控接口请求超时的趋势
java实现一个动态监控系统,监控接口请求超时的趋势
273 2
|
6月前
|
Java jenkins 持续交付
Java项目jar包启动脚本,适用jenkins或定时任务或手动执行
Java项目jar包启动脚本,适用jenkins或定时任务或手动执行
373 3
|
2月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
71 1
|
7月前
|
Java
【专栏】Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性
【4月更文挑战第27天】本文探讨了Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性。反射通过Class、Constructor、Method和Field类实现。文中列举了反射的应用场景,如动态创建对象、调用方法、访问属性和处理注解,并提供了相关实例代码演示。
88 4
|
2月前
|
Java Python
如何通过Java程序调用python脚本
如何通过Java程序调用python脚本
43 0
|
2月前
|
SQL Java 数据库连接
如何在 Java 脚本中有效地使用 JDBC
如何在 Java 脚本中有效地使用 JDBC
18 0
|
4月前
|
运维 Java API
探索Java中的Lambda表达式自动化运维的魔法:如何利用Python脚本提升效率
【8月更文挑战第29天】Lambda表达式是Java 8中引入的一个新特性,它允许我们将功能作为方法参数,或者代码作为数据来处理。在这篇文章中,我们将深入探讨Java中的Lambda表达式,包括它的语法、使用场景以及如何在实际编程中应用它。我们将通过一些简单的示例来演示Lambda表达式的强大功能和灵活性,让你更好地理解和掌握这一新特性。
|
4月前
|
Java Shell
Java Sprintboot jar 项目启动、停止脚本
Java Sprintboot jar 项目启动、停止脚本
40 0