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

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

Отличная статья! А нет ли докладов, других статей от Вячеслава на русском?
На русском у меня есть, но мало

Из более-менее свежего: http://mrale.ph/blog/2015/04/12/jsunderhood.html

Из старинного есть:

http://www.youtube.com/watch?v=tCG0aPNvkTs
http://s3.mrale.ph/TechForum2012.pdf
а существует какой-нибудь аналог EXPLAIN что бы посмотреть, какие оптимизации применились к коду?
Для разных машин существуют разные флаги — чтобы посмотреть на сгенерированный код. Тул IRHydra, который я написал, позволяет в более-менее удобоваримом виде изучать информацию, которую умеет дампить V8-овый Crankshaft. Что-то мне подсказывает, что и для других машин есть подобные способы, но разрабочики их не афишируют :)
А какой смысл в таких глубоких оптимизациях в JS? Ну понятно что какие-нибудь базовые и дешевые jump-to-jump или peephole делать можно и должно, но трансляция JS в native code представляется overkill. Вычиcлительная модель и типы данных как-то совсем далеки от модели и типов native code.

Не знаю как кому, но мне вот представляется что гораздо продуктивнее было бы иметь возможность сделать что-то типа inline C или Dart islands в JS:

"C" int ray_tracer(int a) {
... C code, compiled into native assembly ...
}
// normal JS
var r = ray_tracer(42);


Как-то представляется что это позволит получить более предсказуемые результаты. И уж точно исполняться будет эффективнее.

Да и сократить размер всего этого хозяйства опять же. А то вот получается что весь мой Sciter который есть фактически встраиваемый browser включающий JS superset по размеру меньше чем одна V8.dll ( sciter32.dll — 3.7mb, V8.dll — 4.7mb ).

Смысл в глубоких оптимизациях JS очень простой — люди пишут JS-код, надо чтобы он исполнялся эффективно.

Если бы люди JS не писали, то его никто бы и не оптимизировал.

А так выбора нет :)

В JS скорость парсинга одинаково важна как и скорость исполнения. Скорость eval() опять же. Т.е. баланс — должной оптимизации и скорости сборки того же C или C++ не добиться — дорого во всех смыслах.

По идее JS в UI это клей связывающий выход одной native функции со входом другой. Т.е. в принципе JS это язык описания event routing / dispatching. Смысла JITить какой-нибудь click event handler немного если он срабатывает ровно один раз за время жизни страницы. А вот парсить их надо быстро.

Понятно что в условиях browser sandbox JS это единственная опция написать какой-нибудь ray tracer. Но вот эта единственность и есть основная проблема.

Представляется что гораздо эффективнее было бы иметь тот же embedded С например или вообще абстрактную bytecode VM типа JavaVM которую можно кормить compiled bytecodes + удобный interop из JS с этим хозяйством.

Т.е. путь того же Python — простой bytecode interpreter + удобная возможность встраивания native (или тех же bytecodes близких к native) для функций которым нужен CPU на 100%.

Как-то такое решение представляется более грамотным с инженерной точки зрения.

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


Да скорость парсинга важна. Только ведь скорость парсинга тут вообще сбоку припёка — наличие, отсутствие оптимизирующего компилятора не влияет на нее.

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

Конечно, не все так просто. Оптимизации могут полагаться на разного рода информацию собранную во время неоптимизированного исполнения — поэтому нужно так реализовывать сбор этой информации, чтобы производительность не страдала. Можете быть уверены — разработчики JS VMs все это стараются учесть и найти баланс между пиковой производительностью и startup.

Т.е. путь того же Python — простой bytecode interpreter + удобная возможность встраивания native (или тех же bytecodes близких к native) для функций которым нужен CPU на 100%.


Python — это очень странный пример. Люди ведь совсем даже не поголовно довольны его производительностью — существует целый ряд различных компляторов/реализаций, которые стараются решить проблему производительности. Начиная от Cython и заканчивая PyPy. DropBox вот тоже свой собственный JIT пилит.

А так… создавать чисто интерпретирумый язык по своей изначальной природе


Так уж сложилось, что люди которые JavaScript создали, и люди, которые «героически преодолевают его интерпретируемость», это совершенно разные люди.
Про Python и его производительность.

Когда кто-то реально упирается в его производительность то пишут native extension. NumPy как пример.
То же происходит и в JS когда он работает в среде node.js. В обоих случаях есть относительно простой механизм встраивания native code.

Собственно я про это и говорю. Ну не надо писать ray tracers в JS. Гораздо продуктивнее было бы иметь возможность встраивания чего-то что лучше ложится на регистры и архитектуру CPU.

Сложно сказать одинакова ли важна скорость парсинга как и скорость исполнения. По дву причинам, во-первых многие тяжелые веб приложения висят в памяти достаточно долго и скорость исполнения начинает выигрвывать заметно. Например, у меня вкладка с Google Inbox открыта постоянно. Если даже парсинг+компиляция заняли несколько секунд то выгодна от такой оптимизации давно себя окупила. Вторая причина в том что JS можно скомпилировать один раз и хранить в кэше, и проверять потом по хэшу. Сайты особенно с тяжелым JS не часто его обновляют (максимум несколько раз в день). Сколько раз в день пользователь обновляет в день условный Facebook? И сколько раз Facebook обновляет JS? В таком случае дорогая оптимизация тоже себя окупает. Ну а скоро и WebAssembly подъедет.
вопрос о том, насколько Facebook/Inbox/etc выигрывает от того, что его код оптимизировали, решается отнюдь не всегда в пользу оптимизаций :)

Ну а скоро и WebAssembly подъедет.


и люди сразу перепишут UI Facebook на С++ вместо JS :)
Выигрывает или проигрывает пользователь. Если оптимизация действительно оптимизирует то для меня это лучше, значит Inbox батарейку меньше ест.
Когда я говорю «насколько Facebook/Inbox/etc выигрывает», я как раз и имею ввиду «выигрывает по производительности», т.е. становится быстрее.

С кодом всяких разных web-приложений все очень не просто — много полиморфизма, сложно оптимизировать, классические оптимизации рассчитанные на мономорфный код часто не окупаются.
Полиморфизм тоже можно компилировать (см большинство компилируемых языков). Если отделить ООП от использования объектов как dictionary то большинство объектных структур будут вполне статисческими в приложении которое по несколько часов работает.
Полиморфный код можно, конечно, компилировать, но это отнюдь не так просто, как вам кажется — если хочется добиться, действительно быстрого вызова методов. Это только в С++ у вас есть простые явные vtable-s и все просто, уже в Java начинаются всякие интересности если хочется сделать быструю реализацию интерфейсных вызовов (поищите в интернете статьи о реализации invokeinterface).

большинство объектных структур будут вполне статисческими


Это действительно так (хотя и здесь есть свои тонкости). Однако интерес тут представляет не сами объекты, а то, что с ними происходит… Для оптимизации методов, которые работают с этими объектами надо видеть статичность статичность метаструктуры, если можно так выразится. Выше в тексте статьи я приводил уже пример с

function make(x) {
  return { f: function () { return x } }
}
var a = make(0), b = make(1);


здесь не всякая VM может увидеть, что a и b имеют одинаковую метаструктуру — V8, например, не может.
А обо что именно спотыкается V8 (на замыкание с объектом?) и как упростить пример чтобы он узрел?
Если мы хотим оптимизировать

function f(x) {
  return x.f() // (*)
}

f(a);
f(b);


то V8 спотыкается о то, что она при отслеживании метаструктуры рассматривает функции как нечто неделимое, т.е. она будет считать, что вызов в точке (*) полиморфный. А если бы она учитывала, что функция это тело+контекст, то было бы лучше.

Чинится очень просто: отказом от использования замыканий таким образом на горячих путях.

function Classic(x) {
  this.x = x;
}

Classic.prototype.f = function () { return this.x; }

function make(x) {
  return new Classic(x);
}
var a = make(0), b = make(1);


Но это только один пример…

Можно рассмотреть полиморфизм другого толка:

function A() { this.x = "a"; this.a = 0; }
function B() { this.x = "b"; this.b = 0; }

var a = new A(), b = new B();

function use(o) {
  return o.x;
}

use(a);
use(b);


Как скомпилировать use(o) во что-то умнее чем (псевдокод):

function use(o) {
  if (o.hiddenClass === ClassA) {
    return o.x  // load from some fixed offset
  } else if (o.hiddenClass === ClassB) {
    return o.x  // load from some fixed offset
  } else {
    return /* either generic load or deopt */
  }
}


вопрос открытый
Вячеслав, скажите, как вы оцениваете перспективы emscripten?
Я не большой поклонник компиляции C++ в JS. Если WebAssembly получит распространение и поддержку во всех браузерах, то emscripten станет не нужен, потому что для clang есть уже экспериментальный backend, который умеет выдавать WebAssembly.

Вопрос нужен ли нам WebAssembly я оставлю за рамками — пусть на него отвечают те, кто этим WebAssembly занимаются.
НЛО прилетело и опубликовало эту надпись здесь

Никогда, теперь есть WASM.

НЛО прилетело и опубликовало эту надпись здесь
Есть такое мнение с www.forth.org.ru/news от ас
WebAssembly поддержит Форт
Цитата:
github.com/WebAssembly/design/blob/master/AstSemantics.md пишет: Multiple return value calls will be possible

— это приниципиальный момент в возможности поддержки Форта в этой новой виртуальной машине.

Expression trees offer significant size reduction by avoiding the need for set_local / get_local pairs in the common case of an expression with only one, immediate use.

— ну а это уже неприкрытый Форт Инфиксные выражения исходного языка, которые парсятся в expression trees, рекурсивно разворачиваются при компиляции, и полученный код работает со стеком последовательно без трактовки их в качестве именованных локальных переменных. Так же как locals в Форте не выполняют load/store, в/из стека куда-то ещё, а могут использоваться напрямую и «анонимно» в последовательном вычислении.

Ср. www.forth.org.ru/~ac/rationale/FORTH.TXT

P.S. Чем это полезно в плане производительности и как это лучше «утилизировать» в JS?
Понятия не имею. Меня про asm.js вообще бесполезно спрашивать, я считаю, что это совершенно бесполезная (или даже вредная) «технология» и мир без нее будет лучше.

rumkin внизу правильно замечает, что на замену asm.js пришел WASM… Я, впрочем, не большой фанат WASM в его текущей форме.
НЛО прилетело и опубликовало эту надпись здесь
В WASM мне не нравится то, что они не поддерживают ничего, что сделало бы этот самый WASM для меня полезным. Меня не интересует возможность запускать C++ браузере. Меня интересует возможность запускать динамические языки в браузере отличные по семантике от JavaScript. Причем я хочу это делать с минимальными издержками. Например, я не хочу компилировать мой написанный на C++ GC в WASM — я хочу полагаться на GC, который уже в браузере есть… при этом, кстати, GC не так-то и просто скомпилировать, потому что я хочу ходить по стеку в поисках корней, а WASM такого не позволяет. Аналогично, я не хочу писать свой JIT — я хочу полагаться на JIT, который в браузере есть — со всеми его мощьными фичами типа адаптивной спекулятивной оптимизации.

Поэтому от WASM я впервую очередь жду таких фич как интеграция с GC и JIT.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
В таких случая можно баги файлить на GitHub или посылать мне описания непонятных деоптимизаций. Я разьясню деоптимизацию — и куда-нибудь запишу ее как пример для будущих поколений.

У меня тут спор возник с одним разработчиком. По поводу применения const с объектами.
Он утверждал, что смысл const это просто заменить данный идентификатор значением константы. И его нельзя использовать с объектами.
Это может быть верно для компиляции Си или Ассемблера, но в мире JS имхо делается фиксированная привязка на область памяти с данным значением. И в моем понимании const скорее означает "я не хочу присвоить данному идентификатору что-то еще", нежели какие-то конкретные детали реализации.


Вот рассудите, как человек знающий потроха реализации JS.


ЗЫ. Про то, что свойства объекта-константы все равно можно менять, я знаю. Речь не про это.

Дык это же новый js-holywar. Раньше про ; и , споры были. А теперь вот про const :) Правда если рассматривать const как "заменить данный идентификатор значением константы", то он с этой задачей справляется довольно паршиво.

Ну это всего лишь ментальная реализация в голове одного из разработчиков. Ждем ответа от mraleph

Он утверждал, что смысл const это просто заменить данный идентификатор значением константы.


Разумеется в JavaScript семантика у const отнюдь не такая. Самый наглядный пример того, что это все-таки неизменяемая привязка переменной к значению:

(function () {
  console.log(f()); // prints <?>
  const X = '<!>';
  console.log(f());  // prints <!>

  function f() {
    try {
      return X;
    } catch (e) {
      return '<?>'
    }
  }
})();


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

V8 не использует, к сожалению. А JSC, например, использует. Код

const X = 10;
function bar() { return X * X }


компилируется в return 100 по сути дела
Зарегистрируйтесь на Хабре, чтобы оставить комментарий