technology-blog
technology-blog copied to clipboard
第 19 题:(开放题)a.b.c.d和a['b']['c']['d'],哪个性能更高
别看这题,题目上每个字都能看懂,但是里面涉及到的知识,暗藏杀鸡
这题要往深处走,会涉及ast抽象语法树、编译原理、v8内核对原生js实现问题
我觉得这个题是这篇文章里最难的一道题,所以我放在了开放题中的最后一题
大家多多思考,这题的答案也会在周五公
从性能的角度会有区别吗?最终都需要通过索引去访问吧,而不是在编译阶段就确定好 offset? opcode 不知道会不会有区别?
从知乎看到这个问题,觉得很有意思,本人对v8了解也不是很深,跳过来插个眼,简单写了下a.b.c.d
v8的实现:
#include <node.h>
namespace __objectTest__ {
using v8::Isolate;
using v8::HandleScope;
using v8::Context;
using v8::MaybeLocal;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Number;
using v8::Value;
Local<Object> createD(Isolate* isolate) {
Local<Context> context = isolate->GetCurrentContext();
Local<Object> objD = Object::New(isolate);
MaybeLocal<String> mkey = String::NewFromUtf8(isolate, "d");
Local<String> key = mkey.ToLocalChecked();
Local<Number> val = Number::New(isolate, 23333);
objD->Set(context, key, val).FromJust();
return objD;
}
Local<Object> createC(Isolate* isolate) {
Local<Context> context = isolate->GetCurrentContext();
Local<Object> objC = Object::New(isolate);
MaybeLocal<String> mkey = String::NewFromUtf8(isolate, "c");
Local<String> key = mkey.ToLocalChecked();
Local<Object> val = createD(isolate);
objC->Set(context, key, val).FromJust();
return objC;
}
void Init(Local<Object> exports) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Local<Object> objA = Object::New(isolate);
MaybeLocal<String> mkey = String::NewFromUtf8(isolate, "b");
Local<String> key = mkey.ToLocalChecked();
Local<Object> val = createC(isolate);
objA->Set(context, key, val).FromJust();
exports->Set(String::NewFromUtf8(isolate, "a"), objA);
Local<Value> finalValB = objA->Get(context, key).ToLocalChecked();
Local<Object> oFinalValB = finalValB->ToObject(context).ToLocalChecked();
Local<Value> finalValC = oFinalValB->Get(context, String::NewFromUtf8(isolate, "c")).ToLocalChecked();
Local<Object> oFinalValC = finalValC->ToObject(context).ToLocalChecked();
Local<Value> sFinalVal = oFinalValC->Get(context, String::NewFromUtf8(isolate, "d")).ToLocalChecked();
Local<Number> finalVal = sFinalVal->ToNumber(context).ToLocalChecked();
exports->Set(String::NewFromUtf8(isolate, "val"), finalVal);
}
NODE_MODULE(objectTest, Init)
}
benchmark代码:
'use strict'
const test = require('./object/build/Release/object.node')
const Benchmark = require('benchmark')
let a = test.a
console.log(a.b.c.d, a['b']['c']['d'], test.val)
let suite = new Benchmark.Suite
suite.add('obj', function () {
a.b.c.d === 23333
}).add('string', function() {
a['b']['c']['d'] = 23333
}).add('c ++', function() {
test.val === 23333
}).on('cycle', function(event) {
console.log(String(event.target));
}).on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
}).run()
benchmark结果基本都比较恒定:
$ node bench.js
$ 23333 23333 23333
$
$ obj x 491,218,774 ops/sec ±0.88% (85 runs sampled)
$ string x 430,301,317 ops/sec ±2.19% (86 runs sampled)
$ c ++ x 514,275,549 ops/sec ±0.78% (87 runs sampled)
$ Fastest is c ++
benchmark结果是a.b.c.d
效率高。难道是因为在v8中a['b']['c']['d']
不能用数组下标obj->Get(context, index)
进行查找,而强制转成了obj->Get(context, key)
么。。。
插个眼
从知乎看到这个问题,觉得很有意思,本人对v8了解也不是很深,跳过来插个眼,简单写了下
a.b.c.d
v8的实现:#include <node.h> namespace __objectTest__ { using v8::Isolate; using v8::HandleScope; using v8::Context; using v8::MaybeLocal; using v8::Local; using v8::Object; using v8::String; using v8::Number; using v8::Value; Local<Object> createD(Isolate* isolate) { Local<Context> context = isolate->GetCurrentContext(); Local<Object> objD = Object::New(isolate); MaybeLocal<String> mkey = String::NewFromUtf8(isolate, "d"); Local<String> key = mkey.ToLocalChecked(); Local<Number> val = Number::New(isolate, 23333); objD->Set(context, key, val).FromJust(); return objD; } Local<Object> createC(Isolate* isolate) { Local<Context> context = isolate->GetCurrentContext(); Local<Object> objC = Object::New(isolate); MaybeLocal<String> mkey = String::NewFromUtf8(isolate, "c"); Local<String> key = mkey.ToLocalChecked(); Local<Object> val = createD(isolate); objC->Set(context, key, val).FromJust(); return objC; } void Init(Local<Object> exports) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); Local<Context> context = isolate->GetCurrentContext(); Local<Object> objA = Object::New(isolate); MaybeLocal<String> mkey = String::NewFromUtf8(isolate, "b"); Local<String> key = mkey.ToLocalChecked(); Local<Object> val = createC(isolate); objA->Set(context, key, val).FromJust(); exports->Set(String::NewFromUtf8(isolate, "a"), objA); Local<Value> finalValB = objA->Get(context, key).ToLocalChecked(); Local<Object> oFinalValB = finalValB->ToObject(context).ToLocalChecked(); Local<Value> finalValC = oFinalValB->Get(context, String::NewFromUtf8(isolate, "c")).ToLocalChecked(); Local<Object> oFinalValC = finalValC->ToObject(context).ToLocalChecked(); Local<Value> sFinalVal = oFinalValC->Get(context, String::NewFromUtf8(isolate, "d")).ToLocalChecked(); Local<Number> finalVal = sFinalVal->ToNumber(context).ToLocalChecked(); exports->Set(String::NewFromUtf8(isolate, "val"), finalVal); } NODE_MODULE(objectTest, Init) }
benchmark代码:
'use strict' const test = require('./object/build/Release/object.node') const Benchmark = require('benchmark') let a = test.a console.log(a.b.c.d, a['b']['c']['d'], test.val) let suite = new Benchmark.Suite suite.add('obj', function () { a.b.c.d === 23333 }).add('string', function() { a['b']['c']['d'] = 23333 }).add('c ++', function() { test.val === 23333 }).on('cycle', function(event) { console.log(String(event.target)); }).on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); }).run()
benchmark结果基本都比较恒定:
$ node bench.js $ 23333 23333 23333 $ $ obj x 491,218,774 ops/sec ±0.88% (85 runs sampled) $ string x 430,301,317 ops/sec ±2.19% (86 runs sampled) $ c ++ x 514,275,549 ops/sec ±0.78% (87 runs sampled) $ Fastest is c ++
benchmark结果是
a.b.c.d
效率高。难道是因为在v8中a['b']['c']['d']
不能用数组下标obj->Get(context, index)
进行查找,而强制转成了obj->Get(context, key)
么。。。
厉害了,这个题如果对v8不是很清楚确实回答不上来,比如我这种菜鸟级别的。。。
卧槽 你们这么秀 我觉得我不适合写代码了!!!
这个题从AST角度看就很简单了,a['b']['c']和a.b.c,转换成AST前者的的树是含计算的,后者只是string literal,天然前者会消耗更多的计算成本,时间也更长
这个题用 byte code 看就很明了了,以 Jerryscript 为例(熟悉 V8 的同学可以把下面的代码编译成 V8 的字节码), 参考下面这段代码
var a = { b: { c: { d: 1 } } };
var v1 = a.b.c.d
var v2 = a['b']['c']['d']
对应的字节码如下:
0 : CBC_CREATE_OBJECT
1 : CBC_CREATE_OBJECT
2 : CBC_CREATE_OBJECT
3 : CBC_PUSH_NUMBER_POS_BYTE number:1
5 : CBC_SET_PROPERTY idx:11(lit)->string(d)
7 : CBC_SET_PROPERTY idx:10(lit)->string(c)
9 : CBC_SET_PROPERTY idx:9(lit)->string(b)
11 : CBC_ASSIGN_SET_IDENT idx:6(reg)->var_ident(a)
13 : CBC_PUSH_PROP_LITERAL_LITERAL idx:6(reg)->var_ident(a) idx:9(lit)->string(b)
16 : CBC_PUSH_PROP_LITERAL idx:10(lit)->string(c)
18 : CBC_PUSH_PROP_LITERAL idx:11(lit)->string(d)
20 : CBC_ASSIGN_SET_IDENT idx:7(reg)->var_ident(v1)
22 : CBC_PUSH_PROP_LITERAL_LITERAL idx:6(reg)->var_ident(a) idx:9(lit)->string(b)
25 : CBC_PUSH_PROP_LITERAL idx:10(lit)->string(c)
27 : CBC_PUSH_PROP_LITERAL idx:11(lit)->string(d)
29 : CBC_ASSIGN_SET_IDENT idx:8(reg)->var_ident(v2)
31 : CBC_RETURN_WITH_BLOCK
0-2 行创建了3个对象,3行设置了 d 的值为 1,5-9行设置了 property,11行将对象赋值 a,13-20行 赋值 v1,22-29行赋值 v2 所以可以看到两种方式在 Jerryscript 中都是以字面量的方式进行访问的,流程也是一样的,编译过程可能会有一些区别,具体还要看 vm 代码
假如通过变量访问
var a = { b: { c: { d: 1 } } };
var v1 = a.b.c.d
var k1 = 'd'
var v2 = a['b']['c'][k1]
字节码如下
0 : CBC_CREATE_OBJECT
1 : CBC_CREATE_OBJECT
2 : CBC_CREATE_OBJECT
3 : CBC_PUSH_NUMBER_POS_BYTE number:1
5 : CBC_SET_PROPERTY idx:12(lit)->string(d)
7 : CBC_SET_PROPERTY idx:11(lit)->string(c)
9 : CBC_SET_PROPERTY idx:10(lit)->string(b)
11 : CBC_ASSIGN_SET_IDENT idx:6(reg)->var_ident(a)
13 : CBC_PUSH_PROP_LITERAL_LITERAL idx:6(reg)->var_ident(a) idx:10(lit)->string(b)
16 : CBC_PUSH_PROP_LITERAL idx:11(lit)->string(c)
18 : CBC_PUSH_PROP_LITERAL idx:12(lit)->string(d)
20 : CBC_ASSIGN_SET_IDENT idx:7(reg)->var_ident(v1)
22 : CBC_ASSIGN_LITERAL_SET_IDENT idx:12(lit)->string(d) idx:8(reg)->var_ident(k1)
25 : CBC_PUSH_PROP_LITERAL_LITERAL idx:6(reg)->var_ident(a) idx:10(lit)->string(b)
28 : CBC_PUSH_PROP_LITERAL idx:11(lit)->string(c)
30 : CBC_PUSH_PROP_LITERAL idx:8(reg)->var_ident(k1)
32 : CBC_ASSIGN_SET_IDENT idx:9(reg)->var_ident(v2)
34 : CBC_RETURN_WITH_BLOCK
变化其实不大,只有2点变化:
- 新增了 22 行的赋值
k1='d'
- 30行通过变量去访问 property,之前是通过字面量
所以这两种方式在运行时性能上没有区别(基于 jerry)
@lolBig 兄弟跳槽吗
- 对应于a['b']['c']['d']上来说,得到的ast如下:
{
"type": "Program",
"start": 0,
"end": 16,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 16,
"expression": {
"type": "MemberExpression",
"start": 0,
"end": 16,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 11,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 6,
"object": {
"type": "Identifier",
"start": 0,
"end": 1,
"name": "a"
},
"property": {
"type": "Literal",
"start": 2,
"end": 5,
"value": "b",
"raw": "'b'"
},
"computed": true
},
"property": {
"type": "Literal",
"start": 7,
"end": 10,
"value": "c",
"raw": "'c'"
},
"computed": true
},
"property": {
"type": "Literal",
"start": 12,
"end": 15,
"value": "d",
"raw": "'d'"
},
"computed": true
}
}
],
"sourceType": "module"
}
对应于a.b.c.d来说,得到的ast如下:
{
"type": "Program",
"start": 0,
"end": 7,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 7,
"expression": {
"type": "MemberExpression",
"start": 0,
"end": 7,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 5,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 3,
"object": {
"type": "Identifier",
"start": 0,
"end": 1,
"name": "a"
},
"property": {
"type": "Identifier",
"start": 2,
"end": 3,
"name": "b"
},
"computed": false
},
"property": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "c"
},
"computed": false
},
"property": {
"type": "Identifier",
"start": 6,
"end": 7,
"name": "d"
},
"computed": false
}
}
],
"sourceType": "module"
}
如果是按照直接解释运行的话,无非就是a['b']的方式多了一个Literal的转换,实际上是基本没有什么区别。
按照V8 ByteCode考虑,a.b.c.d
的流程如下:
> console.log(aaa.b.c.d)
[generated bytecode for function: ]
Parameter count 1
Frame size 48
0 E> 0x2bd62d9eb1ba @ 0 : a1 StackCheck
0 S> 0x2bd62d9eb1bb @ 1 : 13 00 00 LdaGlobal [0], [0]
0x2bd62d9eb1be @ 4 : 26 f9 Star r2
0x2bd62d9eb1c0 @ 6 : 12 01 LdaConstant [1]
0x2bd62d9eb1c2 @ 8 : 26 f7 Star r4
0x2bd62d9eb1c4 @ 10 : 27 f9 f8 Mov r2, r3
8 E> 0x2bd62d9eb1c7 @ 13 : 61 0c f8 02 InvokeIntrinsic [_GetProperty], r3-r4
0x2bd62d9eb1cb @ 17 : 26 fa Star r1
12 E> 0x2bd62d9eb1cd @ 19 : 13 02 02 LdaGlobal [2], [2]
0x2bd62d9eb1d0 @ 22 : 26 f8 Star r3
0x2bd62d9eb1d2 @ 24 : 12 03 LdaConstant [3]
0x2bd62d9eb1d4 @ 26 : 26 f6 Star r5
0x2bd62d9eb1d6 @ 28 : 27 f8 f7 Mov r3, r4
16 E> 0x2bd62d9eb1d9 @ 31 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5
0x2bd62d9eb1dd @ 35 : 26 f8 Star r3
0x2bd62d9eb1df @ 37 : 12 04 LdaConstant [4]
0x2bd62d9eb1e1 @ 39 : 26 f6 Star r5
0x2bd62d9eb1e3 @ 41 : 27 f8 f7 Mov r3, r4
18 E> 0x2bd62d9eb1e6 @ 44 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5
0x2bd62d9eb1ea @ 48 : 26 f8 Star r3
0x2bd62d9eb1ec @ 50 : 12 05 LdaConstant [5]
0x2bd62d9eb1ee @ 52 : 26 f6 Star r5
0x2bd62d9eb1f0 @ 54 : 27 f8 f7 Mov r3, r4
20 E> 0x2bd62d9eb1f3 @ 57 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5
0x2bd62d9eb1f7 @ 61 : 26 f8 Star r3
8 E> 0x2bd62d9eb1f9 @ 63 : 57 fa f9 f8 04 CallProperty1 r1, r2, r3, [4]
0x2bd62d9eb1fe @ 68 : 26 fb Star r0
23 S> 0x2bd62d9eb200 @ 70 : a5 Return
Constant pool (size = 6)
Handler Table (size = 0)
Thrown:
a['b']['c']['d']
的ByteCode是这个
> console.log(aa['b']['c']['d'])
[generated bytecode for function: ]
Parameter count 1
Frame size 48
0 E> 0x2bd62d9eb532 @ 0 : a1 StackCheck
0 S> 0x2bd62d9eb533 @ 1 : 13 00 00 LdaGlobal [0], [0]
0x2bd62d9eb536 @ 4 : 26 f9 Star r2
0x2bd62d9eb538 @ 6 : 12 01 LdaConstant [1]
0x2bd62d9eb53a @ 8 : 26 f7 Star r4
0x2bd62d9eb53c @ 10 : 27 f9 f8 Mov r2, r3
8 E> 0x2bd62d9eb53f @ 13 : 61 0c f8 02 InvokeIntrinsic [_GetProperty], r3-r4
0x2bd62d9eb543 @ 17 : 26 fa Star r1
12 E> 0x2bd62d9eb545 @ 19 : 13 02 02 LdaGlobal [2], [2]
0x2bd62d9eb548 @ 22 : 26 f8 Star r3
0x2bd62d9eb54a @ 24 : 12 03 LdaConstant [3]
0x2bd62d9eb54c @ 26 : 26 f6 Star r5
0x2bd62d9eb54e @ 28 : 27 f8 f7 Mov r3, r4
14 E> 0x2bd62d9eb551 @ 31 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5
0x2bd62d9eb555 @ 35 : 26 f8 Star r3
0x2bd62d9eb557 @ 37 : 12 04 LdaConstant [4]
0x2bd62d9eb559 @ 39 : 26 f6 Star r5
0x2bd62d9eb55b @ 41 : 27 f8 f7 Mov r3, r4
19 E> 0x2bd62d9eb55e @ 44 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5
0x2bd62d9eb562 @ 48 : 26 f8 Star r3
0x2bd62d9eb564 @ 50 : 12 05 LdaConstant [5]
0x2bd62d9eb566 @ 52 : 26 f6 Star r5
0x2bd62d9eb568 @ 54 : 27 f8 f7 Mov r3, r4
24 E> 0x2bd62d9eb56b @ 57 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5
0x2bd62d9eb56f @ 61 : 26 f8 Star r3
8 E> 0x2bd62d9eb571 @ 63 : 57 fa f9 f8 04 CallProperty1 r1, r2, r3, [4]
0x2bd62d9eb576 @ 68 : 26 fb Star r0
31 S> 0x2bd62d9eb578 @ 70 : a5 Return
Constant pool (size = 6)
Handler Table (size = 0)
Thrown:
事实证明,根本没有任何区别。硬要说有区别,可能就是后者AST会大一些,但在AST解析上消耗的这点时间基本可以忽略不计。这些字节码的[4][5]来源于Constant Pool
,这需要开启V8的一个编译flag才会被显示出来;手头现在没有V8,就不扩展了。
当然,SpiderMonkey / Chakra 可能有其他的方式。
顺手jsperf了一下,基本可以认为性能相等吧,没啥参考价值:
不如说,编译器/运行时开发者早就把我们能想到的坑都踩了。动态还好说,这种静态字面量的就算有不同也早该被优化了。这种死扣细节的题大概没啥价值。
[北京的月亮和上海的月亮, 哪个更圆?]
别看这题,题目上每个字都能看懂,但是里面涉及到的知识,暗藏杀鸡 这题要往深处走,会涉及天文学、气象学、地球经纬设计等底层知识
[北京的月亮和上海的月亮, 哪个更圆?]
别看这题,题目上每个字都能看懂,但是里面涉及到的知识,暗藏杀鸡 这题要往深处走,会涉及天文学、气象学、地球经纬设计等底层知识
老哥稳
{0: 10, b: 11, b-c: 20}
[北京的月亮和上海的月亮, 哪个更圆?]
别看这题,题目上每个字都能看懂,但是里面涉及到的知识,暗藏杀鸡 这题要往深处走,会涉及天文学、气象学、地球经纬设计等底层知识
可以说很形象了!点赞
a.b 和 a['a_b_c_d_e_f'.split('_').join('').charAt(1)] 的区别吧
@lolBig 兄弟跳槽吗
黄老湿!!!我居然在这里见到你了!!!黄老湿好!!!
@CoderMageFox 你好呀,给你送个火箭,哈哈
造火箭吧?
按照上面大佬们的解答,这两种写法没有性能差异呢?
按照上面大佬们的解答,这两种写法没有性能差异呢?
基本无差异,dot可能好点,但是这点性能可以忽略
按照V8 ByteCode考虑,
a.b.c.d
的流程如下:> console.log(aaa.b.c.d) [generated bytecode for function: ] Parameter count 1 Frame size 48 0 E> 0x2bd62d9eb1ba @ 0 : a1 StackCheck 0 S> 0x2bd62d9eb1bb @ 1 : 13 00 00 LdaGlobal [0], [0] 0x2bd62d9eb1be @ 4 : 26 f9 Star r2 0x2bd62d9eb1c0 @ 6 : 12 01 LdaConstant [1] 0x2bd62d9eb1c2 @ 8 : 26 f7 Star r4 0x2bd62d9eb1c4 @ 10 : 27 f9 f8 Mov r2, r3 8 E> 0x2bd62d9eb1c7 @ 13 : 61 0c f8 02 InvokeIntrinsic [_GetProperty], r3-r4 0x2bd62d9eb1cb @ 17 : 26 fa Star r1 12 E> 0x2bd62d9eb1cd @ 19 : 13 02 02 LdaGlobal [2], [2] 0x2bd62d9eb1d0 @ 22 : 26 f8 Star r3 0x2bd62d9eb1d2 @ 24 : 12 03 LdaConstant [3] 0x2bd62d9eb1d4 @ 26 : 26 f6 Star r5 0x2bd62d9eb1d6 @ 28 : 27 f8 f7 Mov r3, r4 16 E> 0x2bd62d9eb1d9 @ 31 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb1dd @ 35 : 26 f8 Star r3 0x2bd62d9eb1df @ 37 : 12 04 LdaConstant [4] 0x2bd62d9eb1e1 @ 39 : 26 f6 Star r5 0x2bd62d9eb1e3 @ 41 : 27 f8 f7 Mov r3, r4 18 E> 0x2bd62d9eb1e6 @ 44 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb1ea @ 48 : 26 f8 Star r3 0x2bd62d9eb1ec @ 50 : 12 05 LdaConstant [5] 0x2bd62d9eb1ee @ 52 : 26 f6 Star r5 0x2bd62d9eb1f0 @ 54 : 27 f8 f7 Mov r3, r4 20 E> 0x2bd62d9eb1f3 @ 57 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb1f7 @ 61 : 26 f8 Star r3 8 E> 0x2bd62d9eb1f9 @ 63 : 57 fa f9 f8 04 CallProperty1 r1, r2, r3, [4] 0x2bd62d9eb1fe @ 68 : 26 fb Star r0 23 S> 0x2bd62d9eb200 @ 70 : a5 Return Constant pool (size = 6) Handler Table (size = 0) Thrown:
a['b']['c']['d']
的ByteCode是这个> console.log(aa['b']['c']['d']) [generated bytecode for function: ] Parameter count 1 Frame size 48 0 E> 0x2bd62d9eb532 @ 0 : a1 StackCheck 0 S> 0x2bd62d9eb533 @ 1 : 13 00 00 LdaGlobal [0], [0] 0x2bd62d9eb536 @ 4 : 26 f9 Star r2 0x2bd62d9eb538 @ 6 : 12 01 LdaConstant [1] 0x2bd62d9eb53a @ 8 : 26 f7 Star r4 0x2bd62d9eb53c @ 10 : 27 f9 f8 Mov r2, r3 8 E> 0x2bd62d9eb53f @ 13 : 61 0c f8 02 InvokeIntrinsic [_GetProperty], r3-r4 0x2bd62d9eb543 @ 17 : 26 fa Star r1 12 E> 0x2bd62d9eb545 @ 19 : 13 02 02 LdaGlobal [2], [2] 0x2bd62d9eb548 @ 22 : 26 f8 Star r3 0x2bd62d9eb54a @ 24 : 12 03 LdaConstant [3] 0x2bd62d9eb54c @ 26 : 26 f6 Star r5 0x2bd62d9eb54e @ 28 : 27 f8 f7 Mov r3, r4 14 E> 0x2bd62d9eb551 @ 31 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb555 @ 35 : 26 f8 Star r3 0x2bd62d9eb557 @ 37 : 12 04 LdaConstant [4] 0x2bd62d9eb559 @ 39 : 26 f6 Star r5 0x2bd62d9eb55b @ 41 : 27 f8 f7 Mov r3, r4 19 E> 0x2bd62d9eb55e @ 44 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb562 @ 48 : 26 f8 Star r3 0x2bd62d9eb564 @ 50 : 12 05 LdaConstant [5] 0x2bd62d9eb566 @ 52 : 26 f6 Star r5 0x2bd62d9eb568 @ 54 : 27 f8 f7 Mov r3, r4 24 E> 0x2bd62d9eb56b @ 57 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb56f @ 61 : 26 f8 Star r3 8 E> 0x2bd62d9eb571 @ 63 : 57 fa f9 f8 04 CallProperty1 r1, r2, r3, [4] 0x2bd62d9eb576 @ 68 : 26 fb Star r0 31 S> 0x2bd62d9eb578 @ 70 : a5 Return Constant pool (size = 6) Handler Table (size = 0) Thrown:
事实证明,根本没有任何区别。硬要说有区别,可能就是后者AST会大一些,但在AST解析上消耗的这点时间基本可以忽略不计。这些字节码的[4][5]来源于
Constant Pool
,这需要开启V8的一个编译flag才会被显示出来;手头现在没有V8,就不扩展了。当然,SpiderMonkey / Chakra 可能有其他的方式。
顺手jsperf了一下,基本可以认为性能相等吧,没啥参考价值:
不如说,编译器/运行时开发者早就把我们能想到的坑都踩了。动态还好说,这种静态字面量的就算有不同也早该被优化了。这种死扣细节的题大概没啥价值。
你好,请问通过什么方式可以查看js编译后的字节码,搜了下,没找到相关资料,希望给点提示,谢谢
作者咋不公布答案了?? 有什么区别?
mark
这知识它进不去脑子啊(ಥ_ಥ)
看不懂 mark
插眼,没看懂到底哪个更快
看得我眼花缭乱,简直神仙过招啊
@lolBig 厉害
按照V8 ByteCode考虑,
a.b.c.d
的流程如下:> console.log(aaa.b.c.d) [generated bytecode for function: ] Parameter count 1 Frame size 48 0 E> 0x2bd62d9eb1ba @ 0 : a1 StackCheck 0 S> 0x2bd62d9eb1bb @ 1 : 13 00 00 LdaGlobal [0], [0] 0x2bd62d9eb1be @ 4 : 26 f9 Star r2 0x2bd62d9eb1c0 @ 6 : 12 01 LdaConstant [1] 0x2bd62d9eb1c2 @ 8 : 26 f7 Star r4 0x2bd62d9eb1c4 @ 10 : 27 f9 f8 Mov r2, r3 8 E> 0x2bd62d9eb1c7 @ 13 : 61 0c f8 02 InvokeIntrinsic [_GetProperty], r3-r4 0x2bd62d9eb1cb @ 17 : 26 fa Star r1 12 E> 0x2bd62d9eb1cd @ 19 : 13 02 02 LdaGlobal [2], [2] 0x2bd62d9eb1d0 @ 22 : 26 f8 Star r3 0x2bd62d9eb1d2 @ 24 : 12 03 LdaConstant [3] 0x2bd62d9eb1d4 @ 26 : 26 f6 Star r5 0x2bd62d9eb1d6 @ 28 : 27 f8 f7 Mov r3, r4 16 E> 0x2bd62d9eb1d9 @ 31 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb1dd @ 35 : 26 f8 Star r3 0x2bd62d9eb1df @ 37 : 12 04 LdaConstant [4] 0x2bd62d9eb1e1 @ 39 : 26 f6 Star r5 0x2bd62d9eb1e3 @ 41 : 27 f8 f7 Mov r3, r4 18 E> 0x2bd62d9eb1e6 @ 44 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb1ea @ 48 : 26 f8 Star r3 0x2bd62d9eb1ec @ 50 : 12 05 LdaConstant [5] 0x2bd62d9eb1ee @ 52 : 26 f6 Star r5 0x2bd62d9eb1f0 @ 54 : 27 f8 f7 Mov r3, r4 20 E> 0x2bd62d9eb1f3 @ 57 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb1f7 @ 61 : 26 f8 Star r3 8 E> 0x2bd62d9eb1f9 @ 63 : 57 fa f9 f8 04 CallProperty1 r1, r2, r3, [4] 0x2bd62d9eb1fe @ 68 : 26 fb Star r0 23 S> 0x2bd62d9eb200 @ 70 : a5 Return Constant pool (size = 6) Handler Table (size = 0) Thrown:
a['b']['c']['d']
的ByteCode是这个> console.log(aa['b']['c']['d']) [generated bytecode for function: ] Parameter count 1 Frame size 48 0 E> 0x2bd62d9eb532 @ 0 : a1 StackCheck 0 S> 0x2bd62d9eb533 @ 1 : 13 00 00 LdaGlobal [0], [0] 0x2bd62d9eb536 @ 4 : 26 f9 Star r2 0x2bd62d9eb538 @ 6 : 12 01 LdaConstant [1] 0x2bd62d9eb53a @ 8 : 26 f7 Star r4 0x2bd62d9eb53c @ 10 : 27 f9 f8 Mov r2, r3 8 E> 0x2bd62d9eb53f @ 13 : 61 0c f8 02 InvokeIntrinsic [_GetProperty], r3-r4 0x2bd62d9eb543 @ 17 : 26 fa Star r1 12 E> 0x2bd62d9eb545 @ 19 : 13 02 02 LdaGlobal [2], [2] 0x2bd62d9eb548 @ 22 : 26 f8 Star r3 0x2bd62d9eb54a @ 24 : 12 03 LdaConstant [3] 0x2bd62d9eb54c @ 26 : 26 f6 Star r5 0x2bd62d9eb54e @ 28 : 27 f8 f7 Mov r3, r4 14 E> 0x2bd62d9eb551 @ 31 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb555 @ 35 : 26 f8 Star r3 0x2bd62d9eb557 @ 37 : 12 04 LdaConstant [4] 0x2bd62d9eb559 @ 39 : 26 f6 Star r5 0x2bd62d9eb55b @ 41 : 27 f8 f7 Mov r3, r4 19 E> 0x2bd62d9eb55e @ 44 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb562 @ 48 : 26 f8 Star r3 0x2bd62d9eb564 @ 50 : 12 05 LdaConstant [5] 0x2bd62d9eb566 @ 52 : 26 f6 Star r5 0x2bd62d9eb568 @ 54 : 27 f8 f7 Mov r3, r4 24 E> 0x2bd62d9eb56b @ 57 : 61 0c f7 02 InvokeIntrinsic [_GetProperty], r4-r5 0x2bd62d9eb56f @ 61 : 26 f8 Star r3 8 E> 0x2bd62d9eb571 @ 63 : 57 fa f9 f8 04 CallProperty1 r1, r2, r3, [4] 0x2bd62d9eb576 @ 68 : 26 fb Star r0 31 S> 0x2bd62d9eb578 @ 70 : a5 Return Constant pool (size = 6) Handler Table (size = 0) Thrown:
事实证明,根本没有任何区别。硬要说有区别,可能就是后者AST会大一些,但在AST解析上消耗的这点时间基本可以忽略不计。这些字节码的[4][5]来源于
Constant Pool
,这需要开启V8的一个编译flag才会被显示出来;手头现在没有V8,就不扩展了。 当然,SpiderMonkey / Chakra 可能有其他的方式。 顺手jsperf了一下,基本可以认为性能相等吧,没啥参考价值: 不如说,编译器/运行时开发者早就把我们能想到的坑都踩了。动态还好说,这种静态字面量的就算有不同也早该被优化了。这种死扣细节的题大概没啥价值。你好,请问通过什么方式可以查看js编译后的字节码,搜了下,没找到相关资料,希望给点提示,谢谢
node 的命令: node --print-bytecode