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

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

Send message
Никак не связанна. Oilpan вообще особо никак не связан был с Dart (хотя и существовали некоторые теоретические предположения о том, что он может облегчить интеграцию Dart и Blink в вопросах GC).

Dart не будут интегрировать в стандартной сборке, но другие варианты какие-нибудь останутся (за исключением Dartium)?


Не совсем понятен вопрос. Существует всего один Chrome, поэтому не ясно о каких вариантах идет речь.

Dartium останется, потому что пока это единственный способ гарантировать быстрый edit-refresh цикл разработки.
Конкретное значение уже зависит от CPU и т.д.

Вот после некоторых мучений нарисовал тест демонстрирующий проблему с полиморфизмом на FF

jsperf.com/liquidlava-class-system-performance-ff

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

Это решает проблему с «отложенной» инициализацией полей и, как следствие, убирает проблему нестабильной формы объекта, копирование функций решает другую проблему — если у вас есть разные классы наследники, то функция сидящая на прототипе базового класса будет видеть их всех — и как следствие будет полиморфной, даже если каждый отдельный класс наследник будет производить объекты стабильной формы. Если каждый класс наследник пользуется своей копией «базовой» функции — то каждая такая копия мономорфна.
Если взять открыть в чистой вкладке в Хроме и выполнить сначала «Lava method call», а потом «Lava method call (2)», то должна нарисоваться такая картина. Если их выполнить в каком-то другом порядке или допусти выполнить «Lava method call» еще раз после того как «Lava method call (2)» открутится, то «Lava method call» будет такой же медленный как и «Lava method call (2)», потому что type feedback стал полиморфный.

С FF картина интересная, все что я говорю — оно только к V8 относится. Скорее всего в этом микробенчмарке, он (совершенно правильно) понимает что полиморфизма тут быть не может. Ох, если бы только было легко посмотреть в их сгенерированный код, без пересборки jsshell из исходников.
Он влияет только если создать больше одного объекта и у обоих дернуть метод пару раз — иначе незаметно (связанно это с тем, что inline caches проходят через PREMONOMOPRHIC state и это скрывает полиморфизм, второй объект выявляет это — потому что PREMONOMOPRHIC уже пройден и мы находимся в MONOMORPHIC). Посмотрите на Lava method call (2) в моей версии.
Посмотрел сообщения от RubaXa, вспомнил специфику. Там сравнивалось Base.prototype.method.call(this) и __.parent.call(this), где __ указывал на method в потомке, а __.parent на метод в базовом классе, т.е. Base.prototype.method. Причем все это было год назад, до того как V8 научился распознавать f.call. Из-за этого .call не проинлайнивался, и как следствие LICM не выносил LoadFunctionPrototype из цикла. Как следствие каждая лишняя инструкция была на счету и отражалась на результате микробенчмарка. (Плюс еще во втором случае на одно разыменование меньше, все складывается)
В вашем бенчмарке была своя специфика (я правда уже не помню какая :)). В данном конкретном случае специфика немножко другая.

Если вам кажется, что тут я считерил — то напишите свои тесты, быстрее все равно не будет.


Легким движением руки

- NativeParentClass.prototype.method.apply(this);
+ NativeParentClass.prototype.method.call(this);


NativeInstance.method разгоняется на Хроме в 20 раз см jsperf.com/liquidlava-class-system-performance/9

А все почему? Потому что f.apply(this) Crankshaft не распознает. Он всегда умел f.apply(this, arguments) распознавать и недавно его научили f.call(...). Если вызов метода родителя распознается — то все проинлайнивается и различные инварианты типа LoadFunctionPrototype выносятся LICMом за пределы самого горячего цикла и, как следствие, на результат бенчмарка влияют мало (см IR цикла, красная полоса — индикатор вложенности циклов).

Кстати, LoadFunctionPrototype похудел, он все еще больше чем одинокий mov, но меньше, чем то чудовище, которое раньше генерировалось.

Хочется еще отметить, что замеряемый код содержит в себе один печальный антипаттерн — начальное значение this.counter экземплярам потомков приходит из прототипа, из-за этого если создать два экземпляра и дергать у них по-очереди method, то в этом самом методе возникает нездоровый полиморфизм, который может скушать на этом микробенчмарке до 50% производительности.

В целом, наследование в JS, особенно когда у базового «класса» много детей и часть методов ими всеми разделяется, в настоящее время это performance antipattern, потому что все разделяемые методы становятся мучительно полиморфными внутри. Поэтому в коде для которго действительно важна каждая инструкция наследования стоит избегать или насильно клонировать все методы с прототипа базового класса в прототип дочернего — причем не копировать по ссылке, а именно клонировать, например, с помощью компиляции клонов через Function-конструктор.
Разумеется если массив у нас изначально состоит из чисел (для других случаев тип элементов не отслеживается), то null действительно переведёт его просто в массив «чего угодно»), в этом случае a[i] = null не подходит. Надо либо сдвигать элементы, либо писать специальный маркер в несуществующие позиции (например, a[i] = 0). Конкретный подход зависит от назначения конкретного массива и причин, по которым нужно из него удалять элементы.
Это хак связанный с тем, что V8 старается держать прототипы объектов в fast mode. Получается, когда вы присваиваете медленный объект (dictionary/slow mode) в f.prototype он превращается в быстрый.

Сей хак актуален, если вы знаете, что у вас есть объект находящийся по какой-то причине в словарном режиме и вам хочется его превратить в быстрый объект.
Лимит про 100000 для new Array(n) стал для V8 неактуальным после codereview.chromium.org/397593008, теперь преаллокация быстрее, потому что преалоцированный массив остается быстрым. До этой правки он превращался в словарь.
Я не сказал, что он будет работать быстрее. Я скорее сказал: это иллюзия, что он будет работать медленнее «потому что length в цикле больше никто не читает» — как CPU положит, так и будет работать :) У меня есть машина (с Xeonом) там у меня получилось быстрее — я это исключительно для того в пост добавил, чтобы показать, что это все замеры погоды в северном полушарии.
Рекомендации про удаление относится к использованию оператора delete.

Если у вас есть объект, который вы используете как словарь (а не как объект), то кроме удаления deleteом ничего не сделаешь.

Если же у вас именно объект — то лучше просто сделать o.p = null.

С массивом точно также — если массив уже разреженный с кучей дырок, т.е. словарь по сути дела — то пользуйтесь delete, в противном случае либо a[i] = null или двигайте элементы вниз (тем же splice или руками)

Все эти рекомендации не следует применять в слепую — надо просто понимать потенциальные проблемы, профилировать и решать уже после профилировки как разбираться с hotspotами.
это jsperf.com'овые графики, он все выражает в операциях в секунду.
Если не будет утекать в этих кейсах, будет утекать в других. Невозможно реализовать правильную семантику WeakMap в виде shim — из сильных ссылок, слабые не соорудить.
Заметьте я говорю про shim описанный в статье. При нативной реализации WeakMap в JS VM, конечно же, ничего ни в первом, ни во втором случае не должно утечь (если утекает — то это баг в реализации :)).

А в shim описанном в статье утечет, потому что мы при выполнении m.set(x, y) мы по сути дела прикрепляем y сильным образом к x, там к x прикрепляется специальная структурка. По сути дела у нас произошла перестановка из Map * Key → Value мы делаем Key * Map → Value и храним значения на ключах, индексируя их мапами. Такой shim не течет в том смысле, что если ключ «помер», то мап не будет без дела удерживать значения, но до настоящей семантики он не дотягивает. Потому что если ключ не помер, то нем эта вспомогательная структурка будет вечно висеть и содержать (и удерживать) мертвые куски относящиеся к умершим мапам.
7. Неправильное наследование через прототипы


Вы рекомендуете в этом разделе паттерн, которые негативно скажется на производительности, если эти объекты используются в горячих методах, поскольку он приводит к полифорфизму формы («скрытого класса») объекта в зависимотси от того имеет ли свойство значение по умолчанию или нет.
В контексте JavaScript «scope» — это как раз захваченный функцией контекст исполнения. А closure это просто синоним функции (потому что в JavaScript любой объект-функция — это closure, с захваченным контекстом) Поэтому, когда вы говорите, «замена scope на closure» — это звучит как «замена печени на слона», т.е. бессмысленно звучит.
дело тут отнюдь не в this. для V8, например, this — это в некотором смысле просто «нулевой» параметр передающийся в функцию (с некоторыми семантическими особенностями передачи), и никаких особых заточек на него не делается. дело тут в том, что за трюки происходят внутри VM, когда вы делаете присваивание obj.prop = f, где f — это функция. В V8 эти трюки реализованны таким образом, что при написании

function Ctor() {
  this.f = function () { };
}
var a = new Ctor(),
    b = new Ctor();


вместо

function Ctor() {
}
Ctor.prototype.f = function () { };
var a = new Ctor(),
    b = new Ctor();


эти трюки не срабатывают так сказать в полную силу и не просекают, что в a.f и b.f оказывается по сути дела функция с одним и тем же телом, а могли бы просекать. Для этого даже известно, что нужно сделать, надо выражаясь в терминах V8 на скрытый класс подсаживать не JSFunction (т.е. всё замыкание целиком), а SharedFunctionInfo (т.е. по сути дела «тело» без захваченного контекста). Я думаю сделают когда-нибудь.

Однако это решит только проблему с производительностью, а памяти все так же будет потреблять больше.
производительность


Ничего хорошего с производительностью и потреблением памяти при таком подходе не происходит.

Расход памяти растет по очевидным причинам: замыкания-методы на каждом объекте свои.

Производительность падает по причинам связанным с тем, как виртуальные машины работают и как они пытаются понять, где у нас «методы».
Оба подхода гораздо старее JVM и были для LISP разработаны.

«утрамбовка» это чаще LISP2 опубликованный в 67м, а простое копирование — это Cheney, опубликованный в 70м, т.е. позже. Cheney — это частый выбор для сборки мусора в молодом поколении.

Information

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