Як функція util.toFastProperties Bluebird робить властивості об'єкта "швидкими"?


165

У util.jsфайлі Bluebird він має таку функцію:

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

Чомусь після функції повернення є твердження, яке я не впевнений, чому це там.

Крім того, здається, що це навмисно, оскільки автор замовк JSHint попередження про це:

Недоступний 'eval' після 'повернення'. (W027)

Що саме ця функція виконує? Є чи util.toFastPropertiesдійсно зробити властивість об'єкта «швидше»?

Я шукав у сховищі GitHub Bluebird, щоб отримати коментарі до вихідного коду чи пояснення у їхньому списку питань, але не знайшов жодного.

Відповіді:


314

Оновлення 2017 року: По-перше, для читачів, які приходять сьогодні - ось версія, яка працює з 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
}

Запроваджує одну-дві невеликі оптимізації - все наведене нижче все ще діє.

Давайте спочатку обговоримо, що це робить і чому це швидше, а потім, чому це працює.

Що це робить

Двигун V8 використовує два представлення об'єкта:

  • Режим словника - в якому об'єкт зберігається як ключові карти як хеш-карта .
  • Швидкий режим - в якому об'єкти зберігаються як структури , в яких немає обчислень, що беруть участь у доступі до власності.

Ось проста демонстрація, яка демонструє різницю швидкості. Тут ми використовуємо deleteоператор, щоб змусити об'єкти у повільному словниковому режимі.

Двигун намагається використовувати швидкий режим, коли це можливо, і зазвичай, коли виконується багато ресурсів, але іноді він переходить у словниковий режим. Перебуваючи в словниковому режимі, існує велика ефективність покарання, тому загалом бажано переводити об'єкти у швидкий режим.

Цей злом призначений для примушування об'єкта до швидкого режиму зі словника.

Чому це швидше

У прототипах JavaScript, як правило, зберігаються функції, які поділяються між багатьма екземплярами і рідко змінюються дуже багато. З цієї причини дуже бажано мати їх у швидкому режимі, щоб уникнути зайвого штрафу щоразу, коли функція викликається.

Для цього - v8 із задоволенням поставить об'єкти, які є .prototype властивістю функцій у швидкому режимі, оскільки їх буде спільним для кожного об'єкта, створеного за допомогою виклику цієї функції як конструктора. Це взагалі розумна і бажана оптимізація.

Як це працює

Давайте спочатку переглянемо код і розберемо, що робить кожен рядок:

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.
}

Нам не потрібно знаходити код, щоб стверджувати, що v8 робить цю оптимізацію, ми можемо замість цього прочитати тести v8 unit :

// 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));

Читання та запуск цього тесту показує нам, що ця оптимізація справді працює в v8. Однак - було б непогано подивитися, як.

Якщо ми перевіримо, objects.ccми можемо знайти таку функцію (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);
  }
}

Тепер JSObject::MigrateSlowToFastпросто явно бере словник і перетворює його у швидкий V8-об’єкт. Це варто прочитати і цікаве уявлення про внутрішні внутрішні об'єкти v8 - але це не тема тут. Я все ще гаряче рекомендую прочитати його тут, оскільки це хороший спосіб дізнатися про v8 об’єкти.

Якщо ми перевіряємо SetPrototypeв objects.cc, ми можемо бачити , що він викликається в рядку 12231:

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

Що, в свою чергу, називається тим FuntionSetPrototype, з чим ми отримуємо.prototype = .

Робити __proto__ =або .setPrototypeOfб також працювало, але це функції ES6, і Bluebird працює у всіх браузерах з Netscape 7, тому тут не може бути спрощення коду. Наприклад, якщо ми перевіримо, .setPrototypeOfми можемо побачити:

// 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;
}

Що безпосередньо ввімкнено Object:

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

Отже - ми пройшли шлях від коду, який Петка написав, до голого металу. Це було приємно.

Відмова від відповідальності:

Пам'ятайте, що це вся деталь реалізації. Такі люди, як Петка, - оптимізація виродків. Завжди пам’ятайте, що передчасна оптимізація - корінь всього зла в 97% часу. Bluebird робить щось дуже базове дуже часто, тому багато отримує від цих хакерських можливостей - бути таким же швидким, як зворотній зв'язок не просто. Вам рідко доводиться робити щось подібне в коді, який не працює в бібліотеці.


37
Це найцікавіший пост, який я прочитав за деякий час. Великої поваги та вдячності вам!
m59

2
@timoxley Я написав наступне про eval(у коментарях до коду при поясненні коду ОП розміщено): "не дозволяйте оптимізувати функцію шляхом усунення мертвого коду чи подальших оптимізацій. Цей код ніколи не досягнуто, але навіть недоступний код призводить до того, що v8 не оптимізується. функції. " . Ось відповідне читання . Ви хотіли б, щоб я детальніше розробив цю тему?
Бенджамін Груенбаум

3
@dherman a 1;не спричинив би "деоптимізацію", a debugger;, мабуть, працював би однаково добре. Приємно те, що коли evalпередається щось, що не є рядком, воно нічого не робить з цим, так що це досить нешкідливо - на кшталтif(false){ debugger; }
Бенджамін Груенбаум

6
Btw цей код було оновлено через зміну в останній версії 8, тепер вам також потрібно інстанціювати конструктор. Так стало ліньше; d
Ісаїлія

4
@BenjaminGruenbaum Чи можете ви детальніше пояснити, чому цю функцію НЕ слід оптимізувати? У мінімізованому коді eval все одно немає. Чому eval корисний тут у не зміненому коді?
Boopathi Rajaa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.