既然是特定工作业务场景,这里需要满足执行的脚本符合如下特征:
确定评测选手
针对实际场景及需求特性,经过从安全性,易用性等综合评估,最终选了3个具有代表性的选手进行对比评测:
javascript
该动态脚本为java原生提供的能力,在「官方」「原生」等关键词的加持下,一直被认为有着非常优秀的性能条件,是不可忽略的对手
lua
业界普遍认为最轻量级的脚本语言。在「小」中做了最优的权衡,是所有实用性语言中规模最小的一种。因为它的小,被普遍用在移动端(含j2me)、游戏的动态脚本执行部分。同时又是因为它的快,又被普遍用在服务端领域(如nginx)中。
通过与原生java做对比比较,我们看看动态脚本与原生java到底有多大差距
评测的脚本很简单,主要做这些事:
进行千万级的的for循环操作
进行不断的累加操作
进行简单的逻辑判断
进行字符串累加操作(部分场景)
最终看看各个脚本执行完成的时间,判断最终性能。
function test(){
var a=0;
for(i=0; i<=10000000; i++){
if(a<i){
a++;
};
};
return a
}
a = 0
for i=0,10000000,1 do
if(i > a) then
a=a + 1
end
end
return a
int a = 0;
for(int i = 0;i<=10000000;i++){
if(i > a){
a++;
}
}
测评环境
实际操作中大部分时候还是会进去字符串操作,那这里再增加以下字符串累加操作试试:
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)
通过减少循环次数,最终在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 |
最后在实际生产中,我们往往还需要对脚本引擎进行初始化,这也需要消耗大量资源,我们将初始化次数放到一起进行测试看看效果怎么样:
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);
Globals globals = JsePlatform.standardGlobals();
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执行效率低于其他两个的原因主要是有个编译字节码的过程。
写在最后
团队介绍
我们是大淘宝技术-行业与运营工作台团队,我们立足于对天猫淘宝行业商业价值理解,基于数字化驱动的商家运营、商品运营、内容运营策略,构建垂直行业消费者导购、交易、物流、服务创新产品,助力垂直行业端到端用户体验提升及客户生意增长
【团队招聘】:行业JAVA开发工程师
【工作地点】:杭州
简历投递邮箱:lulu.ll@alibaba-inc.com,欢迎来撩~