Dart JIT的startsWith为何性能远低于JavaScript同名方法?
Dart JIT环境下startsWith性能远低于JavaScript环境的核心原因
基准测试说明
测试对比两种字符串匹配逻辑的执行耗时:
- 逻辑1:调用字符串内置
startsWith方法从偏移1位置匹配固定字符串"123" - 逻辑2:手动通过
codeUnitAt取固定偏移位置的字符编码,和1、2、3的ASCII编码直接比对
测试代码如下:
void _test1(int count) { final source = ' 123 '; for (var i = 0; i < count; i++) { var x = source.startsWith('123', 1); } } void _test2(int count) { final source = ' 123 '; for (var i = 0; i < count; i++) { var ok = source.codeUnitAt(1) == 0x31 && source.codeUnitAt(2) == 0x32 && source.codeUnitAt(3) == 0x33; var x = ok; } }
JavaScript环境测试结果
Time passed: 0.000, Test 'startsWith': 105.4 ms Time passed: 0.106, Test 'codeUnitAt': 87.8 ms Time passed: 0.194, Test 'startsWith': 86.7 ms Time passed: 0.280, Test 'codeUnitAt': 87.101 ms Time passed: 0.368, Test 'startsWith': 87 ms Time passed: 0.455, Test 'codeUnitAt': 87.2 ms Time passed: 0.542, Test 'startsWith': 86.699 ms Time passed: 0.629, Test 'codeUnitAt': 85.6 ms Time passed: 0.715, Test 'startsWith': 85.7 ms Time passed: 0.801, Test 'codeUnitAt': 86.6 ms
JS环境下两种逻辑性能基本持平,startsWith平均耗时约85.7ms。
Dart JIT环境测试结果
Time passed: 0.000, Test 'startsWith': 837.2 ms Time passed: 0.842, Test 'codeUnitAt': 125.094 ms Time passed: 0.968, Test 'startsWith': 924.482 ms Time passed: 1.892, Test 'codeUnitAt': 131.861 ms Time passed: 2.024, Test 'startsWith': 736.603 ms Time passed: 2.761, Test 'codeUnitAt': 76.636 ms Time passed: 2.838, Test 'startsWith': 792.341 ms Time passed: 3.631, Test 'codeUnitAt': 61.732 ms Time passed: 3.693, Test 'startsWith': 721.816 ms Time passed: 4.415, Test 'codeUnitAt': 73.93 ms
Dart JIT环境下startsWith平均耗时约721.8ms,是JS环境的8倍以上,也远高于同环境下手写codeUnitAt比对的性能。
性能差异核心成因
- 两端引擎对内置方法的优化策略完全不同
V8引擎(JS运行时)把String.prototype.startsWith作为核心内置函数做了IR层的深度特化:当编译器识别到调用点传入的匹配串是常量、起始偏移是固定值时,会直接把整个方法调用降级为连续的固定偏移内存取值+常量对比操作,生成的机器码和手写codeUnitAt比对的逻辑完全一致,不存在额外开销,因此两者性能持平。
而Dart VM JIT下的startsWith走通用实现逻辑:首先要做参数合法性校验(偏移是否越界、入参是否为空),同时因为startsWith的入参支持所有实现Pattern接口的类型(不止String,还支持正则等),即使实际传入的是固定字符串常量,JIT编译器也不会做激进的调用点特化,不会把方法内联折叠成原生的字符比对逻辑,每次调用都要走类型判断、分支跳转的固定开销。 - Dart JIT的编译优化保守性限制
手写codeUnitAt比对的逻辑,Dart JIT可以做极致优化:识别到source是固定常量字符串、偏移是固定值,直接把循环内的操作折叠成常量返回,甚至消除整个循环的执行开销,因此性能极高。但startsWith作为core库的通用方法,JIT编译时采用保守策略,要兼容所有可能的入参场景,不会针对固定常量入参的场景做特殊优化,因此始终保留了函数调用、类型检查、通用循环比对的开销。 - 补充验证结论
若将测试中startsWith的入参改为动态变量而非固定常量,JS端的startsWith性能同样会出现明显下降;若使用Dart AOT模式编译运行,编译器可做全局调用点分析,对常量入参的短字符串startsWith调用做特化优化,与手写codeUnitAt的性能差距会缩小至10%以内。
内容的提问来源于stack exchange,提问作者mezoni
相关产品推荐
相关产品推荐

