Почему использование `let` внутри цикла`for` так медленно в Chrome?
ОСНОВНОЕ ОБНОВЛЕНИЕ.
Мысль, пока еще не о выпуске Chrome, новые двигатели Ignition+Turbofan для Chrome Canary 59 решили проблему. Тестовое шоу идентичное время для let
а также var
объявленные переменные цикла.
Оригинальный (теперь немой) вопрос.
Когда используешь let
в for
цикл в Chrome работает очень медленно, по сравнению с перемещением переменной за пределы области видимости цикла.
for(let i = 0; i < 1e6; i ++);
занимает вдвое дольше, чем
{ let i; for(i = 0; i < 1e6; i ++);}
Что здесь происходит?
Snippet демонстрирует разницу и влияет только на 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()
Когда я впервые столкнулся с этим, я думал, что это из-за недавно созданного экземпляра i, но следующее показывает, что это не так.
См. Фрагмент кода, поскольку я исключил любую возможность оптимизации дополнительного объявления let с помощью ini со случайным образом и последующего добавления к неопределенному значению k.
Я также добавил второй счетчик цикла p
var times = [0,0]; // hold total times
var count = 0; // number of tests
var soak = 0; // to stop optimizations
function test(){
var j;
var k = time[1];
var start = performance.now();
for(let p =0, i = 0; i+p < 1e3; p++,i ++){j=Math.random(); j += i; k += j;};
times[0] += performance.now()-start;
soak += k;
setTimeout(test1,10)
}
function test1(){
// this function is twice as quick as test on chrome
var k = time[1];
var start = performance.now();
{let p,i ; for(p = 0,i = 0; i+p < 1e3; p++, i ++){let j = Math.random(); j += i; k += j}}
times[1] += performance.now()-start;
soak += k;
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()
3 ответа
ОСНОВНОЕ ОБНОВЛЕНИЕ.
Мысль о выпуске Chrome Canary 60.0.3087, которая еще не была выпущена на главном выпуске Chrome, решила эту проблему. Тестовое шоу идентичное время для let
а такжеvar
объявленные переменные цикла.
Примечание. Мой код тестирования используетFunction.toString()
и не удалось на Канарских, потому что он возвращается"function() {"
не "function () {"
как прошлые версии (легко исправить с помощью регулярных выражений), но потенциальная проблема для тех, кто использует Function.toSting()
Обновление Спасибо пользователю Dan M.. Dan M., которые предоставляют ссылку https://bugs.chromium.org/p/v8/issues/detail?id=4762 (и заголовки), в которой есть больше по этому вопросу.
Предыдущий ответ
Оптимизатор отказался.
Этот вопрос меня озадачил некоторое время, и два ответа - очевидные ответы, но он не имел смысла, так как разница во времени была слишком велика, чтобы создать новую переменную в области видимости и контекст выполнения.
В попытке доказать это я нашел ответ.
Короткий ответ
Цикл for с оператором let в объявлении не поддерживается оптимизатором.
Chrome Версия 55.0.2883.35 бета, 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...
следует использовать с осторожностью.
Обновление: июнь 2018 года. Chrome теперь оптимизирует это гораздо лучше, чем когда первый раз были опубликованы этот вопрос и ответ; больше нет заметного штрафа за использование let
в for
если вы не создаете функции в цикле (и если вы это делаете, выгоды стоят затрат).
Потому что новый 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);
Это больше работы. Если вам не нужен новый i
для каждого цикла используйте let
вне петли. Смотрите обновление выше, не нужно избегать его, кроме крайних случаев.
Мы можем ожидать, что теперь, когда все, кроме модулей, было реализовано, V8, вероятно, улучшит оптимизацию нового, но не удивительно, что функциональность должна изначально иметь приоритет над оптимизацией.
Здорово, что другие движки уже провели оптимизацию, но команда V8, видимо, просто еще не достигла этого. Смотрите обновление выше.
@TJCrowder уже ответил на заглавный вопрос, но я отвечу на ваши сомнения.
Когда я впервые столкнулся с этим, я думал, что это из-за недавно созданного экземпляра i, но следующее показывает, что это не так.
На самом деле, это из-за недавно созданной области для i
переменная. Который (пока) не оптимизирован, так как он более сложен, чем простая область видимости блока.
Посмотрите второй фрагмент кода, так как я исключил любую возможность оптимизации дополнительного объявления let с помощью 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();