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

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

Send message
> а зачем, собственно, придумывать новую сущность Future, когда можно было использовать уже давно работающий в js Promise API

Во-первых, Dart совершенно самостоятельный язык, зачем ему смотреть на то как в JS что-то сделано?

А, во-вторых, посмотрите на даты релиза Dart и Promise API в JS. Внезапно обнаружится, что Dart появился где-то в 2011 (начал разрабатываться где-то в 2010м) — и с самого начала имел Future, и Promise API в JS еще даже дизайнить не начали в то время.
надо было мне сказать, что ты будешь этот конкретный пост переводить, я бы justboris сказал, что кто-то уже переводит. а так ты меня спросил, можно ли статьи переводить и репостить — я сказал можно; но у меня это как-то в голове не сложилось 1+1, что ты эту конкретную статью будешь переводить. извиняюсь.
JIT умеет инлайнить функции, но вызов функции должен быть мономорфным — те вы должны вызывать всегда одну и ту же функцию из данного места.
Да ну хотя бы эта картинка

Эта картинка для объяснения "почему". Можно без объяснения "почему", просто сказать "делай как я! будет счастье" (и между прочим говорили начиная с 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 в текстовом редакторе поправить. Еще та черная магия!

Не говоря о том, каким образом получена эта информация — копание в ассемблерных листингах явно не моя сильная сторона, как думаю, и многих других разработчиков, особенно таких высокоуровневых языков вроде 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>[];  

В Dart 2 состоит из следующих компонентов:


  • серьезно переработанная система типов (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; для создания полноценного мегаморфизма.
Если мы хотим оптимизировать

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 */
  }
}


вопрос открытый

Information

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