Почему функция webAssembly почти на 300 раз медленнее, чем та же функция JS
найти длину строки 300 * медленнее
сначала я прочитал ответ почему моя функция WebAssembly медленнее, чем эквивалент JavaScript?
я не использую глобалы, я не использую никакой памяти. У меня есть две простые функции, которые находят длину отрезка и сравнивают их с то же самое в обычном Javascript. У меня есть 4 params 3 больше местных жителей и возвращает float или double.
в Chrome Javascript в 40 раз быстрее, чем webAssembly, а в firefox wasm почти 300 раз медленнее, чем Javascript.
тестовый случай jsPref.
я добавил тестовый случай в jsPref WebAssembly V Javascript math
что я делаю не так?
или
- я пропустил очевидную ошибку, плохую практику, или я страдаю глупостью кодера.
- WebAssembly не для 32-битной ОС (win 10 ноутбук i7CPU)
- WebAssembly далека от готовой технологии.
пожалуйста, пожалуйста, Вариант 1.
прочитал случай использования webAssembly
повторно использовать существующий код, ориентируясь на WebAssembly, встроенный в больше Приложение JavaScript / HTML. Это может быть что угодно от простого вспомогательные библиотеки для разгрузки вычислительно-ориентированных задач.
я надеялся, что смогу заменить некоторые геометрические библиотеки на webAssembly, чтобы получить дополнительную производительность. Я надеялся, что это будет потрясающе, как 10 или более раз быстрее. Но в 300 раз медленнее WTF.
UPADTE
это не вопросы оптимизации JS.
чтобы гарантировать, что оптимизация имеет как можно меньше эффекта я протестировал, используя следующие методы для уменьшения или устранения любого смещения оптимизации..
- счетчик
c += length(...
для обеспечения выполнения кода. -
bigCount += c
для обеспечения выполнения всей функции. Не нужно - 4 линии для каждой функции, чтобы уменьшить наклон. Не Нужно
- все значения генерируются случайным образом удваивает
- каждый вызов функции возвращает другой результат.
- добавить медленный расчет длины в JS с помощью
Math.hypot
чтобы доказать, что код выполняется. - добавлен пустой вызов, который возвращает первый param JS, чтобы увидеть накладные расходы
// setup and associated functions
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const a = setOf(100009,i=>rand(-100000,100000));
var bigCount = 0;
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
function lenSlow(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.hypot(nx,ny);
}
function lenEmpty(x,y,x1,y1){
return x;
}
// Test functions in same scope as above. None is in global scope
// Each function is copied 4 time and tests are performed randomly.
// c += length(... to ensure all code is executed.
// bigCount += c to ensure whole function is executed.
// 4 lines for each function to reduce a inlining skew
// all values are randomly generated doubles
// each function call returns a different result.
tests : [{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += length(a1,a2,a3,a4);
c += length(a2,a3,a4,a1);
c += length(a3,a4,a1,a2);
c += length(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length64",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lengthF(a1,a2,a3,a4);
c += lengthF(a2,a3,a4,a1);
c += lengthF(a3,a4,a1,a2);
c += lengthF(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length32",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += len(a1,a2,a3,a4);
c += len(a2,a3,a4,a1);
c += len(a3,a4,a1,a2);
c += len(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length JS",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenSlow(a1,a2,a3,a4);
c += lenSlow(a2,a3,a4,a1);
c += lenSlow(a3,a4,a1,a2);
c += lenSlow(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Length JS Slow",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenEmpty(a1,a2,a3,a4);
c += lenEmpty(a2,a3,a4,a1);
c += lenEmpty(a3,a4,a1,a2);
c += lenEmpty(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Empty",
}
],
результаты обновления.
поскольку в тесте намного больше накладных расходов, результаты ближе, но код JS все еще на два порядка быстрее.
обратите внимание, как замедлить функцию Math.hypo
Т. Если оптимизация по сути, эта функция была бы ближе к более быстрой .
- WebAssembly 13389µs
- 728µs на JavaScript
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 147
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 12736µs ±69µs (*) 3013 samples
---------------------------------------------
Test : 'length32'
Mean : 13389µs ±94µs (*) 2914 samples
---------------------------------------------
Test : 'length JS'
Mean : 728µs ±6µs (*) 2906 samples
---------------------------------------------
Test : 'Length JS Slow'
Mean : 23374µs ±191µs (*) 2939 samples << This function use Math.hypot
rather than Math.sqrt
---------------------------------------------
Test : 'Empty'
Mean : 79µs ±2µs (*) 2928 samples
-All ----------------------------------------
Mean : 10.097ms Totals time : 148431.200ms 14700 samples
(*) Error rate approximation does not represent the variance.
*/
в чем смысл WebAssambly, если он не оптимизирует
конец обновления
все, что связано с проблемой.
найти длину линии.
первоисточник на заказ язык
// declare func the < indicates export name, the param with types and return type
func <lengthF(float x, float y, float x1, float y1) float {
float nx, ny, dist; // declare locals float is f32
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
// and as double
func <length(double x, double y, double x1, double y1) double {
double nx, ny, dist;
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
код компилируется в Wat для доказательства чтения
(module
(func
(export "lengthF")
(param f32 f32 f32 f32)
(result f32)
(local f32 f32 f32)
get_local 2
get_local 0
f32.sub
set_local 4
get_local 3
get_local 1
f32.sub
tee_local 5
get_local 5
f32.mul
get_local 4
get_local 4
f32.mul
f32.add
f32.sqrt
)
(func
(export "length")
(param f64 f64 f64 f64)
(result f64)
(local f64 f64 f64)
get_local 2
get_local 0
f64.sub
set_local 4
get_local 3
get_local 1
f64.sub
tee_local 5
get_local 5
f64.mul
get_local 4
get_local 4
f64.mul
f64.add
f64.sqrt
)
)
как скомпилированный wasm в шестнадцатеричной строке (Примечание не включает раздел name) и загружен с помощью WebAssembly.компилировать. Экспортированные функции затем запускаются против функции Javascript len (в приведенном ниже фрагменте)
// hex of above without the name section
const asm = `0061736d0100000001110260047d7d7d7d017d60047c7c7c7c017c0303020001071402076c656e677468460000066c656e67746800010a3b021c01037d2002200093210420032001932205200594200420049492910b1c01037c20022000a1210420032001a122052005a220042004a2a09f0b`
const bin = new Uint8Array(asm.length >> 1);
for(var i = 0; i < asm.length; i+= 2){ bin[i>>1] = parseInt(asm.substr(i,2),16) }
var length,lengthF;
WebAssembly.compile(bin).then(module => {
const wasmInstance = new WebAssembly.Instance(module, {});
lengthF = wasmInstance.exports.lengthF;
length = wasmInstance.exports.length;
});
// test values are const (same result if from array or literals)
const a1 = rand(-100000,100000);
const a2 = rand(-100000,100000);
const a3 = rand(-100000,100000);
const a4 = rand(-100000,100000);
// javascript version of function
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
и код теста это же для всех 3 функций и бега в строгом режиме.
tests : [{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
length(a1,a2,a3,a4);
}
},
name : "length64",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
lengthF(a1,a2,a3,a4);
}
},
name : "length32",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
len(a1,a2,a3,a4);
}
},
name : "lengthNative",
}
]
результаты теста на FireFox являются
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 34
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 26359µs ±128µs (*) 1128 samples
---------------------------------------------
Test : 'length32'
Mean : 27456µs ±109µs (*) 1144 samples
---------------------------------------------
Test : 'lengthNative'
Mean : 106µs ±2µs (*) 1128 samples
-All ----------------------------------------
Mean : 18.018ms Totals time : 61262.240ms 3400 samples
(*) Error rate approximation does not represent the variance.
*/
2 ответов
Андреас описывает ряд веских причин, почему реализация JavaScript была первоначально наблюдалось, что x300 быстрее. Однако, есть ряд других проблем с вашим кодом.
- Это классический микро эталоном, т. е. код, который вы тестируете настолько мал, что другие накладные расходы в тестовом цикле являются существенным фактором. Например, есть накладные расходы при вызове WebAssembly из JavaScript, которые будут учитывать ваши результаты. Что вы пытаетесь измерить? скорость обработки сырья? или накладные расходы языковой границы?
- ваши результаты сильно варьируются от x300 до x2 из-за небольших изменений в тестовом коде. Опять же, это вопрос микро-ориентира. Другие видели то же самое при использовании этого подхода для измерения производительности, например этот пост утверждает, что wasm x84 быстрее, что явно неправильно!
- текущая виртуальная машина WebAssembly очень новая и MVP. Так будет быстрее. Ваш У JavaScript VM было 20 лет, чтобы достичь своей текущей скорости. Производительность границы JS wasm является работал и оптимизирован прямо сейчас.
для более окончательного ответа см. совместный документ команды WebAssembly, в котором описывается ожидаемый увеличение производительности во время выполнения около 30%
наконец, чтобы ответить на ваш вопрос:
в чем смысл WebAssembly, если это не так оптимизировать
Я думаю, у вас есть неправильные представления о том, что WebAssembly сделает для вас. Основываясь на приведенной выше статье, оптимизация производительности среды выполнения довольно скромна. Однако, есть еще ряд преимуществ:
- его компактный двоичный формат означает, что браузер может загружать, анализировать и компилировать код намного быстрее, чем JavaScript. Ожидается, что WebAssembly может быть скомпилирован быстрее, чем ваш браузер загрузить его.
- WebAssembly имеет предсказуемую производительность во время выполнения. С JavaScript производительность обычно увеличивается с каждой итерацией по мере ее дальнейшей оптимизации. Он также может уменьшаться из-за se-оптимизации.
есть также ряд не связанных с производительностью преимуществ тоже.
для более реалистичного измерения производительности, взгляните на:
- его использование в Figma
- результаты использования это с PDFKit
Как практические, производственные программы.
движок JS может применить множество динамических оптимизаций к этому примеру:
выполните все вычисления с целыми числами и преобразуйте только в double для окончательного вызова Math.функция sqrt.
Inline вызов .
выведите вычисление из цикла, так как оно всегда вычисляет одно и то же.
признать, что цикл остается пустым и устранить его полностью.
признают, что результат никогда не возвращается из функции тестирования, и, следовательно, удалить все тело функции тест.
все, кроме (4), применяются, даже если вы добавляете результат каждого вызова. С (5) конечный результат является пустой функцией в любом случае.
С Wasm движок не может выполнить большинство этих шагов, потому что он не может встроиться через языковые границы (по крайней мере, ни один движок не делает этого сегодня, AFAICT). Кроме того, для Wasm предполагается, что производящий (автономный) компилятор уже выполнил соответствующие оптимизации, поэтому JIT Wasm имеет тенденцию быть менее агрессивным, чем для JavaScript, где статическая оптимизация невозможна.