Asm.js практика

image
Этим прохладным днём я искал алгоритмы и реализации вычисления числа пи. Алгоритмов нашлось какое-то несметное множество, но тут нашёлся пост с описанием алгоритма и его реализацией на си.
Алгоритм подкупает своей скоростью, хоть и выдаёт hex представление, но так уж вышло, что мне нужен был вариант на js. Моментальная, практически, переработка на обычный js показала очень плохую статистику, работа при подсчёте 1000000-ого знака заняла… 48 секунд (4ГГц FF).
О том, как возился с asmjs и каких камней повстречал можно узнать под катом.


Для нетерпеливых, результат на гитхабе.

После беглого просмотра стало понятно, что нам не нужно выносить работу со всей генерацией в модуль asm, а используются только две функции: expm и series. Т.к. expm вызывается внутри series, то нам из модуля следует экспортировать только series.
Оригинал функций
double series (int m, int id)

/*  This routine evaluates the series  sum_k 16^(id-k)/(8*k+m) 
    using the modular exponentiation technique. */

{
  int k;
  double ak, eps, p, s, t;
  double expm (double x, double y);
#define eps 1e-17

  s = 0.;

/*  Sum the series up to id. */

  for (k = 0; k < id; k++){
    ak = 8 * k + m;
    p = id - k;
    t = expm (p, ak);
    s = s + t / ak;
    s = s - (int) s;
  }

/*  Compute a few terms where k >= id. */

  for (k = id; k <= id + 100; k++){
    ak = 8 * k + m;
    t = pow (16., (double) (id - k)) / ak;
    if (t < eps) break;
    s = s + t;
    s = s - (int) s;
  }
  return s;
}

double expm (double p, double ak)

/*  expm = 16^p mod ak.  This routine uses the left-to-right binary 
    exponentiation scheme. */

{
  int i, j;
  double p1, pt, r;
#define ntp 25
  static double tp[ntp];
  static int tp1 = 0;

/*  If this is the first call to expm, fill the power of two table tp. */

  if (tp1 == 0) {
    tp1 = 1;
    tp[0] = 1.;

    for (i = 1; i < ntp; i++) tp[i] = 2. * tp[i-1];
  }

  if (ak == 1.) return 0.;

/*  Find the greatest power of two less than or equal to p. */

  for (i = 0; i < ntp; i++) if (tp[i] > p) break;

  pt = tp[i-1];
  p1 = p;
  r = 1.;

/*  Perform binary exponentiation algorithm modulo ak. */

  for (j = 1; j <= i; j++){
    if (p1 >= pt){
      r = 16. * r;
      r = r - (int) (r / ak) * ak;
      p1 = p1 - pt;
    }
    pt = 0.5 * pt;
    if (pt >= 1.){
      r = r * r;
      r = r - (int) (r / ak) * ak;
    }
  }

  return r;
}

Базовый шаблон.


По сути мы создаём чёрный ящик с некоторым интерфейсом, поэтому всё, что мы можем, так передать что-либо в модуль и получить из него на выходе набор методом и/или значений.
В просмотренных мной кодах устоялась конструкция вида:
(function (window) {
  "use strict";
  // переменные
  var buffer = new ArrayBuffer(BUFFER_SIZE); // буфер для работы представлений типизированного массива

  var functionNameOrStuctureName = (function (stdlib, env, heap) {
    "use asm";
    // переменные
    // тело модуля

    return {
      methodNameExport: methodNameInModule,
      methodName2Export: methodName2InModule,
    }; // или просто return methodNameInModule
  })(
    { // классы и объекты (stdlib)
      Uint8Array:  Uint8Array,
      Int8Array:   Int8Array,
      Uint16Array: Uint16Array,
      Int16Array:  Int16Array,
      Uint32Array: Uint32Array,
      Int32Array:  Int32Array,
      Float32Array:Float32Array,
      Float64Array:Float64Array,
      Math:        Math
    },
    { // переменные (env)
      NTP:NTP
    },
    buffer // и буфер, крайне немаловажен, при чём размер > 4096 и равен степени дв0йки
  );
})(window); // wrapper end

Бывает вылетает ошибка вида «TypeError: asm.js type error: asm.js module must end with a return export statement», удостоверьтесь, что модуль возвращает что-либо. Если же возвращает как положено, то следует убедиться в правильности деклараций переменных. У меня была ошибка, когда после декларации я пытался что-то ещё делать с одной интовой переменной.
Надеюсь базовые вещи уже успели узнать, но всё же:
  function f1(i, d) {
    i = i | 0; // integer заявляем, что i  целочисленная переменная
    d = +d; // double заявляем, что d  переменная с плавающей точкой
    var char = new Uint8Array(heap); // строки, в данной статье рассмотрены не будут
    var i2 = 0; // объявляем целочисленную переменную (на самом деле она сейчас fixnum)
    var d2 = 0.0; // объявляем переменную с плавающей точкой
    i2 = i2 | 0; // конвертируем fixnum в integer
    return +d2; // функция имеет тип double  и может возвращать только double
  }


Подводный камень №1: переменные и функции модуля


Я привык сначала декларировать все переменные, а уже потом присваивать им значения. В asm.js всё несколько хитрее: сначала мы декларируем все переменные, которые используем через замыкания, при чём функции математической библиотеки тоже надо описать здесь, а не вызывать ниже.
Вот что вышло:
   "use asm"; 
    // об stdlib выше
    var floor = stdlib.Math.floor; // некоторые инициализируют тут все ссылки на функции, но я ленив
    var pow = stdlib.Math.pow; // поэтому тут только те, что использовал ниже
    var tp1 = 0; // этот флаг используется ниже
    var tp = new stdlib.Float64Array(heap); // представление типизированного массива
    var ntp = env.NTP | 0; // для работы с глобальной переменной используем env, о нём выше

Функции же нельзя создавать предварительно инициализируя переменную и присваивая ей функцию. Поэтому следом идут функции.
    function expm(p, ak) {
        // тело
    }


Подводный камень №2: переменные в функциях модуля.


А вот в теле функций модуля сначала следует указать тип переменных, принимаемых на входе, потом инициализировать внутренние переменные, при чём если это int, то var i = 0, если double, то var d = 0.0, если массив, то через new и тип мссива с передачей в него heap, а уже после инициализации интов советую их «доинициализировать» путём присвоения вида i = i|0. К слову: инициализация переменных заранее в стиле си не обязательна. Числа вида 1e-17 выдают ошибку выхода за границы, используйте 0.00000000000000001
На выходе:
    function expm(p, ak) {
      p = +p;
      ak = +ak;
      var i = 0;
      var pt = 0.0;
      // ....
      i = i | 0;
      ntp = ntp | 0;
      // тело
    }


Подводный камень №3: сравнения и итераторы


Скажу честно, тут я смеялся долго, но int и int сравнению не подлежат. Если вы сделаете что-то вроде i == k или i < l, то вывалится компиляция с ошибкой вида
«TypeError: asm.js type error: arguments to a comparison must both be signed, unsigned or doubles, int and int are given».
Ещё немного смеху добавило сравнение int и целого числа (i ==0)
«TypeError: asm.js type error: arguments to a comparison must both be signed, unsigned or doubles, fixnum and int are given».
Только у чисел с плавающей точкой всё хорошо (например pt == 1.0).
В итоге, если вы хотите всё-таки сравнить int с другим int. надо продекларировать, конструкцию вида (i | 0) < (ntp | 0).
Что касается итераторов, то тут просто «прелесть»: вместо всем нам привычного i++ мы имеем i = (i | 0) + 1 | 0.
Результат:
      if ((tp1 | 0) == 0) {
        // vars
        for (i = 1; (i | 0) < (ntp | 0); i = (i | 0) + 1 | 0) {
          // surprise, read more)
        }
      }


Подводный камень №4: Математические и прочие внешние функции.


Тут всё просто, если вы хотите использовать floor, sin и т.п., то вам нужно декларировать их в начале модуля (сразу после «use asm»). Если в функции написать stdlib.Math.floor, у меня выкидывал ошибку возвращаемого типа. Видимо из-за обращения к свойствам объекта.

Подводный камень №5: Буферы.


А вот тут всё очень и очень интересно. Что мы делаем, когда хотим получить/установать значение из/в массива arr с индексом i? arr[i]. Допустим мы так и поступим, но тогда мы получим ошибку вида.
          arr[i] = +1;

«TypeError: asm.js type error: index expression isn't shifted; must be an Int8/Uint8 access»
Нам тонко намеают, что должен быть сдвиг. У одного гуру я нашёл сдвиг на 2 вправо.
          arr[i >> 2] = +1;

«TypeError: asm.js type error: shift amount must be 3»
Нам как бы тонко намекают, что он должно быть трём.
          arr[i << 3 >> 3] = +1;

Выходит в итоге. Сдвигом в лево мы скомпенсировали сдвиг в право. Вроде всё тоже самое, а работает.

Результат трудов
/**
 * Created with JetBrains WebStorm.
 * User: louter
 * Date: 12.09.13
 * Time: 17:49
 */
(function (window) {
  "use strict";
  var ihex;
  var NTP = 25;
  var buffer = new ArrayBuffer(1024 * 1024 * 8);

  var series = (function (stdlib, env, heap) {
    "use asm";
    var floor = stdlib.Math.floor;
    var pow = stdlib.Math.pow;
    var tp1 = 0;
    var tp = new stdlib.Float64Array(heap);
    var ntp = env.NTP | 0;

    function expm(p, ak) {
      p = +p;
      ak = +ak;
      var i = 0;
      var j = 0;
      var p1 = 0.0;
      var pt = 0.0;
      var r = 0.0; // float as double
      i = i | 0;
      j = j | 0;
      ntp = ntp | 0;

      if ((tp1 | 0) == 0) {
        tp1 = 1 | 0;
        tp[0] = +1;
        for (i = 1; (i | 0) < (ntp | 0); i = (i | 0) + 1 | 0) {
          tp[(i << 3) >> 2] = +(+2 * tp[((i - 1) << 3) >> 3]);
        }
      }
      if (ak == 1.0) {
        return +0;
      }
      for (i = 0; (i | 0) < (ntp | 0); i = (i | 0) + 1 | 0) {
        if (+tp[i << 3 >> 3] > p) {
          break;
        }
      }
      pt = +tp[(i - 1) << 3 >> 3];
      p1 = +p;
      r = +1;
      for (j = 0; (j | 0) <= (i | 0); j = (j | 0) + 1 | 0) {
        if (p1 >= pt) {
          r = +16 * r;
          r = r - (+(floor(r / ak))) * ak;
          p1 = p1 - pt;
        }
        pt = 0.5 * pt;
        if (pt >= 1.) {
          r = r * r;
          r = r - (+(floor(r / ak))) * ak;
        }
      }

      return +r;
    }

    function series(m, id) {
      m = m | 0;
      id = id | 0;
      var k = 0;
      var ak = 0.0;
      var eps = 0.0;
      var p = 0.0;
      var s = 0.0;
      var t = 0.0;
      eps = 0.00000000000000001;
      k = 0 | 0;
      for (k; (k | 0) < (id | 0); k = (k | 0) + 1 | 0) {
        ak = +8 * (+(k | 0)) + (+(m | 0));
        p = (+(id | 0) - +(k | 0));
        t = +expm(p, ak);
        s = s + t / ak;
        s = s - floor(s);
      }
      for (k = (id | 0); (k | 0) <= ((id + 100) | 0); k = (k | 0) + 1 | 0) {
        ak = +8 * (+(k | 0)) + (+(m | 0));
        t = pow(+16, +(id | 0) - (+(k | 0))) / +ak;
        if (t < eps) break;
        s = s + t;
        s = s - floor(s);
      }
      return +s;
    }

    return series;
  })(
    {
      Uint8Array:  Uint8Array,
      Int8Array:   Int8Array,
      Uint16Array: Uint16Array,
      Int16Array:  Int16Array,
      Uint32Array: Uint32Array,
      Int32Array:  Int32Array,
      Float32Array:Float32Array,
      Float64Array:Float64Array,
      Math:        Math
    },
    {
      NTP:NTP
    },
    buffer
  );
  ihex = function (x, nhx, chx) {
    var i, y, hx = "0123456789ABCDEF";

    y = Math.abs(x);
    for (i = 0; i < nhx; i++) {
      y = 16. * (y - (y | 0));
      chx[i] = hx[y | 0];
    }
  };
  window.pi = function (id) {
    var pid, s1, s2, s3, s4
      , hex = [];
    s1 = series(1, id);
    s2 = series(4, id);
    s3 = series(5, id);
    s4 = series(6, id);
    pid = 4 * s1 - 2 * s2 - s3 - s4;
    pid = pid - (pid | 0) + 1;
    ihex(pid, 16, hex);

    return {
      hex:     hex.join('').substr(0, 10),
      fraction:pid
    };
  };
})(window);


P.S. Я уж не знаю, кто или что не правы, но(!) почему-то скомпилированная программа выдала результаты хуже, чем asm.js.
А именно
time ./pi 1000000
>> real 0m2.161s
>> user 0m2.149s
>> sys 0m0.001s

console.time('s');
pi(1000000);
console.timeEnd('s');
>> s: 1868.5мс

И ещё:
time ./pi 10000000
>> real 0m25.345s
>> user 0m25.176s
>> sys 0m0.019s

console.time('s');
pi(10000000);
console.timeEnd('s');
>> s: 22152.83мс

Не верите? Проверьте. Исходник программы в вышеописанном посте. Исходники мои так же выше указаны.

UPD:
На гитхаб выложил testASM.js для проверки работает ли asmjs или нет. После подключения появляется переменная window.asmjs (bool). testASM.js и testASM.min.js
Поделиться публикацией
Комментарии 43
    0
    Если я правильно понял, у вас немного перепутано количество знаков в конечной статистике. Сейчас выглядит как будто на порядок время лучше, на самом деле (если я правильно понял), всего на 3 секунды лучше (сравнивать надо первую и последнюю — они для 10^7 знаков, результат в 1.8с — для 10^6).
      0
      Спасибо, исправил. В первом случае взял время не из той консоли.
      В конечной статистике разная единица измерения (секунды и мс). А сравнивать надо по сути второе значение от программы и результат работы браузера. Целиком вывел для наглядности.
      +1
      Результат мягко говоря впечатляет
        +3
        но процесс написания с костылями — не очень
          +1
          Предполагается, что руками на нем писать не будут (это как писать на ассемблере — можно, но сложнее, чем на языках высокого уровня), а костылями будет управлять транслятор.
        +4
        Чё-то как-то технология asm.js выглядит сыроватой.
          0
          Не могу не согласиться) По-моему в этот язык инвестировала мозилла с целью использования своей платформы, как игровой. Поэтому есть emcc, чтобы компилить обычный сишный код в asmjs. Поэтому и WebGL + asm.js. А где игры, там и юзеры)
            0
            Да, asm.js конечно это костыль костылем, но надеюсь, что в него смогут компилироваться другие языки и не надо будет его дополнительно изучать.
              +1
              В следующем посте рассмотрю компиляцию на основе lljs > asm.js.
              Собираюсь посмотреть в сторону Emscripten.
              Первое язык, который можно скомпилировать в asm.js, а второе инструмент для компиляции сишного кода в asm.js)
            +3
            У меня ощущение, что asm.js разработал skynet, чтобы люди не смогли понять когда их начнут захватывать.
            А по делу, интересно конечно узнать из первых рук, что это такое, хоть и не понятно как программистское сообщество до такого докатилось.
            За статью спасибо.
              +5
              Насколько я знаю, asm.js сделан для трансляторов с других языков и не предназначен к тому, чтобы на нём руками писали.
                0
                В этой статье мы видели пример того, как пришлось напрямую писать на asm.js. Кроме того есть куча библиотек, которые захотят улучшить производительность в критических местах. Им же не переходить ради этого на другие языки. Поэтому в результате получится, что очень многим программистам придется напрямую копаться в таком коде.
                  0
                  Ну да. Я, вот, программирую на Си время от времени. А когда хочется улучшить производительность, я лезу в Ассеблер и не ною на синтаксис.
                    0
                    Я бы сказал, что синтаксис простого ассемблера более читабелен. И здесь не было никаких проблем сделать синтаксис основанный, например, на неком наборе функций, который был бы понятен и людям.
                    А ещё один вариант — аннотации.
                    В общем, тут есть свобода для более красивой реализации.
                      0
                      asm.js сделан так, чтобы код мог интерпретироваться браузером без поддержки asm.js, просто с меньшей скоростью. Это исключает поддержку функций.
                        0
                        Не то чтобы совсем исключает. Просто нужну было бы подключать библиотеку с функциями заглушками и это бы особо не тормозило браузеры без поддержки asm.js.
                        А вариант с аннотациями точно был бы немного быстрее (опять же без поддержки asm), т.к. не приходилось бы интерпритировать/оптимизировать кучу странных конструкций. Плюс к этому не нужно было бы вспоминать, что (1|0) — это int, т.к. можно было бы написать
                        /*int*/ var i = 1 или
                        var i = /*int*/ 1
                        Если добавить сюда вывод типов (не знаю, реализованно ли это сейчас), то избыточность была бы минимальной.
                          0
                          Сам долго думал что же мешало сделать в таком виде. И нет ответа.
                          К тому же эти перепрыгивания типов fixnum->int->intish->signed вообще выглядят магически.
                            –1
                            Во-первых, комментарии не для этого. Не дело их трогать.
                            Во-вторых, это чересчур многословно. Просто кошмар.
                            В-третьих, переходы между типами сделаны, чтобы всё работало эквивалентно движкам без asm.js, только быстрее.

                            И, кстати, не нужно вспоминать, что variable|0 — это int, это просто надо знать. В обычном JS variable|0 тоже даёт int, это же известный приём, попробуйте.
                              +1
                              Ещё скажите, что +'123' даст мне 123)
                              1) ну команда |0 тоже не для того была придумана, чтобы создавать эквивалент int i, а скорее изначально это… = (int) i; т.е. приведение типов.
                              2) ну вообще говорить о многословности, когда есть популярное объявление вида public static int, например… В общем на 4 символа больше, зато очевиднее.
                              3) тут вы не правы) Переходы между типами в обычном js есть и так. Т.е. если мы убираем все эти трансформации в фикснум, инт, сигн и назад получаем… Рабочую версию. Они зачем-то нужны компилятору, но зачем вопрос остался)

                              Комментарии имеют только одно тонкое место, которое я вижу: минификаторы) Они их просто вырежут и вот снова фигскомпилишь язык на выходе)
                                0
                                На счет комментариев и минификаторов только добавлю, что выход есть и тут. Либо все аннотации начинать со спец. символа (как например в java @), чтобы минификаторы понимали, что их не нужно вырезать. Либо же аннотации были бы все описаны в стандарте и минификаторы опять же могли смотреть, что вырезать, а что нет.
                                0
                                На счет многословности могу сказать только одно. Она предотвращает огромное количество ошибок. И как я сказал уже, если добавить выведение типов по Хиндли-Милнеру, то останется только самое необходимое. Но даже без него оверхед по сравнению с текущим синтаксисом процентов на 50%, что прямо скажем не кошмар.
                0
                Спасибо! У меня есть несколько вопросов:
                1. Как сделать то же самое с помощью Emscripten?
                2. Можно ли вызывать Asm.js функции из обычных JS функций?
                3. Как определить, понимает ли браузер Asm.js (не в режиме совместимости)?
                  +1
                  1, Как компилировать код в asm.js вроде написано, поэтому берём pi.c и emcc pi.c -o pi.js. Но как с этим работать не разбирался пока что.
                  2. Да, вы можете вернуть из модуля объект с функциями. Тут, например можно в конце модуля написать
                  return {
                  series: series,
                  expm: expm
                  }
                  и потом в функциях js обращаться как к прочим методам (например в данном случае было бы series.series(...) or series.expm(...))
                  3.Озадаченный этим вопросом я так и не нашёл ответа, кроме разве что по-дурацки проверить время работы функции. В консоль в FF выводится «Error: successfully compiled asm.js code». Но никакие ухищрения не помогли мне вытащить эти заветные слова.
                    +1
                    3. testASM.js и testASM.min.js см апдейт.
                      0
                      Т.е. там время выполнения проверяется?
                        0
                        Да. Увы иначе только если грубо браузерное имя и версию) Но в сумме эта проверка и 70мс не займёт, так что если нужно, (а бывает ну очень нужно) подождать) А вот как поступать потом дело ваше)
                          0
                          function isASMSupporting(){
                           'use asm';
                           var i = 0.0;
                           try {
                            i = "";
                            return false;
                           }
                           catch(e){
                            return true;
                           }
                          }
                          

                          При условии, что там есть try-catch. Если нет — можно вынести за предел асм-кода.

                          Сомневаюсь, что это вообще валидный асм, но идея ясна :)
                    +1
                    Ваш код на FF Nightly не проходит asm.js-верификацию c сообщениями типа

                    [12:43:56.056] TypeError: asm.js type error: non-expression-statement call must be coerced @ pi.js:91


                    (надо писать +floor, +pow везде)

                    А теперь внимание, сюрприз, производительность при этом у него не страдает. Как следствие, если взять и аккуратно переписать этот код на простой человеческий JavaScript получим ту же самую производительность и читаемый код

                    [13:02:08.076] Error: successfully compiled asm.js code (total compilation time 0ms) @ pi.js
                    [13:02:08.084] "pi (asm.js) start"
                    [13:02:30.776] "{"hex":"7AF5863EFF","fraction":1.4803089050105553}"
                    [13:02:30.776] "pi (asm.js) took 22692 ms."
                    [13:02:30.786] "pi (pure JS) start"
                    [13:02:51.523] "{"hex":"7AF5863EFF","fraction":1.4803089050105553}"
                    [13:02:51.523] "pi (pure JS) took 20736 ms."


                    Как из всего этого следует вывод? Без надобности тут вам asm.js.

                    Это, впрочем, и из кода видно — нет там ничего такого, что вменяемый JIT не осилил бы оптимизировать самостоятельно, без назойливых ручных |0 и +foo
                      0
                      Да, на счёт +floor читал, но у меня не ночная, так что увидев, что работает, решил, что атавизм)

                      Какое число переданно в пи? Хэша 7AF5863EFF нет ни на 100 000 ни на 10 000
                      Сейчас перепроверил, вот взял ваш код и ничегошеньки не менял, 71,5 сек от 1'000'000.
                      console
                      — [01:34:18.041] console.time('njs');
                      console.dir(pi(1000000));
                      console.timeEnd('njs');
                      [01:34:18.045] undefined
                      [01:34:18.046] njs: таймер запущен
                      — [01:35:29.505] [object Object]
                      [01:35:29.506] njs: 71459.41мс

                      и мой итог 1,9 сек от 1'000'000
                      console
                      — [01:38:17.177] console.time('asmjs');
                      console.dir(pi(1000000));
                      console.timeEnd('asmjs');
                      [01:38:17.180] undefined
                      [01:38:17.181] asmjs: таймер запущен
                      [01:38:19.050] [object Object]
                      [01:38:19.050] asmjs: 1868.46мс
                        0
                        Число переданное в функцию: 10000000

                        Кстати походу FF замедляет нормальные функции (гонит их только на интерпретаторе?) если их выполнять прямо из консоли.

                        Я выполнял из контекста страницы (добавил в конец JS файла)

                          function measure(name, f) {
                            console.log(name + " start")
                            var start = Date.now();
                            var result = f(10000000);
                            var end = Date.now();
                            console.log(JSON.stringify(result));
                            console.log(name + " took " + (end - start) + " ms.");
                          }
                        
                        measure("pi (pure JS)", pi);
                        
                          0
                          консоль работает немного по другому, на ней не рекомендуется проверять тонкие вещи, можно почитать об этом подробнее.
                            0
                            Это мы уже заметили)
                            Хотя прирост, в итоге обнаружен, всё же он не так велик)
                        0
                        Судя по всему 10 000 000, но что-то тут не так, ваш код на 10^7 я просто даже боюсь запускать. Даже на шестом порядке больше минуты. Ваше pure точно pure?
                          0
                          Точно pure. Замеряно на FF Nightly и Chrome Dev Channel на Mac Book. Но замер запущен не из консоли, а из самого JS скрипта.
                            0
                            Перепроверил, правда ваша, разница не столь разительна, всего 75% асм от чистого. Интересно, конечно, почему так, ну что же, коварная консоль)
                            Два урока сразу: и по asm.js, и методы проверки надо проверять)
                              0
                              у меня вообще разницы нет (asm.js на самом деле чуть медленнее) в FF Nightly, какая у вас версия FF?

                              Интересно, конечно, почему так, ну что же, коварная консоль)


                              Про консоль я выясню у Мозиловцев.
                                0
                                FF 23 из репы федоры.
                                Больше всего меня удивило, что скомпилированная программа паботает дольше asm.js-а.
                                  0
                                  А как вы эту программу собирали? Допустим если я собираю как

                                  $ gcc -o pi -O3 -m32 -msse4 -mfpmath=sse pi.c


                                  то особой разницы не вижу

                                  $ time ./pi
                                   position = 10000000
                                   fraction = 1.480308905010555
                                   hex digits =  7AF5863EFF
                                  ./pi 10000000  20.38s user 0.01s system 99% cpu 20.391 total
                                    0
                                    от gcc -lm main.c до gcc -O3 -lm main.c -o pi -msse4 -mfpmath=sse (gcc 4.7.2)
                                    результат примерно одинаков:
                                    10^6 2162ms, 10^7 25201ms
                        0
                        Интересно, а в чём смысл этих магических сдвигов индекса в массиве на 3 бита? Это что, способ декларации типа такой? Типа, если 3 — то индекс целочисленный?
                          +2
                          смысл очень простой: asm.js вырос из emscripten, который фактически компилирует LLVM биткод в JS. Биткод работает с указателями и сырой памятью, но в JS указателей нет, поэтому память эмулируется типизированными массивами, которые все указывают на один и тот же ArrayBuffer, а указатели эмулируют целочисленной переменной. При этом поддерживается только правильно выравненные указатели, т.е. int16 должен быть выравнен на 2 байта, а int32 — на 4 байта и так далее.

                          Теперь если представить что i — это правильно выравненный указатель на double, то как прочитать double по «адресу» i? Надо взять Float64Array представление кучи и прочитать из него элемент с индексом (i / 8) (double занимаем 8 байт).

                          Отсюда и требование на сдвиг — это деление на размер элемента в преположении что остальное это «адрес», потому что emscripten всегда такой код рожает. В рукописном коде выглядит, конечно же, сюрреалистично
                          0
                          А реально ли прямо в браузере интерпретировать классический код на С++ (начиная с #include ...)? И если да, реально ли в нём обращаться к функциям из javascript-библиотек?
                            0
                            Вроде нет, но теоретически возможно: можно сбилдить все библиотеки в asm.js и дальше выгрузить в браузер, пускай компилит, но смысла вэтом не много: куда проще и быстрее изолированная песочница для компиляций. Но можете поискать, может где есть инструмент)

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое