2015-08-01 5 views
39

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

  1. обыкновенные (т.е. разборчивый, хотя C-стиль) JavaScript
  2. То же, что 1, с asm.js аннотации добавили так, что она проходит валидатор, в соответствии с Firefox и другими инструментами
  3. То же, что и 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 требуется (например, «х | 0», «~ ~ x "," arr [(x < < 2) >> 2] »и т. д.)

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

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

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

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

+0

Для начала я запустил тест [«Massive»] (https://kripken.github.io/Massive/) в Chrome 44 и FF 39 (Win XP, 32 бит), вот мои результаты для [Chrome ] (http://pastebin.com/fZQYzWKs) и [Firefox] (http://pastebin.com/brtZHecb) (копирование и сбрасывание в поле «введите данные, скопированные из другого запуска» на тестовой странице - да, это работает с фактическим HTML). За исключением одного пункта («poppler-cold-preparation»), Chrome был медленнее везде, в крайнем случае в 24,6 раза медленнее FF. Похоже, что Chrome в настоящее время просто не способен обрабатывать asm.js разумно. – Siguza

+2

просто идея, вы «сравнили» последующие/повторные вызовы, так как asm будет использовать больше времени на этапах компиляции/выбора (я полагаю)? – birdspider

+0

@birdspider Ты хочешь сказать, что несколько раз тестировал бенчмарк? Нет, я просто взял то, что было там ... текущий интерфейс, похоже, требует перезагрузки страницы для повторного запуска теста, что, скорее всего, требует компиляции/оптимизации кода снова. Но весь тест занял около 15 минут, чтобы закончить для меня, поэтому я думаю, что время компиляции не является существенным фактором. Если Chrome действительно забирает так много времени, чтобы скомпилировать его, это меня озадачивает, что код даже запускается * вообще *. – Siguza

ответ

9

На самом деле 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 жетонов

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

+3

Хотя вы правы, что он не предназначен для написания от руки, [здесь] (http://turtlescript.github.cscott.net/asmjs.html) кажется валидатор asm.js, и OP 'AsmWaveModule' передает чек. – Siguza

+0

Кроме того, Firefox печатает это в консоли: «Успешно скомпилирован код asm.js (общее время компиляции 1 мс, не хранится в кеше (слишком мало для пользы)). Когда я удаляю '~~' из jsfiddle OP, это изменяется на 'TypeError: asm.js type error: Disabled by debugger'. Таким образом, казалось бы, сам asm.js не ошибочен. – Siguza

+0

Я согласен, что на 50% больше узлов/токенов должно замедлиться, но это удивительный удар. Аннотации (с включенным «no asm» включены) вносят 3-кратное замедление в Firefox и Chrome. Я пробовал его на Safari (без поддержки asm.js), все 3 версии были очень медленными (не удивительно), но аннотации требуют только 50% -ное замедление в Safari. – jtiscione

1

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

+0

Но в этом случае мусора не нужно собирать, так как все операции выполняются в общей куче ArrayBuffer, созданной при запуске и доступной через типизированные представления массивов (Int32Array и Uint32Array). AFAIK нет способа разместить целое приложение в asm.js. Вам всегда понадобится внешний код, чтобы создать экземпляр скомпилированного модуля и вызвать его точки входа. – jtiscione

+0

[Я пытался также измерить время изнутри итерации] (https://jsfiddle.net/8xk7m6gr/5/), но, похоже, это не имеет значения ... или использует 'stdlib. performance.now() 'вызывать еще один контекстный переключатель? Если это так, можно ли даже измерить фактическое время, затрачиваемое на функцию asm.js? – Siguza

+0

Если вы перейдете через источник JS, где он всегда инициализирует «totalCycles» до 4, и поднимите его до 8 или 12, он будет цикл 2X и 3X столько раз через этот код точки доступа выше. В Chrome время замедления составляло от 22 до 40 до 65 мс для обычных JS, от 140 до 275 до 400 мс для asm.js и от 70 до 140 до 210 мс для asm.js без директивы. Поскольку время, затрачиваемое на запуск, почти прямо пропорционально времени, которое он проводит в этом коде выше, я думаю, что накладные расходы от переключения контекста выглядят довольно маленькими, по крайней мере, в этом случае. – jtiscione

Смежные вопросы