Подтвердить что ты не робот

Почему становится участником быстрее, чем вызов hasOwnProperty?

Я пишу объект JS, который должен выполнять действительно базовое кэширование ключа-значения в парах функций:. Класс работает на клиенте и кэширует частично скомпилированные шаблоны для частичной части страницы, поэтому он может содержать от 20 до 200 элементов.

Прежде чем писать класс, я решил, что было бы неплохо увидеть, какой был самый быстрый метод поиска кэша. Возможные варианты:

1. Доступ к основным ресурсам:

if (x[k] !== undefined) {
    v = x[k];
}

2. Проверка ключа (собственный):

if (x.hasOwnProperty(k)) {
    v = x[k];
}

3. Проверка ключа (общее):

if (k in x) {
    v = x[k];
}

Я предположил, что 3 будет самым быстрым (проверяет, существует ли свойство, но не получает его или не беспокоится о том, где оно существует), а 1 будет самым медленным (фактически получает свойство, даже если оно ничего не делает).

Внесение всех этих данных в jsPerf принесло очень странные результаты. Как в Chrome (и Chromium), так и в IE, # 1 примерно в два раза быстрее. В Firefox # 3 имеет незначительное преимущество, но производительность одинакова между всеми тремя. Это не имело значения, если я работал в виртуальной машине или нет, и не сильно менялся между версиями.

У меня возникли проблемы с объяснением этих результатов. Возможно, что # 1 отмечает, что ничего не произойдет с данными, и поэтому просто проверяет ключ внутри, но почему это происходит быстрее, чем # 3? Почему # 3 не получает ту же оптимизацию?

Что вызывает эти результаты? Есть ли какая-то оптимизация JIT, которую я мог бы нанести, что искажает данные?

Что еще более важно, почему это так сильно отличается между браузерами, причем все параметры примерно равны в FF?

4b9b3361

Ответ 1

Тайна производительности x[k] в Chrome (V8) находится в этом фрагменте сборки из ic-ia32.cc. Короче говоря: V8 поддерживает глобальный кеш, который сопоставляет пару (map, name) с index определяющим местоположением свойства. Карта - это внутреннее имя, используемое в V8 для скрытых классов, другие JS-модули называют их по-разному (фигуры в SpiderMonkey и структуры в JavaScriptCore). Этот кеш заполняется только для собственных свойств объектов быстрого режима. Быстрый режим - это представление объекта, который не использует словарь для хранения свойств, но скорее похож на C-структуру со свойствами, занимающими фиксированные смещения.

Как вы видите, как только кеш заполняется в первый раз, когда выполняется ваш цикл, он всегда будет попадать на последующие повторы, что означает, что поиск свойств всегда будет обрабатываться внутри сгенерированного кода и никогда не войдет во время выполнения, потому что все свойства benchmark на самом деле существуют на объекте. Если вы просматриваете код, вы увидите следующую строку:

256   31.8%   31.8%  KeyedLoadIC: A keyed load IC from the snapshot

и сброс счетчиков собственных кодов покажет это (фактическое число зависит от количества повторений, которые вы повторяете):

| c:V8.KeyedLoadGenericLookupCache                               |    41999967 |

который показывает, что кэш действительно попадает.

Теперь V8 фактически не использует один и тот же кеш для x.hasOwnProperty(k) или k in x, на самом деле он не использует кеш и всегда в конечном итоге вызывает время выполнения, например. в профиле для случая hasOwnProperty вы увидите много методов С++:

339   17.0%   17.0%  _ZN2v88internal8JSObject28LocalLookupRealNamedPropertyEPNS0_4NameEPNS0_12LookupResultE.constprop.635
254   12.7%   12.7%  v8::internal::Runtime_HasLocalProperty(int, v8::internal::Object**, v8::internal::Isolate*)
156    7.8%    7.8%  v8::internal::JSObject::HasRealNamedProperty(v8::internal::Handle<v8::internal::JSObject>, v8::internal::Handle<v8::internal::Name>)
134    6.7%    6.7%  v8::internal::Runtime_IsJSProxy(int, v8::internal::Object**, v8::internal::Isolate*)
 71    3.6%    3.6%  int v8::internal::Search<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int)

и основная проблема здесь не в том, что это методы С++, а не рукописная сборка (например, KeyedLoadIC), но эти методы выполняют одинаковый поиск снова и снова без кэширования результата.

Теперь реализации могут быть совершенно разными между машинами, поэтому, к сожалению, я не могу дать полное объяснение того, что происходит на других машинах, но я предполагаю, что любой движок, демонстрирующий более высокую производительность x[k], использует аналогичный кеш (или представляет собой x в качестве словаря, что также позволяет быстро исследовать сгенерированный код), и любой движок, который показывает эквивалентную производительность между случаями, либо не использует никакого кэширования, либо использует тот же кеш для всех трех операций (что будет иметь смысл).

Если V8 проверил один и тот же кеш перед тем, как переходить к времени выполнения для hasOwnProperty и in, то в вашем тесте вы видели бы эквивалентную производительность между случаями.