Почему использование `let` внутри цикла` for` настолько медленное в Chrome?
ОСНОВНОЕ ОБНОВЛЕНИЕ.
Мысль о том, что еще не выпущена в Chrome, выпущена новая Ignition + Turbofan engine для Chrome Canary 59, решила проблему. Тест показывает идентичные времена для let
и var
объявленных переменных цикла.
Оригинальный (теперь немой) вопрос.
При использовании let
в цикле for
в Chrome он работает очень медленно, по сравнению с перемещением переменной непосредственно за пределы области цикла.
for(let i = 0; i < 1e6; i ++);
занимает в два раза длиннее
{ let i; for(i = 0; i < 1e6; i ++);}
Что происходит?
Фрагмент демонстрирует разницу и влияет только на Chrome, и так было до тех пор, пока я помню Chrome, поддерживающий let
.
var times = [0,0]; // hold total times
var count = 0; // number of tests
function test(){
var start = performance.now();
for(let i = 0; i < 1e6; i += 1){};
times[0] += performance.now()-start;
setTimeout(test1,10)
}
function test1(){
// this function is twice as quick as test on chrome
var start = performance.now();
{let i ; for(i = 0; i < 1e6; i += 1);}
times[1] += performance.now()-start;
setTimeout(test2,10)
}
// display results
function test2(){
var tot =times[0]+times[1];
time.textContent = tot.toFixed(3) + "ms";
time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3) + "ms";
time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
if(count++ < 1000){;
setTimeout(test,10);
}
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
test2()
Ответы
Ответ 1
ОСНОВНОЕ ОБНОВЛЕНИЕ.
Мысль о том, что еще не выпущена в Chrome, выпущена новая Ignition + Turbofan engine для Chrome Canary 60.0.3087, решила проблему. Тест показывает идентичные времена для let
и var
объявленных переменных цикла.
Боковое примечание. В моем тестовом коде используется Function.toString()
и не удалось на Canary, потому что он возвращает "function() {"
not "function () {"
в качестве прошлых версий (легко исправить с помощью regexp), но потенциальная проблема для тех, кто используйте Function.toSting()
Обновление Благодаря пользователю Dan. M, которые предоставляют ссылку https://bugs.chromium.org/p/v8/issues/detail?id=4762 (и голова вверх), у которой больше проблем.
Предыдущий ответ
Оптимизатор отказался.
Этот вопрос озадачил меня в течение некоторого времени, и два ответа являются очевидными ответами, но это не имело никакого смысла, поскольку разница во времени была слишком велика, чтобы создать новую область действия и контекст выполнения.
Чтобы доказать это, я нашел ответ.
Короткий ответ
А для цикла с выражением let в объявлении не поддерживается оптимизатором.
Chrome Version 55.0.2883.35 beta, Windows 10.
Изображение стоит тысяча слов, и должно быть, первое место, чтобы посмотреть.
Соответствующие функции для вышеуказанного профиля
var time = [0,0]; // hold total times
function letInside(){
var start = performance.now();
for(let i = 0; i < 1e5; i += 1); // <- if you try this at home don't forget the ;
time[0] += performance.now()-start;
setTimeout(letOutside,10);
}
function letOutside(){ // this function is twice as quick as test on chrome
var start = performance.now();
{let i; for(i = 0; i < 1e5; i += 1)}
time[1] += performance.now()-start;
setTimeout(displayResults,10);
}
Поскольку Chrome является основным игроком, а заблокированные переменные области для счетчиков циклов повсюду, те, кому нужен код исполнения, и считают, что переменные с узкоспециализированными важными важными function{}(for(let i; i<2;i++}{...})//?WHY?
должны рассматривать на данный момент альтернативный синтаксис и объявлять счетчик циклов вне цикла.
Я хотел бы сказать, что разница во времени тривиальна, но в свете того, что весь код внутри функции не оптимизирован с использованием for(let i...
, следует использовать с осторожностью.
Ответ 2
Поскольку для каждой итерации цикла создается новый i
, так что замыкания, созданные в цикле, закрываются над i
для этой итерации. Это объясняется спецификацией в алгоритме оценки тела цикла for
, в котором описывается создание новой переменной для каждой итерации цикла.
Пример:
for (let i = 0; i < 5; ++i) {
setTimeout(function() {
console.log("i = " + i);
}, i * 50);
}
// vs.
setTimeout(function() {
let j;
for (j = 0; j < 5; ++j) {
setTimeout(function() {
console.log("j = " + j);
}, j * 50);
}
}, 400);
Ответ 3
@T.J.Crowder уже ответил на заголовок вопроса, но я отвечу на ваши сомнения.
Когда я впервые столкнулся с этим, я подумал, что это из-за только что созданного экземпляра i, но следующее показывает, что это не так.
Собственно, это из-за недавно созданной области для переменной i
. Что еще не оптимизировано, поскольку оно более сложное, чем простая область блока.
Смотрите второй фрагмент кода, поскольку я исключил возможность дополнительной оптимизации объявления объявления с помощью ini со случайным и затем добавлением к неопределенному значению k.
Ваше дополнительное объявление let j
в
{let i; for (i = 0; i < 1e3; i ++) {let j = Math.random(); j += i; k += j;}}
// I'll ignore the `p` variable you had in your code
был оптимизирован. Это довольно тривиальная задача для оптимизатора, она может вообще избежать этой переменной, упростив тело цикла до
k += Math.random() + i;
Область действительно не нужна, если вы не создаете там закрытие или не используете eval
или подобные мерзости.
Если ввести такое замыкание (как мертвый код, надеюсь, оптимизатор этого не осознает) и ямы
{let i; for (i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}}
против
for (let i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}
то мы увидим, что они работают примерно с той же скоростью.
var times = [0,0]; // hold total times
var count = 0; // number of tests
var soak = 0; // to stop optimizations
function test1(){
var k = time[1];
var start = performance.now();
{let i; for(i=0; i < 1e3; i++){ let j=Math.random(); k += j+i; function f() { j; }}}
times[0] += performance.now()-start;
soak += k;
setTimeout(test2,10)
}
function test2(){
var k = time[1];
var start = performance.now();
for(let i=0; i < 1e3; i++){ let j=Math.random(); k += j+i; function f() { j; }}
times[1] += performance.now()-start;
soak += k;
setTimeout(display,10)
}
// display results
function display(){
var tot =times[0]+times[1];
time.textContent = tot.toFixed(3) + "ms";
time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3) + "ms";
time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
if(count++ < 1000){
setTimeout(test1,10);
}
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
display();