В обоих ваших примерах второй фрагмент кода выделяет в 100 раз больше памяти, чем первый фрагмент кода. Он также выполняет приблизительно log_1.5 (100) изменения размера массива (при условии стандартной реализации учебника динамического массива c с коэффициентом роста 1,5). Изменение размера массива обходится дорого (выделение нового фрагмента памяти, затем O (n) копирование всех элементов в новый фрагмент памяти). В более общем смысле, сборщики мусора ненавидят мутацию , они гораздо эффективнее собирают множество мелких недолговечных объектов, чем поддерживают несколько крупных долгоживущих объектов.
Другими словами, в В первом примере вы измеряете Array#map
и Array#select
соответственно, тогда как во втором примере вы измеряете не только Array#each
, но также Array#<<
, а также изменение размера массива и выделение памяти. По результатам бенчмаркинга невозможно определить, какой из них вносит свой вклад. Как однажды сказал Зед Шоу: «Если вы хотите измерить что-то, не измеряйте другое дерьмо» .
Но даже если вы исправите эту ошибку в своем тесте, вообще говоря, больше специализированные операции имеют больше информации, чем обычные, поэтому более общие операции обычно не могут быть быстрее специализированных.
В вашем конкретном примере c это может быть что-то очень простое, например, вы использование реализации Ruby, которая не очень хороша для оптимизации кода Ruby (например, YARV, и, в отличие от, например, Truffle Ruby), в то же время оптимизированная собственная реализация Array#map
и Array#select
(опять же Возьмем в качестве примера YARV, который имеет C реализации для обоих из них и, как правило, не способен очень хорошо оптимизировать код Ruby).
И, наконец, написать правильные микробенчмарки сложно. Действительно, очень, очень сложно. Я рекомендую прочесть и понять всю эту дискуссионную ветку mechanical-sympathy Список рассылки: JMH vs Caliper: справочная тема . Хотя речь идет конкретно о Java бенчмаркинге (на самом деле о JVM бенчмаркинге), многие из аргументов применимы к любому современному высокопроизводительному движку OO, например Rubinius, Truffle Ruby и др. c. и в меньшей степени также в YARV. Обратите внимание, что большая часть обсуждения посвящена написанию микробенчмарков harnesses , а не написанию микробенчмарок как таковых, то есть речь идет о написании каркасов, которые позволяют разработчикам писать правильные микробенчмарки без необходимости знать об этом материале но, к сожалению, даже с лучшими жетонами микробенчмарков (а Ruby Benchmark
на самом деле не очень хороший), вам все равно нужно иметь очень глубокое понимание современных компиляторов, сборщиков мусора, механизмов исполнения, процессоров, аппаратные архитектуры, но также и статистика.
Вот хороший пример неудачного теста, который может быть неочевиден для неподготовленного автора тестов: Почему печать «B» значительно медленнее, чем печать «#»? .