Comments 29
Но так как VM использует внутренние оптимизации представления вида «считать беззнаковым 32-битным целым, пока не будет доказано обратное», это сработает. Так что тут «кротовьи норы» второго порядка.
Стоит ли их использовать? Я бы сказал, что нет. Код должен быть читабельным и понятным другому инженеру, который ничего не знает о вашем продукте. И только если результат читабельного кода медленный, и только если профилировщик покажет, что узкое место — конкретно в операции остатка от деления, то только тогда следует придумывать оптимизации.
Код должен быть читабельным и понятным другому инженеру, который ничего не знает о вашем продукте.
Но знает язык на должном уровне, согласны? И, очевидно, примерно помнит, чему равны степени двойки.
Я к тому, что приведённые примеры — не ухудшают читаемости кода. Для программиста число 65536 должно быть не более «магическим», чем 1000. А что память быстрее всего копировать блоками, равными разрядности шины — это общеизвестный факт.
Про преждевременные оптимизации и читабельность твердят на каждом шагу. Как будто это что-то плохое. В результате имеем тонны тормозных решений просто потому, что инженеры с умным видом сцат писать иначе.
и для double-ов деление на 15000 ничем принципиально не отличается от деления на степень двойки
Нет разницы только в процессорах последних поколений:
https://www.agner.org/optimize/instruction_tables.pdf
Wormholes же в оригинале. И картинка намекает, а перевод не очень удачный. Wormholes в данном случае типа залез в чёрную дыру тут, а вылез в 100 млн парсеков (за 1000 лет до рождения). Червоточины, возможно, было бы точнее и понятнее
Все являются, конечно. Главная идея статьи (поста в блоге в оригинале) в последней фразе. Перефразируя "JS такой высокоуровневый, а битовые хаки для оптимизации все равно работают".
То есть фактически подобные вещи «пробивают» все мнгочисленные слои абстракций, которые пытаются сделать вид, что JS — это такая себе сущность, описанная в спецификации… и никакого отношения к ассемблеру не имеет… а оказывается, что нет — таки имеет…
И что характерно, на самом деле многие скриптовые/высокоуровневые языки такими "битовыми" хаками особо не пооптимизируешь. Вот из того, что помню
- SQL — за современный Postgres с Jit не скажу, но в MS SQL Server/MySQL/Oracle/PostgreSQL разница на уровне погрешности.
- VB/VBS/VBA. Некоторые приемы работают, но только при строгой типизации. Variant или работа с COM мгновенно съедают разницу.
- 1С — там арифметика на своём "особенном" числовом типе. Этот тип раз в 100 медленнее Int32. Сдвигов и битовой логики нет.
- Powershell — с ним смешно. Я лет 6 назад с удивлением обнаружил, что типизированные переменные медленнее нетипизированных. Там просто при каждом присваивании для типизированных переменных происходит принудительное приведение к типу.
- Bash, cmd — не найти разницу
Да и JS стал таким чувствительным к битовым хакам примерно с появлением "дерзкого" V8, потом остальные стараются догнать (кто не верит — ставьте виртуалку с IE6 и проверьте).
Если вы привыкли видеть сквозь синтаксис Javascript макроассемблер, то конечно ни чего необычного. Просто обычно вопросы оптимизации замыкаются на языка и библиотек (for-of vs .forEach(), DOM vs jQuery и т.п.). smile)
Умные компиляторы или VМ способны производить такую оптимизацию превращая за кулисами операцию получения остатка в побитовую операцию и обратно. По факту последняя V8 Javascript VM (не реализовано в NodeJS) делает именно так.
NodeJS 11 вышла и там апдейт V8 до последней версии, может уже и реализовано.
Пока не заглянул в MDN, не смог понять, что Math.clz32 возвращает количество лидирующих нулевых битов в 32-битном двоичном представлении числа.
Функция copy
(та, которая использует Float64Array
) иногда копирует данные неправильно. Даже если длина массива делится на 8. Это хороший пример того, как "умная" оптимизация может приводить к появлению трудно обнаруживаемых ошибок.
Под спойлером — пример массива, который копируется неправильно. Но я предлагаю перед тем, как заглядывать туда, попробовать найти проблему самостоятельно.
let input = new Uint8Array(8).fill(255);
let output = new Uint8Array(8);
copy(input, output);
console.log(input, output);
При декодировании данных как Float64
, получается значение NaN
. Это единственное значение, у которого есть несколько различный представлений (хотя значения +0
и -0
выглядят как равные для операторов ==
и ===
, они являются различными). Согласно стандарту, в JavaScript все значения NaN неразличимы:
The Number type has exactly 18437736874454810627 (that is, 264−253+3) values, representing the double-precision 64-bit format IEEE 754-2008 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic, except that the 9007199254740990 (that is, 253−2) distinct “Not-a-Number” values of the IEEE Standard are represented in ECMAScript as a single special NaN value. (Note that the NaN value is produced by the program expression NaN
.) In some implementations, external code might be able to detect a difference between various Not-a-Number values, but such behaviour is implementation-dependent; to ECMAScript code, all NaN values are indistinguishable from each other.
При кодировании в байты, скорее всего, будет использоваться не то представление NaN, которое было в исходном массиве (стандарт не указывает, какое именно нужно использовать, так что результат зависит от реализации), поэтому скопированные данные не будут совпадать с исходными.
Нутром чуял, что нельзя байтовый массив безболезненно в массив Float-ов сконвертить… Не знал почему, но со времён школы и QBasic-а натыкался на порчу данных при этом процессе.
Кротовые норы в JavaScript