> а зачем, собственно, придумывать новую сущность Future, когда можно было использовать уже давно работающий в js Promise API
Во-первых, Dart совершенно самостоятельный язык, зачем ему смотреть на то как в JS что-то сделано?
А, во-вторых, посмотрите на даты релиза Dart и Promise API в JS. Внезапно обнаружится, что Dart появился где-то в 2011 (начал разрабатываться где-то в 2010м) — и с самого начала имел Future, и Promise API в JS еще даже дизайнить не начали в то время.
надо было мне сказать, что ты будешь этот конкретный пост переводить, я бы justboris сказал, что кто-то уже переводит. а так ты меня спросил, можно ли статьи переводить и репостить — я сказал можно; но у меня это как-то в голове не сложилось 1+1, что ты эту конкретную статью будешь переводить. извиняюсь.
Эта картинка для объяснения "почему". Можно без объяснения "почему", просто сказать "делай как я! будет счастье" (и между прочим говорили начиная с 2010 года, я сам помнится говорил пару раз).
Вот этот вывод например требует нехило так разбираться в предмете.
С этим я в принципе согласен. Нужно иметь хоть какой-то опыт чтения профилей, с учетом низкоуровневых деталей. Однако у меня есть такой пример: когда было дело и я общался с нодовцами по поводу производительности, то они обычно достаточно быстро просекали, как читать профили.
Я, кстати, об этом и в посте написал — что было бы очень хорошо если бы V8 умела все эти вещи объяснять понятным языком. Например, не KeyedLoadIC_Megamorphic, а что-нибудь более понятное человеку менее углубленному в детали реализации.
Ну это все фантазии, к сожалению, из разряда вечерних мечтаний за чаркой среди единоверов. "Ах вот бы было хорошо, если бы!"
Я бы может быть тоже хотел проснутся одним морозным утром и обнаружить, что JS скукожился. Но одним Rustом тут дело не решить, к сожалению. Нужен глобальный выбор — на чем хочешь, на том и пиши, а для этого нужен вменяемый compilation target. (… и все равно в конце-то внезапно обнаружится, что писать на все том же ЖС будут порядочно.)
Я не пытаюсь Rust отодвинуть: у меня две причины, одна корыстная, одна не очень.
Во-первых, причина простая: я не понимаю зачем писать такие вещи на Rust, если можно на JS (который я не особо-то и люблю). Ну пусть бы написали что-то осмысленное и большое, но такую мелкую штуку смысла на мой взгляд не имеет никакого. Только из любви к языку, ну пусть тогда так и пишут — а не наводят тень на плетень.
Во-вторых, причина корыстная: в WASM компилировать по нормальному можно только языки типа C/C++/Rust, для всего остального надо за собой тащить собственный рантайм — GC, JIT, etc — скомпилированный в тот же WASM. Я считаю, что это неоправданные издержки. Поэтому получатся, что JS до сих пор единственный нормальный целевой язык для компиляции высокоуровневых языков (хотя он тоже не сахар, но хотя бы свой JIT/GC/exceptions, тащить не надо). А значит нужно поддерживать огонь под JS производительностью со всех возможных направлений.
(Что инетерсно тот товарищ, который source-map делает он даже написал собственный аллокатор памяти для WASM — потому что встроенный в Rust был слишком большой… ну у меня даже слов нет.)
Не говоря о том, каким образом получена эта информация — копание в ассемблерных листингах явно не моя сильная сторона, как думаю, и многих других разработчиков, особенно таких высокоуровневых языков вроде JS.
Ни для каких оптимизаций не пришлось копаться в ассемблерных листингах, все были очевидны либо из профиля, либо из анализа алгоритма. На ассемблерный листинг я там в одним месте только сослался и то, только ради дополнительной информации читателю.
и для меня некоторые улучшения просто черная магия.
Какие именно? Вот допустим пишете вы бэкэнд на C# или Java — для вас мономорфный/полиморфный код имеет точно такое же значение с точки зрения перформанса! Это никакая не черная магия.
Для достижения похожего результата в случае с JS потребовался эксперт с огромным опытом, который потратил очень много времени и сил на оптимизацию.
Я потратил ~2 часа утром на диване перед работой, чтобы оптимизировать код. А затем я потратил гораздо больше времени, чтобы написать длинный пост с пошаговым объяснением.
Глянул, там говорится, что для компиляции rust нужно поставить компилятор rust
Там одним компилятором rust не обойтись, там еще всяких самописных тулов надо ставить.
Все оптимизации достаточно примитивны — к тому же с течением времени необходимость в оптимизациях из группы №3 должна сойти на нет.
Единственная действительно спорная оптимзация состоит в использовании typed arrays руками — потому что она делает код действительно сложно читаемым. Но опять же — эту проблему можно решить просто добавив правильные фичи в JavaScript — а не приглашать людей переписывать их код на Rust.
Да и почему предполагется, что цена поддержки Rust и WASM нулевая? Это совершенно отдельный язык и экосистема! Автор Вы посмотрите на список вещей, которые нужно установить, чтобы обновить ту часть source-map, которая написана на Rust.
Вот в том-то и дело, что Dart 1 никогда и не был задизайнен, как статически типизированный язык. Все странности языка перестают быть странностями, если это знать и воспринимать его именно таким образом.
Решение становится гораздо менее странным, если воспринимать Dart 1, как динамически типизированный язык. Допустим приехал вам JSON [1, 2, 3], вообще-то он List<dynamic>, но вы допустим как-то знаете, что он на самом-то деле List<int>. В Dart 1 вы его спокойно можете передать куда-нибудь, где ожидают List<int>. В Dart 2 вам его сначала надо бы преобразовать, например через list.cast<int>()
В Dart 1 ["1", "2"] имеет тип List<dynamic>, а List<dynamic> является подтипом List<int>. Это было сделано, чтобы можно было легко смешивать полностью динамический и типизированный код. dynamic был одновременно и top и bottom решетки типов, т.е. в коде внизу оба присваивания были валидны и в runtime и в compile time
List<dynamic> a;
List<int> b;
a = <int>[];
b = <dynamic>[];
серьезно переработанная система типов (strong mode, которая включает в себя generic methods, локальный вывод типов, изменения в семантике dynamic и void);
int больше не является целым числом произвольного размера (aka bigint), а ограничен 64-битами;
мелкие изменения в языке (optional new/const, async functions start synchronously);
большое число новых методов в core библиотеках (см CHANGELOG.md);
важное инфраструктурное изменение: введение Общего Фронтенда и Kernel, унифицированного формата представления и сериализации Dart AST — все тулы будут использовать один и тот же парсер Dart написанный на Dartе, даже VM.
Когда запилят eval? Ведь существуют не только мобильные приложения и фротенды. Так, из-за этого отсутствует вменяемый repl в Dart SDK.
eval можно через service протокол делать. Есть сторонние прототипы replов на основе этого.
Добавят ли возможность ждать завершения выполнения async кода в sync функциях?
По нормальному это добавлять тяжко (по разным техническим причинам), но в 2.0 добавили dart:cli с методом waitFor, который синхронно ждет завершения Future. Я думаю после 2.0 будем смотреть на "многопоточность" и асинхронность в целом более пристально, потому что людям хочется чего-то более вменяемого особенно за пределами браузера.
В stagehand не хватает шаблона бекенд+фронтенд
Можно в stagehand репе сделать баг на эту тему или там нарисовать PR :)
Если посмотреть на код, который V8 делает для Typed и Monorphic бенчмарков, то оказывается, что он один и тот же. А производительность почему-то разная. А если переписать код
// Pregenerate objects
for (let i = 0; i < n; i++) {
typed[i] = new Type(i);
}
for (let i = 0; i < n; i++) {
poly[i] = {};
poly[i]["k" + i] = null;
poly[i].x = i;
}
for (let i = 0; i < n; i++) {
mono[i] = {
x: i,
n: i, // Add n to balance memory usage
};
}
то на моей машине внезапно все выравнивается, что намекает на какие-то мистические источники разницы.
На самом деле ваш мегаморфный совсем отнюдь не мегаморфный, потому что в массиве poly встречаются объекты всего двух разных скрытых классов: с быстрыми элементами и с разреженными.
// запускать с --allow-natives-syntax для доступа к %HaveSameMap
let maps = [poly[0]];
for (let i = 1; i < poly.length; i++) {
let found = false;
for (let o of maps) {
if (%HaveSameMap(o, poly[i])) {
found = true;
break;
}
}
if (!found) maps.push(poly[i]);
}
console.log(maps.length); // напечает 2
Происходит это потому, что V8 отделяет числовые свойства (т.е. свойствами с именами "0", "1", "2", ...) от других свойств. Сделано это для ускорения работы масивов.
Вам нужно написать что-нибудь типа poly[i]["k" + i] = null; для создания полноценного мегаморфизма.
то 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 */
}
}
Во-первых, Dart совершенно самостоятельный язык, зачем ему смотреть на то как в JS что-то сделано?
А, во-вторых, посмотрите на даты релиза Dart и Promise API в JS. Внезапно обнаружится, что Dart появился где-то в 2011 (начал разрабатываться где-то в 2010м) — и с самого начала имел Future, и Promise API в JS еще даже дизайнить не начали в то время.
Эта картинка для объяснения "почему". Можно без объяснения "почему", просто сказать "делай как я! будет счастье" (и между прочим говорили начиная с 2010 года, я сам помнится говорил пару раз).
Тут надо повторится, что SpiderMonkey от такого не страдает и V8-товцы обещают подумать и починить.
С этим я в принципе согласен. Нужно иметь хоть какой-то опыт чтения профилей, с учетом низкоуровневых деталей. Однако у меня есть такой пример: когда было дело и я общался с нодовцами по поводу производительности, то они обычно достаточно быстро просекали, как читать профили.
Я, кстати, об этом и в посте написал — что было бы очень хорошо если бы V8 умела все эти вещи объяснять понятным языком. Например, не
KeyedLoadIC_Megamorphic
, а что-нибудь более понятное человеку менее углубленному в детали реализации.Ну это все фантазии, к сожалению, из разряда вечерних мечтаний за чаркой среди единоверов. "Ах вот бы было хорошо, если бы!"
Я бы может быть тоже хотел проснутся одним морозным утром и обнаружить, что JS скукожился. Но одним Rustом тут дело не решить, к сожалению. Нужен глобальный выбор — на чем хочешь, на том и пиши, а для этого нужен вменяемый compilation target. (… и все равно в конце-то внезапно обнаружится, что писать на все том же ЖС будут порядочно.)
Я не пытаюсь Rust отодвинуть: у меня две причины, одна корыстная, одна не очень.
Во-первых, причина простая: я не понимаю зачем писать такие вещи на Rust, если можно на JS (который я не особо-то и люблю). Ну пусть бы написали что-то осмысленное и большое, но такую мелкую штуку смысла на мой взгляд не имеет никакого. Только из любви к языку, ну пусть тогда так и пишут — а не наводят тень на плетень.
Во-вторых, причина корыстная: в WASM компилировать по нормальному можно только языки типа C/C++/Rust, для всего остального надо за собой тащить собственный рантайм — GC, JIT, etc — скомпилированный в тот же WASM. Я считаю, что это неоправданные издержки. Поэтому получатся, что JS до сих пор единственный нормальный целевой язык для компиляции высокоуровневых языков (хотя он тоже не сахар, но хотя бы свой JIT/GC/exceptions, тащить не надо). А значит нужно поддерживать огонь под JS производительностью со всех возможных направлений.
(Что инетерсно тот товарищ, который
source-map
делает он даже написал собственный аллокатор памяти для WASM — потому что встроенный в Rust был слишком большой… ну у меня даже слов нет.)Да, и это работало. А вот попробуйте WASM в текстовом редакторе поправить. Еще та черная магия!
Я, кстати, обновил график WASM vs pure-JS
Ни для каких оптимизаций не пришлось копаться в ассемблерных листингах, все были очевидны либо из профиля, либо из анализа алгоритма. На ассемблерный листинг я там в одним месте только сослался и то, только ради дополнительной информации читателю.
Какие именно? Вот допустим пишете вы бэкэнд на C# или Java — для вас мономорфный/полиморфный код имеет точно такое же значение с точки зрения перформанса! Это никакая не черная магия.
Я потратил ~2 часа утром на диване перед работой, чтобы оптимизировать код. А затем я потратил гораздо больше времени, чтобы написать длинный пост с пошаговым объяснением.
Там одним компилятором rust не обойтись, там еще всяких самописных тулов надо ставить.
Нет, ничего они так не делали изначально. Максимум они сделали:
Все. Никаких других оптимизаций они не делали. Никаких особых проблем с поддержкой и гибкостью от двух этих изменений у них не было.
Я читал ответ, и я с ним не до конца согласен. (Если что, я это одна из двух сторон).
Все оптимизации достаточно примитивны — к тому же с течением времени необходимость в оптимизациях из группы №3 должна сойти на нет.
Единственная действительно спорная оптимзация состоит в использовании typed arrays руками — потому что она делает код действительно сложно читаемым. Но опять же — эту проблему можно решить просто добавив правильные фичи в JavaScript — а не приглашать людей переписывать их код на Rust.
Да и почему предполагется, что цена поддержки Rust и WASM нулевая? Это совершенно отдельный язык и экосистема! Автор Вы посмотрите на список вещей, которые нужно установить, чтобы обновить ту часть
source-map
, которая написана на Rust.Решетка (lattice) — это такая алгебраическая структура.
Решение становится гораздо менее странным, если воспринимать Dart 1, как динамически типизированный язык. Допустим приехал вам JSON
[1, 2, 3]
, вообще-то онList<dynamic>
, но вы допустим как-то знаете, что он на самом-то делеList<int>
. В Dart 1 вы его спокойно можете передать куда-нибудь, где ожидаютList<int>
. В Dart 2 вам его сначала надо бы преобразовать, например черезlist.cast<int>()
В Dart 1
["1", "2"]
имеет типList<dynamic>
, аList<dynamic>
является подтипомList<int>
. Это было сделано, чтобы можно было легко смешивать полностью динамический и типизированный код.dynamic
был одновременно иtop
иbottom
решетки типов, т.е. в коде внизу оба присваивания были валидны и в runtime и в compile timeВ Dart 2 состоит из следующих компонентов:
dynamic
иvoid
);int
больше не является целым числом произвольного размера (aka bigint), а ограничен 64-битами;eval
можно через service протокол делать. Есть сторонние прототипы replов на основе этого.По нормальному это добавлять тяжко (по разным техническим причинам), но в 2.0 добавили
dart:cli
с методомwaitFor
, который синхронно ждет завершенияFuture
. Я думаю после 2.0 будем смотреть на "многопоточность" и асинхронность в целом более пристально, потому что людям хочется чего-то более вменяемого особенно за пределами браузера.Можно в
stagehand
репе сделать баг на эту тему или там нарисовать PR :)то на моей машине внезапно все выравнивается, что намекает на какие-то мистические источники разницы.
poly
встречаются объекты всего двух разных скрытых классов: с быстрыми элементами и с разреженными.Происходит это потому, что V8 отделяет числовые свойства (т.е. свойствами с именами
"0", "1", "2", ...
) от других свойств. Сделано это для ускорения работы масивов.Вам нужно написать что-нибудь типа
poly[i]["k" + i] = null;
для создания полноценного мегаморфизма.то V8 спотыкается о то, что она при отслеживании метаструктуры рассматривает функции как нечто неделимое, т.е. она будет считать, что вызов в точке
(*)
полиморфный. А если бы она учитывала, что функция это тело+контекст, то было бы лучше.Чинится очень просто: отказом от использования замыканий таким образом на горячих путях.
Но это только один пример…
Можно рассмотреть полиморфизм другого толка:
Как скомпилировать
use(o)
во что-то умнее чем (псевдокод):вопрос открытый