V8 разработчик здесь. Ваша интуиция верна.
TL; DR: inlinedAccess
каждый раз создает новый объект. constantAccess
более эффективен, поскольку позволяет избежать воссоздания объекта при каждом вызове. Для еще лучшей производительности используйте Map
.
Тот факт, что «быстрый тест» выдает одинаковые значения времени для обеих функций, показывает, насколько легко микробенчмарки могут вводить в заблуждение; -)
- Создание объектов, подобных объекту в вашем примере, выполняется довольно быстро, поэтому влияние трудно измерить. Вы можете усилить влияние повторного создания объекта, сделав его более дорогим, например, заменив одно свойство на
b: new Array(100),
. - Преобразование числа в строку и последующее объединение строк в
'result: ' + ...
вносят значительный вклад вобщее время;Вы можете пропустить это, чтобы получить более четкий сигнал. - Для небольшого теста вы должны быть осторожны, чтобы компилятор не оптимизировал все. Назначение результата глобальной переменной делает свое дело.
- Это также имеет огромное значение, независимо от того, ищите ли вы всегда одно и то же свойство или разные свойства. Поиск объектов в JavaScript не совсем простая (== быстрая) операция;V8 имеет очень быструю стратегию оптимизации / кэширования, когда у данного сайта всегда одно и то же свойство (и одна и та же форма объекта), но для изменяющихся свойств (или формы объекта) он должен выполнять более дорогой поиск.
Map
поиск различных ключей выполняется быстрее, чем поиск свойств объекта. Использование объектов в качестве карт - это 2010 год, современный JavaScript имеет правильные значения Map
s, поэтому используйте их! : -) Array
поиск элементов еще быстрее, но, конечно, вы можете использовать их только тогда, когда ваши ключи целые. - Когда число возможных ключей, которые ищут, малозаявления переключателя трудно превзойти. Однако они плохо масштабируются для большого количества ключей.
Давайте вложим все эти мысли в код:
function inlinedAccess(key) {
const inlinedLookupTable = {
a: 1,
b: new Array(100),
c: 3,
d: 4,
}
return inlinedLookupTable[key];
}
const CONSTANT_TABLE = {
a: 1,
b: new Array(100),
c: 3,
d: 4,
}
function constantAccess(key) {
return CONSTANT_TABLE[key];
}
const LOOKUP_MAP = new Map([
["a", 1],
["b", new Array(100)],
["c", 3],
["d", 4]
]);
function mapAccess(key) {
return LOOKUP_MAP.get(key);
}
const ARRAY_TABLE = ["a", "b", "c", "d"]
function integerAccess(key) {
return ARRAY_TABLE[key];
}
function switchAccess(key) {
switch (key) {
case "a": return 1;
case "b": return new Array(100);
case "c": return 3;
case "d": return 4;
}
}
const kCount = 10000000;
let result = null;
let t1 = Date.now();
for (let i = 0; i < kCount; i++) {
result = inlinedAccess("a");
result = inlinedAccess("d");
}
let t2 = Date.now();
for (let i = 0; i < kCount; i++) {
result = constantAccess("a");
result = constantAccess("d");
}
let t3 = Date.now();
for (let i = 0; i < kCount; i++) {
result = mapAccess("a");
result = mapAccess("d");
}
let t4 = Date.now();
for (let i = 0; i < kCount; i++) {
result = integerAccess(0);
result = integerAccess(3);
}
let t5 = Date.now();
for (let i = 0; i < kCount; i++) {
result = switchAccess("a");
result = switchAccess("d");
}
let t6 = Date.now();
console.log("inlinedAccess: " + (t2 - t1));
console.log("constantAccess: " + (t3 - t2));
console.log("mapAccess: " + (t4 - t3));
console.log("integerAccess: " + (t5 - t4));
console.log("switchAccess: " + (t6 - t5));
Я получаю следующие результаты:
inlinedAccess: 1613
constantAccess: 194
mapAccess: 95
integerAccess: 15
switchAccess: 9
Все, что сказал: эти числа "миллисекунды для 10M поисков". В реальных приложениях различия, вероятно, слишком малы, чтобы иметь значение, поэтому вы можете написать любой код, который будет наиболее читаемым / поддерживаемым / и т.д. Например, если вы выполняете только 100К-поиски, результаты будут такими:
inlinedAccess: 31
constantAccess: 6
mapAccess: 6
integerAccess: 5
switchAccess: 4
Кстати, распространенным вариантом этой ситуации является создание / вызов функций. Это:
function singleton_callback(...) { ... }
function efficient(...) {
return singleton_callback(...);
}
гораздо эффективнее, чем это:
function wasteful(...) {
function new_callback_every_time(...) { ... }
return new_callback_every_time(...);
}
И, аналогично, это:
function singleton_method(args) { ... }
function EfficientObjectConstructor(param) {
this.___ = param;
this.method = singleton_method;
}
гораздо эффективнее, чем это:
function WastefulObjectConstructor(param) {
this.___ = param;
this.method = function(...) {
// Allocates a new function every time.
};
}
(Конечно, обычный способ сделать это - Constructor.prototype.method = function(...) {...}
, что также позволяет избежать повторного создания функций. В настоящее время вы можете просто использовать class
es.)