Подтвердить что ты не робот

Почему asm.js ухудшает производительность?

Чтобы посмотреть, как это работает, я написал очень короткий модуль asm.js вручную, который имитирует двумерное волновое уравнение с использованием 32-битной целочисленной математики и типизированных массивов (Int32Array). У меня есть три версии, все как можно более похоже:

  • Обычный (т.е. разборчивый, хотя и C-стиль) JavaScript
  • То же, что и 1, добавлены аннотации asm.js, чтобы он прошел проверку, согласно Firefox и другим инструментам.
  • То же, что и 2, кроме как без использования asm; директива в верхней части

Я оставил демо на http://jsfiddle.net/jtiscione/xj0x0qk3/, который позволяет переключаться между модулями, чтобы увидеть эффекты использования каждого из них. Все три работают, но с разной скоростью. Это горячая точка (с аннотациями asm.js):

for (i = 0; ~~i < ~~h; i = (1 + i)|0) {
    for (j = 0; ~~j < ~~w; j = (1 + j)|0) {
        if (~~i == 0) {
            index = (1 + index) | 0;
            continue;
        }
        if (~~(i + 1) == ~~h) {
            index = (1 + index) | 0;
            continue;
        }
        if (~~j == 0) {
            index = (1 + index) | 0;
            continue;
        }
        if (~~(j + 1) == ~~w) {
            index = (1 + index) | 0;
            continue;
        }
        uCen = signedHeap  [((u0_offset + index) << 2) >> 2] | 0;
        uNorth = signedHeap[((u0_offset + index - w) << 2) >> 2] | 0;
        uSouth = signedHeap[((u0_offset + index + w) << 2) >> 2] | 0;
        uWest = signedHeap [((u0_offset + index - 1) << 2) >> 2] | 0;
        uEast = signedHeap [((u0_offset + index + 1) << 2) >> 2] | 0;
        uxx = (((uWest + uEast) >> 1) - uCen) | 0;
        uyy = (((uNorth + uSouth) >> 1) - uCen) | 0;
        vel = signedHeap[((vel_offset + index) << 2) >> 2] | 0;
        vel = vel + (uxx >> 1) | 0;
        vel = applyCap(vel) | 0;
        vel = vel + (uyy >> 1) | 0;
        vel = applyCap(vel) | 0;
        force = signedHeap[((force_offset + index) << 2) >> 2] | 0;
        signedHeap[((u1_offset + index) << 2) >> 2] = applyCap(((applyCap((uCen + vel) | 0) | 0) + force) | 0) | 0;
        force = force - (force >> forceDampingBitShift) | 0;
        signedHeap[((force_offset + index) << 2) >> 2] = force;
        vel = vel - (vel >> velocityDampingBitShift) | 0;
        signedHeap[((vel_offset + index) << 2) >> 2] = vel;
        index = (index + 1)|0;
    }
}

Версия "обычного JavaScript" структурирована, как указано выше, но без побитовых операторов, которые требуют asm.js(например, "x | 0", "~~ x", "arr [(x < 2) → 2]" и т.д.)

Это результаты для всех трех модулей на моей машине, используя Firefox (версия для разработчиков версии 41) и Chrome (версия 44), в миллисекундах за итерацию:

  • FIREFOX (версия 41): 20 мс, 35 мс, 60 мс.
  • CHROME (версия 44): 25 мс, 150 мс, 75 мс.

Итак, обычный JavaScript выигрывает в обоих браузерах. Наличие аннотаций, требуемых asm.js, ухудшает производительность в 3 раза в обоих случаях. Кроме того, наличие "использования asm"; директива имеет очевидный эффект - она ​​немного помогает Firefox и приносит Chrome на колени!

Кажется странным, что просто добавление побитовых операторов должно вносить трехкратное снижение производительности, которое невозможно преодолеть, говоря браузеру использовать asm.js. Кроме того, почему использование браузера для использования asm.js незначительно помогает Firefox и полностью обходит Chrome?

4b9b3361

Ответ 1

Фактически asm.js не был создан для написания кода вручную, а только в результате компиляции с других языков. Насколько я знаю, нет инструментов, которые проверяют код asm.js. Вы пытались написать код в C lang и использовать Emscripten для генерации кода asm.js? Я сильно подозреваю, что результат будет совсем другим и оптимизирован для asm.js.

Я думаю, что смешивание типизированных и нетипизированных vars вы только добавляете сложность без каких-либо преимуществ. Напротив, код "asm.js" более сложный: я попытался разобрать asm.js и простые функции на jointjs.com/demos/javascript-ast, и результаты:

  • простая js-функция имеет 137 узлов и 746 токенов
  • функция asm.js имеет 235 узлов и 1252 токенов

Я бы сказал, что если у вас есть больше инструкций для выполнения в каждом цикле, это будет намного медленнее.

Ответ 2

Существует некоторая стоимость исправления для переключения контекстов asm.js. В идеале вы делаете это один раз и запускаете весь свой код в своем приложении как asm.js. Затем вы можете управлять памятью с помощью типизированных массивов и избегать множества сборщиков мусора. Я бы предложил переписать профилировщик и измерить asm.js в asm.js - без переключения контекста.