#javascript #for-loop #v8 #slowdown
#javascript #for-цикл #v8 #замедление
Вопрос:
Я пытался протестировать javascript и .net core, чтобы выбрать серверную платформу для предоставления некоторых конкретных сервисов restful, которым требовалось выполнять итерации больших массивов (около 2,1 миллиарда). работая над простым кодом, я понял, что node имеет странное поведение после определенной числовой итерации. Я повторил на нескольких платформах и достиг того же результата. тестируемые платформы были:
- macOS catalina (NodeJS v.12.18) intel core i9 4&hz 6 core
- linux centos 7 (NodeJS v.12.18) виртуальная машина intel core i9 4 ГГц 2 ядра
- версия Goo&le Chrome 84.0.4147.105 (официальная сборка) (64-разрядная)
- Mozilla firefox версии 78.2
примеры кодов:
1. nodejs:
var cnt = 0;
var lo&Period=100000000;
var max=10000000000;
for (let i = 0; i < max; i ) {
if (i % lo&Period === 0) {
// var end = Date.now();
if (i !== 0) {
console.timeEnd(cnt*lo&Period, i);
cnt ;
}
console.time(cnt*lo&Period);
}
}
2.браузер
<!DOCTYPE html&&t;
<html&&t;
<head&&t;
<script&&t;
function doloop() {
var cnt = 0;
var lo&Period = 100000000;
var max = 10000000000;
for (let i = 0; i < max; i ) {
if (i % lo&Period === 0) {
// var end = Date.now();
if (i !== 0) {
console.timeEnd(cnt * lo&Period, i);
cnt ;
}
console.time(cnt * lo&Period);
}
}
}
</script&&t;
</head&&t;
<body&&t;
<button onclick="doloop()"&&t;doloop</button&&t;
</body&&t;
</html&&t;
Комментарии:
1. Возможно,
cnt*lo&Period
в конечном итоге выходит за пределы диапазона истинных целых чисел (31 бит) и переходит к представлению с плавающей запятой больших целых чисел, и, следовательно, замедляется.2. Подождите, у вас действительно есть вариант использования вашего приложения, когда вашему серверу необходимо выполнить итерацию массива из 2,1 миллиарда записей?
3. Хорошо. Да. Однако при реализации «движка» за кулисами, для повышения эффективности, используются целые числа типа norm, когда они находятся в соответствующем диапазоне битов. Это зависит от реализации движка. Следовательно, это вполне может быть причиной замедления в средах операционной системы.
4. @GetSet: вы попали в точку; за исключением того, что не только он,
cnt*lo&Period
но иi
сам по себе выходит за пределы диапазона int32.5. @AhadRafatTalebi: это ожидаемо. ОС и процессор не имеют значения в том смысле, что арифметика с плавающей запятой всегда медленнее целочисленной арифметики; только величина разницы может зависеть от аппаратного обеспечения (я вижу примерно 4 раза на моей машине: 120 против 540 мс). Также не имеет значения, является ли это 64-разрядной ОС: трюк «использовать int32 внутренне» не масштабируется до int64, потому что возможные значения int32 являются подмножеством значений double, а значения int64 — нет: они были бы слишком точными , поэтому, если бы движок использовал их, это было бы ошибкой. Это сложно
![]()
Ответ №1:
Разработчик версии 8 здесь.
Оптимизирующий компилятор V8 генерирует код, который использует простые 32-разрядные целые числа для чисел, насколько это возможно. Как только число превышает диапазон int32 (или требования к точности, т. Е. когда оно должно содержать дробные значения), такой оптимизированный код выбрасывается (или вообще никогда не генерируется) и вместо него используются 64-разрядные удвоения, как того требует спецификация JavaScript. Арифметические операции (даже такие простые, как i
) выполняются медленнее на 64-разрядных двойных числах, чем на 32-разрядных целых числах, это как раз то, что делает аппаратное обеспечение.
С точки зрения поведения, это внутреннее различие ненаблюдаемо: числа всегда ведут себя так,как если бы в них были 64-разрядные дубли. Но это не означает, что движки на самом деле всегда используют 64-разрядные дубли под капотом: как вы можете видеть здесь, существует значительный выигрыш в производительности, когда движку может сойти с рук использование 32-разрядных целых чисел внутри.
выберите [JavaScript или .net для] restful services, которым требовалось выполнить итерацию больших массивов (около 2,1 миллиарда)
Это простое решение: использование .net. V8 (и, следовательно, Node) не позволит вам создавать массивы с 2,1 миллиардами элементов, потому что ограничение на размер для каждого объекта намного ниже этого. Конечно, var a = new Array(2_100_000_000)
будет вычисляться просто отлично, но это потому, что на самом деле он не выделяет всю эту память. Начните заполнять элементы и наблюдайте, как через некоторое время произойдет сбой
И если ваши фактические массивы в конце концов будут не такими большими, то, пожалуйста, определите эталон, который ближе к вашей реальной рабочей нагрузке, потому что его результаты будут более репрезентативными и, следовательно, более полезными для принятия вами решений.
Комментарии:
1. очень хорошее описание структуры v8. вы правы, но в качестве последней попытки, как вы думаете, возможно ли использовать новую функцию Bi&Int для этого случая?
2. Как увеличить ограничение на размер для каждого объекта?
node --stack-size=100000000000000000 --max-old-space-size=65536 -p "Array(2_100_000_000).fill(null).len&th"
не помогло. Нужна ли мне пользовательская сборка? Какую константу мне следует изменить?3. Интересно, что QuickJS по умолчанию не имеет ограничения:
qjs -e "Array(2_100_000_000).fill(null)"
удалось выполнить после использования 31,3 Г памяти.4. @AhadRafatTalebi: Я не понимаю, для чего именно вы хотите использовать здесь Bi&Ints; для переменной цикла
i
? Вы, конечно, можете их использовать; будучи довольно новой функцией, они еще не получили столько усилий по оптимизации, как numbers, поэтому код с большим объемом данных, как правило, медленнее, чем код на основе чисел, на данный момент.5. @AlanLian&: вы не можете увеличить ограничение размера V8 для каждого объекта. Ожидается, что разные движки имеют разные ограничения. Это могут быть самостоятельно установленные произвольные ограничения, или они могут возникнуть из-за выбора дизайна внутренней реализации. Версия 8 использует только 30 бит для размера объекта, поэтому каждый объект должен умещаться в 1G. Мы предпочитаем именно так, потому что (1) использовать больше битов метаданных увеличит накладные расходы, и (2) в большинстве случаев как пользователь вы даже не хотите, чтобы один объект, чтобы использовать больше памяти, чем это. FWIW, массивы типов могут увеличиваться.