Làm thế nào để chức năng produc.toFastProperIES của Bluebird tạo ra các thuộc tính của đối tượng.


165

Trong util.jstệp của Bluebird , nó có chức năng sau:

function toFastProperties(obj) {
    /*jshint -W027*/
    function f() {}
    f.prototype = obj;
    ASSERT("%HasFastProperties", true, obj);
    return f;
    eval(obj);
}

Vì một số lý do, có một tuyên bố sau hàm trả về, mà tôi không chắc tại sao nó lại ở đó.

Đồng thời, có vẻ như đó là cố ý, vì tác giả đã im lặng cảnh báo về JSHint về điều này:

Không thể truy cập 'eval' sau khi 'trở về'. (W027)

Chính xác thì chức năng này làm gì? Có util.toFastPropertiesthực sự làm cho các thuộc tính của đối tượng "nhanh hơn"?

Tôi đã tìm kiếm thông qua kho GitHub của Bluebird để tìm bất kỳ nhận xét nào trong mã nguồn hoặc giải thích trong danh sách các vấn đề của họ, nhưng tôi không thể tìm thấy bất kỳ nhận xét nào.

Câu trả lời:


314

Cập nhật 2017: Đầu tiên, dành cho độc giả đến hôm nay - đây là phiên bản hoạt động với Node 7 (4+):

function enforceFastProperties(o) {
    function Sub() {}
    Sub.prototype = o;
    var receiver = new Sub(); // create an instance
    function ic() { return typeof receiver.foo; } // perform access
    ic(); 
    ic();
    return o;
    eval("o" + o); // ensure no dead code elimination
}

Sans một hoặc hai tối ưu hóa nhỏ - tất cả những điều dưới đây vẫn còn hiệu lực.

Trước tiên chúng ta hãy thảo luận về những gì nó làm và tại sao nó nhanh hơn và sau đó tại sao nó hoạt động.

Những gì nó làm

Động cơ V8 sử dụng hai biểu diễn đối tượng:

  • Chế độ từ điển - trong đó đối tượng được lưu trữ dưới dạng bản đồ khóa - giá trị dưới dạng bản đồ băm .
  • Chế độ nhanh - trong đó các đối tượng được lưu trữ như các cấu trúc , trong đó không có tính toán liên quan đến truy cập thuộc tính.

Dưới đây là một bản demo đơn giản thể hiện sự khác biệt về tốc độ. Ở đây chúng tôi sử dụng deletecâu lệnh để buộc các đối tượng vào chế độ từ điển chậm.

Công cụ cố gắng sử dụng chế độ nhanh bất cứ khi nào có thể và nói chung bất cứ khi nào có nhiều quyền truy cập thuộc tính được thực hiện - tuy nhiên đôi khi nó bị ném vào chế độ từ điển. Ở trong chế độ từ điển có một hình phạt hiệu suất lớn vì vậy nhìn chung nên đặt các đối tượng ở chế độ nhanh.

Bản hack này nhằm buộc đối tượng vào chế độ nhanh từ chế độ từ điển.

Tại sao nó nhanh hơn

Trong các nguyên mẫu JavaScript thường lưu trữ các hàm được chia sẻ giữa nhiều trường hợp và hiếm khi thay đổi nhiều. Vì lý do này, rất mong muốn có chúng ở chế độ nhanh để tránh bị phạt thêm mỗi khi một chức năng được gọi.

Đối với điều này - v8 sẽ sẵn sàng đặt các đối tượng là thuộc .prototypetính của các hàm ở chế độ nhanh vì chúng sẽ được chia sẻ bởi mọi đối tượng được tạo bằng cách gọi hàm đó là hàm tạo. Đây thường là một tối ưu hóa thông minh và mong muốn.

Làm thế nào nó hoạt động

Trước tiên chúng ta hãy đi qua mã và tìm hiểu mỗi dòng làm gì:

function toFastProperties(obj) {
    /*jshint -W027*/ // suppress the "unreachable code" error
    function f() {} // declare a new function
    f.prototype = obj; // assign obj as its prototype to trigger the optimization
    // assert the optimization passes to prevent the code from breaking in the
    // future in case this optimization breaks:
    ASSERT("%HasFastProperties", true, obj); // requires the "native syntax" flag
    return f; // return it
    eval(obj); // prevent the function from being optimized through dead code 
               // elimination or further optimizations. This code is never  
               // reached but even using eval in unreachable code causes v8
               // to not optimize functions.
}

Chúng tôi không phải tự tìm mã để khẳng định rằng v8 thực hiện tối ưu hóa này, thay vào đó chúng tôi có thể đọc các bài kiểm tra đơn vị v8 :

// Adding this many properties makes it slow.
assertFalse(%HasFastProperties(proto));
DoProtoMagic(proto, set__proto__);
// Making it a prototype makes it fast again.
assertTrue(%HasFastProperties(proto));

Đọc và chạy thử nghiệm này cho chúng ta thấy rằng tối ưu hóa này thực sự hoạt động trong v8. Tuy nhiên - thật tuyệt khi thấy cách làm.

Nếu chúng ta kiểm tra, objects.ccchúng ta có thể tìm thấy chức năng sau (L9925):

void JSObject::OptimizeAsPrototype(Handle<JSObject> object) {
  if (object->IsGlobalObject()) return;

  // Make sure prototypes are fast objects and their maps have the bit set
  // so they remain fast.
  if (!object->HasFastProperties()) {
    MigrateSlowToFast(object, 0);
  }
}

Bây giờ, JSObject::MigrateSlowToFastchỉ cần lấy từ điển một cách rõ ràng và chuyển đổi nó thành một đối tượng V8 nhanh. Đây là một bài đáng đọc và một cái nhìn sâu sắc thú vị về nội bộ đối tượng v8 - nhưng nó không phải là chủ đề ở đây. Tôi vẫn nhiệt liệt khuyên bạn nên đọc nó ở đâyđây là một cách tốt để tìm hiểu về các đối tượng v8.

Nếu chúng tôi kiểm tra SetPrototypetrong objects.cc, chúng ta có thể thấy rằng nó được gọi là phù hợp 12.231:

if (value->IsJSObject()) {
    JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}

Mà lần lượt được gọi bởi FuntionSetPrototypeđó là những gì chúng ta nhận được với .prototype =.

Làm __proto__ =hoặc .setPrototypeOfsẽ làm việc nhưng đây là các chức năng ES6 và Bluebird chạy trên tất cả các trình duyệt kể từ Netscape 7, vì vậy đó không phải là câu hỏi để đơn giản hóa mã ở đây. Ví dụ: nếu chúng tôi kiểm tra, .setPrototypeOfchúng tôi có thể thấy:

// ES6 section 19.1.2.19.
function ObjectSetPrototypeOf(obj, proto) {
  CHECK_OBJECT_COERCIBLE(obj, "Object.setPrototypeOf");

  if (proto !== null && !IS_SPEC_OBJECT(proto)) {
    throw MakeTypeError("proto_object_or_null", [proto]);
  }

  if (IS_SPEC_OBJECT(obj)) {
    %SetPrototype(obj, proto); // MAKE IT FAST
  }

  return obj;
}

Mà trực tiếp là trên Object:

InstallFunctions($Object, DONT_ENUM, $Array(
...
"setPrototypeOf", ObjectSetPrototypeOf,
...
));

Vì vậy - chúng tôi đã đi theo con đường từ mã Petka đã viết đến kim loại trần. Điều này thật tuyệt

Tuyên bố từ chối trách nhiệm:

Hãy nhớ rằng đây là tất cả các chi tiết thực hiện. Những người như Petka là tối ưu hóa quái vật. Luôn nhớ rằng tối ưu hóa sớm là gốc rễ của mọi tội lỗi 97% thời gian. Bluebird thực hiện một cái gì đó rất cơ bản rất thường xuyên để nó thu được rất nhiều từ các bản hack hiệu suất này - nhanh như gọi lại không dễ dàng. Bạn hiếm khi phải làm một cái gì đó như thế này trong mã không cung cấp năng lượng cho thư viện.


37
Đây là bài viết thú vị nhất mà tôi đã đọc trong một thời gian. Rất nhiều sự tôn trọng và đánh giá cao cho bạn!
m59

2
@timoxley Tôi đã viết như sau về eval(trong phần nhận xét mã khi giải thích mã OP đã đăng): "ngăn chức năng được tối ưu hóa thông qua loại bỏ mã chết hoặc tối ưu hóa thêm. Mã này không bao giờ đạt được nhưng ngay cả mã không thể truy cập được khiến v8 không tối ưu hóa chức năng." . Đây là một bài đọc liên quan . Bạn có muốn tôi giải thích thêm về chủ đề này?
Benjamin Gruenbaum

3
@dherman a 1;sẽ không gây ra "deoptimization", một cái debugger;có lẽ sẽ hoạt động tốt như nhau. Điều tuyệt vời là khi evalđược thông qua một thứ gì đó không phải là một chuỗi, nó sẽ không làm gì với nó nên nó khá vô hại - giống nhưif(false){ debugger; }
Benjamin Gruenbaum

6
Btw mã này đã được cập nhật do sự thay đổi trong v8 gần đây, bây giờ bạn cũng cần khởi tạo hàm tạo. Vì vậy, nó trở nên lười hơn; d
Esailija

4
@BenjaminGruenbaum Bạn có thể giải thích lý do tại sao chức năng này KHÔNG được tối ưu hóa? Trong mã rút gọn, eval dù sao cũng không có. Tại sao eval hữu ích ở đây trong mã không được rút gọn?
Boopathi Rajaa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.