Почему использование `let` внутри цикла` for` так медленно в Chrome?

21

ОСНОВНОЕ ОБНОВЛЕНИЕ.

Мысль о том, что еще не выпущена в 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()

Когда я впервые столкнулся с этим, я подумал, что это из-за только что созданного экземпляра 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()
  • 4
    Первый должен создавать новый уровень области видимости на каждой итерации, если он не оптимизирован, чтобы избежать этого.
  • 1
    Дополнительная информация: stackoverflow.com/questions/37792934/…
Показать ещё 6 комментариев
Теги:
ecmascript-6
performance
google-chrome
let

3 ответа

3
Лучший ответ

ОСНОВНОЕ ОБНОВЛЕНИЕ.

Мысль о том, что еще не выпущена в 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 в объявлении не поддерживается оптимизатором.

Изображение 127822 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..., следует использовать с осторожностью.


  • 0
    Есть ли какая-либо связанная ошибка / проблема для V8, которую вы можете отследить? Жаль, что в одном из самых популярных движков js по-прежнему скрывается главный убийца перфектов, тем более что let становится все более и более распространенным.
  • 1
    Нашел его: bugs.chromium.org/p/v8/issues/detail?id=4762
Показать ещё 1 комментарий
20

Поскольку для каждой итерации цикла создается новый 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, видимо, еще не дошла.

  • 0
    Я обновил свой ответ другим фрагментом. Добавление второго объявления в более быстрой функции. Если для создания переменной потребовалось время, это должно было замедлиться. И если ваш ответ правильный, где значение i хранится в конце каждой итерации, если оно воссоздается?
  • 1
    @Blindman67: Blindman67: Там нет "если" об этом, см спецификации . Ваш новый фрагмент просто использует другую часть оптимизатора (может быть, устранение мертвого кода, возможно, они лучше справились с оптимизацией объявлений в теле, чем в голове).
Показать ещё 2 комментария
2

@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();

Ещё вопросы

Сообщество Overcoder
Наверх
Меню