Почему функция webAssembly работает почти на 300 раз медленнее той же функции JS

Найти длину строки на 300 * медленнее

Сначала я прочитал ответ на вопрос Почему моя функция WebAssembly медленнее, чем эквивалент в JavaScript?

Но это пролило немного света на эту проблему, и я потратил много времени, которое вполне может быть желтым на стене.

Я не использую глобалы, я не использую память. У меня есть две простые функции, которые находят длину отрезка и сравнивают их с одним и тем же в простом старом Javascript. У меня есть 4 параметра и еще 3 локальных, и я возвращаю число с плавающей или двойной.

В Chrome Javascript в 40 раз быстрее, чем webAssembly, а в Firefox wasm почти в 300 раз медленнее, чем Javascript.

контрольный пример jsPref.

Я добавил тестовый пример в jsPref WebAssembly V Javascript math

Что я делаю не так?

Либо

  1. Я пропустил очевидную ошибку, плохую практику или страдаю от глупости кодера.
  2. WebAssembly не для 32-битной ОС (выиграть 10 ноутбуков i7CPU)
  3. WebAssembly далека от готовой технологии.

Пожалуйста, выберите вариант 1.

Я прочитал пример использования веб-сборки

Повторно использовать существующий код, ориентируясь на WebAssembly, встроенный в более крупный JavaScript/HTML приложение. Это может быть что угодно из простого вспомогательные библиотеки для разгрузки задач, ориентированных на вычисления.

Я надеялся, что смогу заменить некоторые геометрические библиотеки на webAssembly, чтобы получить дополнительную производительность. Я надеялся, что это будет круто, в 10 и более раз быстрее. НО в 300 раз медленнее WTF.


UPADTE

Это не проблема оптимизации JS.

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

  • счетчик c += length(..., чтобы убедиться, что весь код выполняется.
  • bigCount += c для обеспечения выполнения всей функции. Не требуется
  • 4 строки для каждой функции, чтобы уменьшить наклон наклона. Не требуется
  • все значения случайным образом генерируются двойными числами
  • каждый вызов функции возвращает свой результат.
  • добавьте более медленное вычисление длины в JS, используя Math.hypot, чтобы доказать, что код выполняется.
  • добавлен пустой вызов, который возвращает первый параметр 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",
    }
],

Ответ 1

Андреас описывает несколько веских причин, по которым реализация JavaScript первоначально была замечена в 300 раз быстрее. Однако у вашего кода есть ряд других проблем.

  1. Это классический "микро-тест", т.е. код, который вы тестируете, настолько мал, что другие издержки в цикле тестирования являются существенным фактором. Например, при вызове WebAssembly из JavaScript возникают накладные расходы, которые будут влиять на ваши результаты. Что вы пытаетесь измерить? скорость сырой обработки? или накладные расходы языковой границы?
  2. Ваши результаты сильно различаются, от х300 до х2, из-за небольших изменений в тестовом коде. Опять же, это проблема микро тестов. Другие видели то же самое при использовании этого подхода для измерения производительности, например, это сообщение утверждает, что он быстрее на x84, что явно неверно!
  3. Текущая виртуальная машина WebAssembly является очень новой и MVP. Это будет быстрее. У вашей виртуальной машины JavaScript было 20 лет, чтобы достичь своей текущей скорости. Производительность границы JS & lt; => wasm в настоящее время обрабатывается и оптимизируется.

Более точный ответ см. в совместном документе команды WebAssembly, в котором излагается ожидаемый прирост производительности во время выполнения примерно на 30%

.Наконец, чтобы ответить на ваш вопрос:

Какой смысл в WebAssembly, если он не оптимизирует

Я думаю, у вас есть неправильные представления о том, что WebAssembly сделает для вас. Исходя из вышеприведенного документа, оптимизация производительности во время выполнения довольно скромная. Тем не менее, есть ряд преимуществ в производительности:

  1. Его компактный средний двоичный формат и низкоуровневая природа означают, что браузер может загружать, анализировать и компилировать код намного быстрее, чем JavaScript. Предполагается, что WebAssembly может быть скомпилирована быстрее, чем ваш браузер может загрузить ее.
  2. WebAssembly имеет предсказуемую производительность во время выполнения. В JavaScript производительность, как правило, увеличивается с каждой итерацией при дальнейшей оптимизации. Он также может уменьшиться из-за se-оптимизации.

Также есть ряд не связанных с производительностью преимуществ.

Для более реалистичного измерения производительности посмотрите на:

И то и другое практично, производственные кодовые базы.

Ответ 2

Механизм JS может применить к этому примеру много динамических оптимизаций:

  • Выполняйте все вычисления с целыми числами и преобразуйте их только в double для окончательного вызова в Math.sqrt.

  • Ввести вызов функции len.

  • Поднимите вычисление из цикла, так как он всегда вычисляет одно и то же.

  • Признайте, что цикл оставлен пустым и полностью его устранить.

  • Признайте, что результат никогда не возвращается из функции тестирования и, следовательно, удаляет весь объект тестовой функции.

Все, кроме (4), применяются, даже если вы добавляете результат каждого вызова. С (5) конечным результатом является пустая функция в любом случае.

С Wasm движок не может выполнять большинство этих шагов, потому что он не может встроить границы языка (по крайней мере, ни один движок не делает это сегодня, AFAICT). Кроме того, для Wasm предполагается, что исполняющий (автономный) компилятор уже выполнил соответствующие оптимизации, поэтому Wasm JIT имеет тенденцию быть менее агрессивным, чем один для JavaScript, где статическая оптимизация невозможна.