Как стать автором
Обновить

Комментарии 62

Я правильно понимаю, что, теоретически, это может дать прирост и в интерпретаторах других браузеров?
И практически. Весь этот текст можно сжать до фразы «Пишем как можно более типизированно» (типизированные массивы, не перегруженные функции, статический набор полей у объектов).
Тот же TypeScript кроме сахаров помогает писать быстрый код за счет статической проверки типов(именно помогает, а не делает).
Почему JavaScript изначально такой? Как хорошо было бы, если бы он был типизированным.
Так исторически сложилось. (с)
НЛО прилетело и опубликовало эту надпись здесь
Есть Dart…
Он есть, но его и нет. Точнее он пока в стадии «Посмотреть и поиграться», а JavaScript есть уже сейчас и с ним надо что-то делать.
Ну как, всё, кроме HTTP-клиента/сервера и сокетов для нативной части реализовали. Библиотека обширная, интероп с JS умеет. Так что можно использовать.
К сожалению, это только хром. Трансляции в JS он, конечно, поддается, но с костылями. Например, попробуйте транслировать генераторы в JS кроме них есть еще много других примеров. Dart язык очень хороший, но распостраненность — это его огромная проблема (которая в ближейшие лет 5 точно не решится).
dart2js конечно хорошо, но в Dart есть такие штуки не свойственные для JavaScript и dart2js делает их костылями. Нет генераторов, нет Image, нет декларативных классов, нет честных изолятов (изоляты воркерах не могут трогать DOM). Ты же сам прекрасно знаешь их фундаментальные различия :)

Dart это очень хорошо, но я бы пока не стал его использовать в продакшене.
> К сожалению, это только хром. Трансляции в JS он, конечно, поддается, но с костылями.

Байка. Рекомендую почитать вот этот пост Сета Лэдда: blog.sethladd.com/2012/10/9-dart-myths-debunked.html
Вот AS3 тоже ECMAScript, но с типами — совсем другая песня :)
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Сначала мы дадим вам возможность сохранять в переменную значение любого типа.

Потом начнем бить по рукам, чтобы вы этого не делали.
НЛО прилетело и опубликовало эту надпись здесь
Это не упрек, это на тему, что, если плохо придумано сначала, то надо сломать и переделать.
«Плохо» — «кому?», «с какой целью?», «в каких обстоятельствах?»
Не стоит делать абсолютизированных обобщений без уточнения контекста — это верный путь к совершению ошибки ;-)
Предельная скорость выполнения не всегда и не везде нужна, бывают и другие «кейсы».
Не бить по рукам, а всего лишь толсто намекать, что использование этой опции имеет очень ненулевую цену…
Местами не правильно перевели, например:
1. Массивы бывают двух видов — хранящие 31 битные инты и указатели на другие обьекты(этой разницей бит и кушается) и только числа двойной точности.
Чем плохо — был массив с двумя интами, под него выделилилось место для хранения 4х. Вы добавили double и массив перепаковался в типизированный. Старые данные уходят в GC. Добавили строку — обратная перепаковка. Ваш double теперь доступен через указатель, что сильно медленно

2. Полиморфные функции не медленее чем мономорфные. Это буду две разные функции, каждая из которых потребует своей оптимизации( 0.5-1мсек на функцию, это не мало). IC(inline cache) может чуть раньше выбрать какую он будет использовать.

3. Огромная Сила в inline функциях! Жалко что хром не инлайнит функции которые требуют переключение контекста. Да, именно в JS функциональное программирование реально быстрее чем старый добрый ООП.

Вообще профилирование через d8 или запуск хрома с флагами чтука относительно бесполезная. Позволяет оттюнить только синтетические примеры и реально узкие места.
1. По типу содержимого быстрые элементы бывают трех видов и трансформируются в одном направлении от менее общих к более общим: smi (small integer) -> double -> object. Бывают еще дырявые (holey) и непрерывные (packed). Еще бывают медленные элементы — представляются словарем.

2. Рекомендация соблюдать мономорфизм относится не к функциям, а к отдельным операциям, например, оператору умножения, оператору [] или вызову метода. Операции работают быстрее, когда они мономорфны — выполнятся над объектами одного и того же типа/скрытого класса. Как вы определяете «полиморфную функцию»?

3. V8 инлайнит функции, которые требуют переключения контекста на ia32 (Mac, Windows), но не инлайнит на x64/arm. Что касается функционального программирования, то зависит от того какие именно паттерны мы сравниваем. В большинстве случаев ООП основанное на связке constructor + prototype chain имеет больше шансов быть хорошо заоптимизированным. Можно почитать мой блог о том, какие именно проблемы возникают если опираться на замыкания: mrale.ph/blog/2012/09/23/grokking-v8-closures-for-fun.html

А собственно кроме узких мест ничего никогда тюнить и не надо. Все советы по оптимизации бесполезны в 99% случаев, пока вы не наткнетесь на случай попадающий в злые 1%, где надо костьми лечь но выжать все что можно.
Вы давно видели 32х битные Маки?
Долго копал и свой код, и исходники v8 чтобы понять как можно заставить заинланиться некоторые мои функции (например математические).
Но у вас в блоге заметил странную магию с «use strict», который не дает захватывать контекст.
function sk(x, y) { // x and y are not context allocated "use strict"; return arguments[0] * arguments[1]; }
Можете эту магию прокоментировать, и вообще дать пару разъяснений про strict mode. Это лексический режим, или оно влияет и на генерируемый ассемблер?
> Вы давно видели 32х битные Маки?

Chrome на Mac по сей день 32-битный

omega ~ ∳ file /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome 
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome: Mach-O executable i386


64-битная сборка только на Linux используется AFAIK

> Но у вас в блоге заметил странную магию с «use strict», который не дает захватывать контекст.

В данном конкретном примере "use strict" разрывает связь между arguments и формальными параметрами. Это убирает создание контекста и позволяет инлайнить этот код (если функция создает контекст, то такую пока V8 не инлайнит).

Strict mode описан в стандарте: es5.github.com/#C

> как можно заставить заинланиться некоторые мои функции (например математические).

Если вы набросаете примеры кода, то можно будет обсудить почему что-то не инлайнится и какая будет польза от инлайна :-)
пример банален
var vector = { add: function (a,b){ return [a[0]+b[0],a[1]+b[1]] } } //и где-то в совершенно другом месте vector.add не инлайнится При этом, если бы я использовал просто функцию vector_add в глобальном scope - инлайн был бы возможен.
Кстати, пользуясь, случаяем хотел бы уточнить насчет хранения двордов. Что же лучше — массив(который будет создан уже перепакованным?) или хэш с .x\.y(который тоже вообще бинарная структура вроде)?
эта функция не заинлайнится из-за array literal даже если будет в глобальном scope: code.google.com/p/v8/issues/detail?id=1322

если vector существует в единственном числе, то тут разницы между глобальным и не глобальным scope не должно быть.

Хранение 32-битных величин сложный вопрос. На ia32 те из них которые не влезают в smi (31-bit signed integer) превратятся в полновесные числа с плавающей точкой. Зависит от многих факторов: как много тех кто не влезает в 31бит, как они будут использоваться и т.д. Int32Array может оказаться оптимальным в некоторых случаях.
Можно поподробнее про силу инлайна?
var a = [1, 2, 3, 4];

// Если выполнять это честно, то функция будет вызвана на каждом элементе - это дорого
// Но такая запись более читаема
a.forEach(function (item) {
    console.log(item);
});

// Такие экспрешены инлайнится (где это возможно) в стейтмент for () {}
for (var i = 0; i < a.length; i++) {
    console.log(a[i]);
}
только вот такой оптимизации V8 не делает пока :-)
В общем самое вкусное покуда не инлайнится.
А тогда каким образом это работает быстро? Не над каждым же элементом честно функция вызывается.
следует определить понятие быстро :-)

над каждым элементом функция честно вызывается. в зависимости от того, что функция делает цена этого вызова может быть заметна, а может быть амортизированна и не очень заметна.
быстро всмысле сопоставимо со стейтментом for
ну по сравнению с циклом for если зарядить на длинном массиве, много-много раз пустую функцию то будет видна цена вызова.
Кстати, а что мешает заинлайнить такой цикл? (понятно, что мой пример был предельно тривиальный)
ничто не мешает, просто не реализованна нормальная протяжка констант. а без протяжки констант на одном локальном type feedback тут далеко не уедешь из-за полиморфизма точки вызова внутри цикла, который внутри forEach.
Понятно, как обычно дело времени :)
Другой вопрос, а что мешает заинлайнить пустые функции? Точнее привести их к виду — не вызывать, все undefined возвращать. Ведь функции — immutable и тело у них самом собой появиться не может.
А что нужно делать вместо удаления элементов из массивов?
Либо использовать хэши, либо закрывать дырку в массиве, либо ответить на вопрос зачем удаляем.
А вы сами используете массивы, только когда не предполагается удалять из них элементы?
Я использую массивы для разных целей.
Но никогда не оставляю в них дырки.
Тут лучше обойтись хэшом.
А удаление с конца массива тоже оставляет дырки в его внутреннем представлении? Где можно подробнее почитать на этот счет?
когда говорится, что лучше не удалять элементы, имеется ввиду удаление многих элементво из середины оператором delete.
вы уверены про delete? он не удаляет элемент, а прописывает undefined
splice удаляет элементы из середины
мы, видимо, по разному определяем понятие удалить.

прежде всего delete не прописывает undefined, он удаляет свойство полностью оставляя вместо него дырку. разницу просто увидеть на примере кода:

a = [1];
b = [1];
Array.prototype[0] = 42;
console.log(a[0] + " " + b[0]);  // 1 1
delete a[0];
b[0] = undefined;
console.log(a[0] + " " + b[0]);  // 42 undefined


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

splice же, который удаляет и сдвигает элементы не оставляет после себя дырок, поэтому безобиден в данном отношении. Следует, впрочем, всегда помнить, что он имеет линейную сложность по количеству сдвигаемых элементов.
признаю, был не прав, вспылил :)
Можно юзать как стек или очередь.
А что нужно делать вместо удаления элементов из массивов?
Удалять-то можно; а нужно не оставлять дырки.

Поэтому вместо «delete arrayName[index]» следует использовать «arrayName.splice(index, 1)», когда это возможно.
я бы сказал, что это спорная рекомендация и они совсем не эквивалентны. все конечно же зависит от конкретного кода, splice, например, линейная операция по количеству сдвигаемых элементов. в каких-то случаях может лучше arr[i] = null; делать (хоть оно и не эквивалентно delete)
Я как раз и использую splice. Однако он медленнее, чем просто устанавливать значение равным null, либо делать delete. Но ведь это тоже не всегда приемлимо. Было бы хорошо услышать реальные примеры задач и те цифры, которые мы выигрываем/проигрываем, используя один или другой метод.
Все зависит от конкретной задачи, на самом деле. Каждый код очень индивидуален.
Замечательная статья! Сразу вспомнились все абстракции с jQuery и JS и так же конечно JavaScript в нашем случаем с VM.
Спасибо за перевод! Собрал d8, но похоже это было самой простой задачей)

--trace-opt, --trace-deopt — ничего не выводят, и так и эдак пробовал. Есть какой-нибудь код, который гарантировано выведет инфу?
--trace-bailout — похоже выпилили. В Changelog (Version 3.13.4): Print reason for disabling optimization. Kill --trace-bailout flag. По крайней мере сейчас, ругается на несуществующий флаг.
--trace-deopt действительно выпилили.

Что касается --trace-opt/--trace-deopt то они ничего не выводят если ничего не оптимизируется/деоптимизируется. Попробуйте какой-нибудь горячий цикл.

НЛО прилетело и опубликовало эту надпись здесь
> добавить оптимизации таких случаев в компилятор же?

Вы действительно считаете, что то, что тривиально человеку — машине тоже тривиально? ;-)
> В частности, оптимизирующий компилятор пропускает любые функции, содержащие блоки try/catch
Интересно, значит ли это, что jQuery толком не оптимизируется? или для него сделано исключение?
Просто у меня стойкая ассоциация в мозгу, что
jQuery('...') === try { jQuery('..') } catech(e) {};
> Инициализируйте все объекты в конструкторах, чтобы они как можно меньше менялись в дальнейшем.

Спасибо, погуглил, ускорил стандартный setTimeout в node.js на 15-20%.
github.com/joyent/node/issues/4182
Хе, в master-бранче уже есть примерно такое же :)
Таки получилось ускорить в 1.5 раза.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории