Когда-то фабрики / закрытия в JS были в пределах 15% от конструкторов / прототипов. Сегодня разница составляет более 8000% в пользу прототипов (а прототипы используют примерно половину памяти).
https://jsperf.com/prototype-vs-factory-performance/4
Замыкания (теоретически) не создают дополнительных объектов. У вас есть замыкание и экземпляр вместо прототипа и экземпляра (и у замыканий есть еще одно преимущество, потому что вы не можете добавлять / удалять из них свойства). Мой единственный вывод состоит в том, что, хотя функции являются примитивами и неизменяемыми (хотя объекты функций - нет), они не интернированы, что приводит к скачкам в кэше инструкций. Эта разница проявляется в JS-движках.
Есть ли у кого-нибудь реальные факты о том, почему существует такое огромное несоответствие?





Closures (in theory) don't create more objects.
Этот стиль создания объекта «закрытие / фабрика» действительно создает больше объектов: с использованием прототипов экземпляры методов прототипа являются общими, тогда как в «фабричном» стиле каждый экземпляр объекта получает свою собственную копию всех методов. Это наблюдаемо, так что это не то, что движок может просто оптимизировать. Учитывать:
var x1 = createValueObject();
var x2 = createValueObject();
x1.get.my_tag = 42;
console.info(x2.get.my_tag); // undefined
console.info(x2.get === x1.get); // false
var y1 = new ValueObject();
var y2 = new ValueObject();
y1.get.my_tag = 123;
console.info(y2.get.my_tag); // 123
console.info(y2.get === y1.get); // true
Я хотел бы подчеркнуть, что в целом использование замыканий и фабрик - это нормально; этот момент здесь применяется только к этот конкретный образец создания объектов.
Это также отличный пример: Остерегайтесь вводящих в заблуждение микробенчмарков!
Каждый раз, когда вы видите сотни миллионов операций в секунду в тесте jsperf.com, значит, оптимизирующему компилятору почти наверняка удавалось исключить весь ваш код, и вы измеряете пустые циклы. Никакая реальная операция не работает так быстро.
В данном случае это не удивительно: использование прототипов - это идиоматический способ определения / создания объектов в JavaScript в, и движки вкладывают много усилий в оптимизацию каждого аспекта этого шаблона, поэтому современные движки имеют возможность отслеживать, какие методы прототипа получают. вызывается, в конечном итоге встраивает их (не сразу, только в горячий код!), выясняет, что они не дают полезных результатов, и отбрасывает весь бесполезный код.
При правильном и тщательном тестировании я ожидал, что то, что вы называете шаблоном «конструкторы / прототипы», по-прежнему будет значительно быстрее, но не так быстро, как ошибочно показывают текущие результаты.
instruction cache thrashing
Нет, кеш инструкций тут ни при чем.
Once upon a time, factories/closures in JS were within 15-ish percent of constructors/prototypes.
Мне трудно поверить, что так было в любое время за последние десять лет. Может быть, 20 лет назад, когда все было по-собачьи?
Как показывает фрагмент кода, наличие копий объектов функций можно наблюдать (программы могут полагаться на это); другими словами, спецификация JavaScript требует наличия копий. Их код будет совместно использоваться в большинстве движков (следовательно, без эффектов кеширования инструкций), но объекты должны быть отдельными. Скрытые классы не меняют этого.
Зачем нужны копии функций? Разве указатель на интернированную функцию не позволит использовать одну и ту же функцию для разных объектов? Поскольку форма результирующих литералов идентична и встречается в одной и той же точке кода, не должно ли быть возможным совместное использование скрытого класса? Если вы повторяете десятки идентичных функций с разными участками памяти, постоянно меняя местами кеш-память, разве это не перегрузка кеша по сравнению с повторным запуском одних и тех же 5-6 функций?