у arguments object достаточно мутная семантика, поэтому если v8 видит, что он используется нетривиальным образом, то она отказывается оптимизировать. это проистекает из соображения, что в коде, для которого важны оптимизации, arguments чаще всего не используется или используется тривиальным способом, поэтому и реализовывать сложную поддержку нет смысла. (меньше сложностей — меньше багов).
какой факт? что замеры производительности С++ кода на разных машинах, компиляторах и флагах сборки дали разные результаты? это действительно факт.
обратите внимание, я не пытаюсь утверждать, что V8 производит в данном конкретном случае супер код (хотя я посмотрел на него и код достаточно хороший, если не считать бага описнного внизу) или что пересечение границы JS-C++ легче перышка. я утверждаю другое: разница в десятки миллисекунд на таком коде не может быть объяснена задержками при пересечении этой границы.
более того, я даже склоняюсь ко мнению, что у автора что-то со флагами компиляции не так — из-за чего скорость С++ного кода оказалась такой странной. но для подтверждения надо посмотреть на то, что его компилятор породил в этом месте. (еще заметьте, что и время исполнения обычного С++ного варианта и CUDA-вского включают в себя время пересечения границы JS-C++, т.е. оно меньше 17ms).
какой факт? что замеры производительности С++ кода на разных машинах, компиляторах и флагах сборки дали разные результаты? это действительно факт.
обратите внимание, я не пытаюсь утверждать, что V8 производит в данном конкретном случае супер код (хотя я посмотрел на него и код достаточно хороший, если не считать бага описнного внизу) или что пересечение границы JS-C++ легче перышка. я утверждаю другое: разница в десятки миллисекунд на таком коде не может быть объяснена задержками при пересечении этой границы.
более того, я даже склоняюсь ко мнению, что у автора что-то со флагами компиляции не так — из-за чего скорость С++ного кода оказалась такой странной. но для подтверждения надо посмотреть на то, что его компилятор породил в этом месте. (еще заметьте, что и время исполнения обычного С++ного варианта и CUDA-вского включают в себя время пересечения границы JS-C++, т.е. оно меньше 17ms).
у меня на ноде v0.8.8 вылазит баг в V8ом выводе представлений (representation inference) из-за которого замена на стэке (on stack replacement) пораждает совершенно бредовый код, в котором он пытается привести заведомо не целое значение к целому из-за чего происходит деоптимизация… в результате код постоянно крутится в неоптимизированной версии.
если же я запрещаю OSR и вызываю integrateJS дважды, то картина с пиковой производительностью получается примерно такая:
omega ~/src/temp/perl_vs_node ∳ git diff [master@368da]
diff --git a/integral.js b/integral.js
index 7b11f22..194adb2 100755
--- a/integral.js
+++ b/integral.js
@@ -18,4 +18,5 @@ function integrateJS(x0,xN,y0,yN,iterations){
console.log("JS time = "+(new Date().getTime() - time));
}
-integrateJS(-4,4,-4,4,1024);
\ No newline at end of file
+integrateJS(-4,4,-4,4,1024);
+integrateJS(-4,4,-4,4,1024);
omega ~/src/temp/perl_vs_node ∳ node integral.js [master@368da]
JS result = 127.99999736028109
JS time = 132
JS result = 127.99999736028109
JS time = 115
omega ~/src/temp/perl_vs_node ∳ node --nouse-osr integral.js [master@368da]
JS result = 127.99999736028109
JS time = 122
JS result = 127.99999736028109
JS time = 55
баг я зарапортую на эту тему. причина по моей прикидке в том, что начальное значение переменной result — 0 выглядит как целое число и попадая в phi-функцию на границе между нормальным потоком управления и OSR-entry блоком приводит к неправильному выводу представления для этой phi (https://github.com/v8/v8/blob/d3924c236bfca217385eaf5355a96146f0dcc60d/src/hydrogen-instructions.cc#L2567-2576 < — код увидев 0 выберет целое представление) и почему-то посказки (hint) для HUnknownOSRValue на обратной дуге не сработали.
Кстати, если заменить result = 0, на result = 0.1 баг в representation inference по понятным причинам пропадает.
функция вызывается один раз и выполняется сотни миллисекунд. в таком случае накладные расходы на пересечение границы просто незаметны. они ведь отнюдь не десятками миллисекунд измеряются, а совсем даже долями миллисекунды.
утечка — это когда память болтается непонятно где, не используется и не может быть переиспользована. а здесь не эффективное использование памяти, потому что это память никуда не пропала и занята объектом, на который есть валидные ссылки из другого объекта.
а сами объекты с 9 свойствами, конечно же, помирают… просто они были меньше с словаря, который теперь свисает с каждого testedObject[i], что пораждает иллюзию их живости.
Прежде всего совет: не пытайтесь понять, что удаляется, а что не удаляется по графику потребления памяти — это гадание на кофейной гуще. Просто посмотрите на снапшот кучи.
Объяснение (по крайней мере для Хрома) очень простое: когда вы делаете delete testedObject[i].obj, V8 нормализует объект testedObject[i] — трансформирует его из быстрого компактного представления в медленное и раздутое представление на основе словаря, который еще и выделяется с запасом по размеру. При этом V8 не замечает, что после удаления в словаре будет пусто — и словарь (800 байтов) остается болтаться в воздухе. И так для каждого из ваших объектов.
это не совсем правильный вывод: бенчмарк фокусируется на производительности чистого javascript, который однопоточен по своей природе. (workerы не являются частью ECMAScript стандарта и потому в тестах не используются). поэтому основной поток исполнения бенчмарка просто не может загрузить больше одного ядра.
некоторые JavaScript рантаймы, впрочем, испольуют потоки для различных внутренних целей: например, конкурентная сборка мусора и компиляция в Chakra, параллельная фаза разметки (marking) в JavaScriptCore, фаза sweeping в SpiderMonkey. так что наличие нескольких дополнительных ядер только на пользу.
Тесты в V8 Benchmark Suite могут со стороны выглядеть совершенно случайными, но на самом деле они тестируют фундаментальные вещи. NavierStokes, например, тестирует качество оптимизаций операций над числами с плавающей точкой и скорость работы массивов содержащих числа с плавающей точкой.
Проблема с большим реальным кодом состоит в том, что в нем сразу множество факторов влияющих на производительность смешиваются, поэтому V8 Benchmark Suite пытался разложить, так сказать, различные факторы по полочкам и каждый бенчмарк был достаточно сфокусирован на фичах языка, которые V8 хотела ускорить: DeltaBlue — полиморфный объектно-ориентированный код, Crypto — побитовые операции и т.д.
arguments[i]
,arguments.length
,func.apply(obj, arguments)
Map
.нет, Mapом все должно удерживаться, как вы проверяли?
Официальный репозиторий: code.google.com/p/v8/
Памятка для желающих поучаствовать: code.google.com/p/v8/wiki/Contributing
обратите внимание, я не пытаюсь утверждать, что V8 производит в данном конкретном случае супер код (хотя я посмотрел на него и код достаточно хороший, если не считать бага описнного внизу) или что пересечение границы JS-C++ легче перышка. я утверждаю другое: разница в десятки миллисекунд на таком коде не может быть объяснена задержками при пересечении этой границы.
более того, я даже склоняюсь ко мнению, что у автора что-то со флагами компиляции не так — из-за чего скорость С++ного кода оказалась такой странной. но для подтверждения надо посмотреть на то, что его компилятор породил в этом месте. (еще заметьте, что и время исполнения обычного С++ного варианта и CUDA-вского включают в себя время пересечения границы JS-C++, т.е. оно меньше 17ms).
обратите внимание, я не пытаюсь утверждать, что V8 производит в данном конкретном случае супер код (хотя я посмотрел на него и код достаточно хороший, если не считать бага описнного внизу) или что пересечение границы JS-C++ легче перышка. я утверждаю другое: разница в десятки миллисекунд на таком коде не может быть объяснена задержками при пересечении этой границы.
более того, я даже склоняюсь ко мнению, что у автора что-то со флагами компиляции не так — из-за чего скорость С++ного кода оказалась такой странной. но для подтверждения надо посмотреть на то, что его компилятор породил в этом месте. (еще заметьте, что и время исполнения обычного С++ного варианта и CUDA-вского включают в себя время пересечения границы JS-C++, т.е. оно меньше 17ms).
извиняюсь, коряво вставил консольный лог :-/
если же я запрещаю OSR и вызываю integrateJS дважды, то картина с пиковой производительностью получается примерно такая:
omega ~/src/temp/perl_vs_node ∳ git diff [master@368da] diff --git a/integral.js b/integral.js index 7b11f22..194adb2 100755 --- a/integral.js +++ b/integral.js @@ -18,4 +18,5 @@ function integrateJS(x0,xN,y0,yN,iterations){ console.log("JS time = "+(new Date().getTime() - time)); } -integrateJS(-4,4,-4,4,1024); \ No newline at end of file +integrateJS(-4,4,-4,4,1024); +integrateJS(-4,4,-4,4,1024); omega ~/src/temp/perl_vs_node ∳ node integral.js [master@368da] JS result = 127.99999736028109 JS time = 132 JS result = 127.99999736028109 JS time = 115 omega ~/src/temp/perl_vs_node ∳ node --nouse-osr integral.js [master@368da] JS result = 127.99999736028109 JS time = 122 JS result = 127.99999736028109 JS time = 55
баг я зарапортую на эту тему. причина по моей прикидке в том, что начальное значение переменной result — 0 выглядит как целое число и попадая в phi-функцию на границе между нормальным потоком управления и OSR-entry блоком приводит к неправильному выводу представления для этой phi (https://github.com/v8/v8/blob/d3924c236bfca217385eaf5355a96146f0dcc60d/src/hydrogen-instructions.cc#L2567-2576 < — код увидев 0 выберет целое представление) и почему-то посказки (hint) для HUnknownOSRValue на обратной дуге не сработали.
Кстати, если заменить result = 0, на result = 0.1 баг в representation inference по понятным причинам пропадает.
testedObject[i]
, что пораждает иллюзию их живости.Объяснение (по крайней мере для Хрома) очень простое: когда вы делаете
delete testedObject[i].obj
, V8 нормализует объектtestedObject[i]
— трансформирует его из быстрого компактного представления в медленное и раздутое представление на основе словаря, который еще и выделяется с запасом по размеру. При этом V8 не замечает, что после удаления в словаре будет пусто — и словарь (800 байтов) остается болтаться в воздухе. И так для каждого из ваших объектов.некоторые JavaScript рантаймы, впрочем, испольуют потоки для различных внутренних целей: например, конкурентная сборка мусора и компиляция в Chakra, параллельная фаза разметки (marking) в JavaScriptCore, фаза sweeping в SpiderMonkey. так что наличие нескольких дополнительных ядер только на пользу.
Проблема с большим реальным кодом состоит в том, что в нем сразу множество факторов влияющих на производительность смешиваются, поэтому V8 Benchmark Suite пытался разложить, так сказать, различные факторы по полочкам и каждый бенчмарк был достаточно сфокусирован на фичах языка, которые V8 хотела ускорить: DeltaBlue — полиморфный объектно-ориентированный код, Crypto — побитовые операции и т.д.
Хорошее описание фокуса каждого бенчмарка: developers.google.com/octane/benchmark