Чтобы посмотреть, как это работает, я написал очень короткий модуль 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?