Pull to refresh
95
0
Вячеслав Егоров @mraleph

Пользователь

Send message
> однако, type feedback JIT-компилятор способен на это лишь либо после нескольких вызовов участка кода

да, это так… и что?

> т.е. почти, но не лучше и быстрее.

«почти» достаточно часто означает «быстрее не переписывая функцию не получится»

вот посмотрите не график: attractivechaos.github.com/plb/plb-lang.png

LuaJIT2 для кода перемножения двух матриц (в лоб, вложенные циклы) дает код который на 0.1s оказывается быстрее того, что дает GCC. JavaScript версия на V8 оказывается как Java. (если в V8 сделать array bounds check elimination, и убрать interruption check на обратной дуге цикла необходимый для поддержки отладчика, то он LuaJIT догонит)

И код (особенно у LuaJIT2, который bounds checkи не вставляет для ffi массивов и не обязан поддерживать прерывание циклов в отличие от V8) получается ну ровно такой как вы руками напишите на ассемблере. Временные floating point значения на xmm регистрах, инварианты циклов вынесены и т.д.

С Sudoku ситуация менее приятная, но опять же V8 быстрее, чем Mono оказывается, которая вообще-то исполняет более строго типизированный байткод. Впрочем я код Sudoku не смотрел, может там есть какие-то низко висящие фрукты для оптимизации.

> скорость работы любой программы, скомпиленной JIT или AOT компилятором, будет всегда медленнее

нет. не любой и не всегда. JIT-компиляторы опирающиеся на type feedback в той или иной его форме (трассировка, inline cache) способны для горячих мест порождать достаточно чистый код, сравнимый с тем, что дают компиляторы для более строго типизированных языков типа C++ или Java.
SubStack — это James Halliday, человек, а не пароходстартап ;-)
Ну и Locker (как, например, и другая фича V8 — preemption) — это скорее деталь реализации. Нестандартная плюшка, никаким образом не торчащая через pure js api.
Тут я потерял нить разговора если честно :-)

Locker в V8 — это несколько большее, чем обертка на простым мьютексом. Когда человек пишет на С++ он может синхронизировать или не синхронизировать потоки, как его душе угодно. Но в V8 как только ему захотелось использовать V8 из двух разных потоков — он обязан использовать Locker, для того чтобы a) гарантировать эксклюзивный доступ к VM b) гарантировать, что VM правильно инициализует внутренние структуры данных соответствующие потоку.

Если бы даже в C++ были примитивы синхронизации, Locker ими не заменить, он сложнее.
тьфу, CMutex, совсем уже крыша поехала ;-)
> MFC — вообще ни разу не стандарт.

А я что сказал, что стандарт? Я CLocker к тому упомянул, что люди нуждаются в RAII-based велосипедах, которые бы для них управляли локами, чтобы не прострелить себе ногу ручным управлением. А вот если стандарт включает либо примитивы, либо хотябы библиотечку официальных «оберток», то потребность в велосипедах отпадает. Переносимость повышается. Карма очищается и чакры открываются ;-)
Да я не отрицаю, можно и без примитивов. Хотя в том же MFC был CMutex и прочие радости, чтобы минимизировать вероятность прострела ноги при жонглировании с HANDLEами и WaitFor*, ReleaseMutex.

Я тут бы даже примитивы на первое место не ставил, а ставил то, что нет ни формальной семантики, ни хотя бы упоминания чего нибудь связанного с параллелизмом и недетерминизмом в стандарте ECMA-262.
в Algol 68 были par и sem ;-)

Я уж не говорю про специализированные языки типа occam.

Я не отрицаю, можно и без них, но c ними удобнее и безопаснее.
Я бы не стал сравнивать C++ с JavaScript.

С++ близок к железу и стандарт явным образом оставляет многое на усмотрению разработчику компилятора. У меня не под рукой старого стандарта, но новый явным образом описывает модель исполнения как nondeterministic abstract machine.

Ничего подобного в ECMA-262 нет. Есть только заданные на уровне синтаксического дерева правила вычислений. Как их совместить с недетерминизмом параллельного исполнения не совсем ясно.

Учитывая что JavaScript подразумевает достаточно толстый runtime и нетривиальную работу с объектами, легко представить ситуацию когда две программы с потоками будут давать совершенно разные результаты на двух разных реализациях или трудно диагнастируемые баги (вспомним как канонический пример проблемы с double checked locking до появления внятной memory model в Java). Или будет страдать производительность.

Да и без примитивов синхронизации писать код c синхронизациями действительно будет тяжко. В Java на уровне языка есть synchronized методы и блоки + volatile, на уровне библиотеки java.util.concurrent, в C++ (если мы смотрим на C++ до C++11) есть хотя бы деструкторы стековых объектов => паттерн RAII.

В JS будь там многопоточность без поддержки на примитивов синхронизации на синтаксическом уровне пришлось бы делать:
this.mutex.lock();
try {
  this.f(this.x++);
} finaly {
  this.mutex.unlock();
}


волосато…

конечно можно оформить хелпер

function sync(receiver, mutex, f) {
  mutex.lock();
  try {
    f.call(receiver);
  } finaly {
    mutex.unlock();
  }
}

sync(this, this.mutex, function () {
  this.f(this.x++);
});


но все равно мало радости.
Язык JavaScript (ECMAScript) не содержит примитивов синхронизации и не описывает семантику паралельного исполнения. Сравните, например, с Java — в Java Language Specification целая нетривиальная глава посвящена потокам и их семантике: java.sun.com/docs/books/jls/third_edition/html/memory.html

Единственное, что безопасно можно уложить в JavaScript — это share nothing многозадачность, которая в рамках V8 API легко реализуется через изоляты.
уже в ALGOL 60 был нормальный block lexical scoping, С++ (в те времена C with Classes) в середине 80x так же подхватил эту идею.
> что там нужно было специально просить чтобы переменная не вываливалась наружу.

вы про то, что любая переменная не объявленная локально считается глобальной? есть такое. это решение мне кажется достаточно логичным.

кстати можно провести параллель с C — если функцию вызываете без декларации, то только на этапе линковки обнаружится проблема ;-)
в Lua с областью видимости все нормально: local x будет виден в пределах блока, в котором объявлен.
> радуясь тому что с ним можно достичь почти гибкости скриптовых языков с темплейтами и перегрузкой операторов, и при этом выполнять код в 300 раз быстрее?

omg. на дворе 2011 год, люди давно научились неплохо jitовать различные скриптовые языки.

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

for (int i = 0; i < N; i++) s += calc(i, j);


мы выполняем открытую подстановку, а потом CSE. после чего оказывается, что в точке где к s прибавляется результат calc у нас живы: i, j, s, tmp1, tmp2, tmp3 (можно считать на x86 регистры почти кончились), а в оригинальном коде живы i, j, s, a, b.

Может так оказаться, что только a * b элиминировать (живы i, j, s, tmp3) выгоднее.

Важно тут не количество переменных (в IR действительно a * b уже само по себе сидит во временной переменной), важно количество живых переменных в каждой отдельно взятой точке программы и удлиннение промежутков жизни для временных переменных.
> Устранение/Исключение недоступного кода

Недоступный код — unreachable code. Понятие dead code шире — это любой код, который можно удалить не повлияв на результат выполнения програмы. Например, вычисление значения, которое потом не используется нигде. Некоторые бенчмарки грешат тем, что их вычислительная часть по сути dead code.

> А теперь представьте сколько эта оптимизация экономит процессорного времени в рамках большой вычислительной программы.
Устранение общих подвыражений уменьшает количество вычислений, но увеличивает количество живых переменных и тем самым давление на регистры. Поэтому если регистров мало, а выражние простое (стоит меньше чем скажем чтение из памяти), то получается CSE может легко повредить и может надо наоборот делать rematerialization в местах использования.
Я и не сказал, что смогу присвоить строку, я сказал, что obj[11] будет иметь тип string ;-) Ловкость рук и никакого мошенства:

var obj = new Int32Array(10);
Object.getPrototypeOf(obj)[11] = "surpise!";
В JavaScript нет строгой типизации по определению. jslinux «сносно работает» за счет так называемых WebGL Typed Arrays, которые не изменяют систему типов, но имеют четко специфицированную семантику операции индексирования, что позволяет современным JS VM проводить кое какие оптимизации.

Однако, даже если JS VM удается установить, что некий obj является «экземпляром» Int32Array никто не может скажем утверждать, что typeof obj[11] === 'number'. Легко нарисовать пример, когда typeof obj[11] === 'string'.
там ничего не падает, там проблема с тем, что для websocket transport'а не совсем правильно реализована поддержка «сердцебиения» из-за чего закрывается соединение. Если отключить поддержку сердцебиения (а в erlang'овской реализации ее кстати и нет), то все нормально работает:

twitter.com/#!/3rdEden/status/74884695933980673
twitter.com/#!/3rdEden/status/74879359017680896

Information

Rating
Does not participate
Location
Дания
Date of birth
Registered
Activity