Ответ 1
Хорошо, как вы подходите к таким вещам.
Когда кто-то утверждает, что его реализация в 2-3 раза быстрее с обычными фразами, такими как "максимальное повторное использование кода", "без дополнительной безопасности" и т.д. и не дает никакого реального теста, вы должны поднять красный флаг в голову, Действительно, все тесты в своих почтовых списках/документах не имеют исходного кода и не написаны (по результатам) вручную (что, вероятно, нарушает правила бенчмаркинга) вместо использования JMH.
Прежде чем размахивать руками, почему что-то быстрее, чем что-то другое, давайте напишем бенчмарк и посмотрим, быстрее ли он действительно, прежде чем делать какие-либо утверждения. Код эталона здесь: он просто проверяет все основные операции для наборов размером 1024 и 1024 * 1024 (~ 1kk) с коэффициентом заполнения 50%, Тесты выполняются на процессоре Intel Core i7-4870HQ с частотой 2.50 ГГц. Оценка - пропускная способность, чем выше, тем лучше.
Весь эталон выглядит следующим образом:
@Benchmark
public boolean getClassic(BitSetState state) {
return state.bitSet.get(state.nextIndex);
}
@Benchmark
public boolean getOpen(BitSetState state) {
return state.openBitSet.get(state.nextIndex);
}
@Benchmark
public boolean getOpenFast(BitSetState state) {
return state.openBitSet.fastGet(state.nextIndex);
}
Хорошо, посмотрим на результаты:
Benchmark (setSize) Mode Cnt Score Error Units
BitSetBenchmark.andClassic 1024 thrpt 5 109.541 ± 46.361 ops/us
BitSetBenchmark.andOpen 1024 thrpt 5 111.039 ± 9.648 ops/us
BitSetBenchmark.cardinalityClassic 1024 thrpt 5 93.509 ± 10.943 ops/us
BitSetBenchmark.cardinalityOpen 1024 thrpt 5 29.216 ± 4.824 ops/us
BitSetBenchmark.getClassic 1024 thrpt 5 291.944 ± 46.907 ops/us
BitSetBenchmark.getOpen 1024 thrpt 5 245.023 ± 75.144 ops/us
BitSetBenchmark.getOpenFast 1024 thrpt 5 228.563 ± 91.933 ops/us
BitSetBenchmark.orClassic 1024 thrpt 5 121.070 ± 12.220 ops/us
BitSetBenchmark.orOpen 1024 thrpt 5 107.612 ± 16.579 ops/us
BitSetBenchmark.setClassic 1024 thrpt 5 527.291 ± 26.895 ops/us
BitSetBenchmark.setNextClassic 1024 thrpt 5 592.465 ± 34.926 ops/us
BitSetBenchmark.setNextOpen 1024 thrpt 5 575.186 ± 33.459 ops/us
BitSetBenchmark.setOpen 1024 thrpt 5 527.568 ± 46.240 ops/us
BitSetBenchmark.setOpenFast 1024 thrpt 5 522.131 ± 54.856 ops/us
Benchmark (setSize) Mode Cnt Score Error Units
BitSetBenchmark.andClassic 1232896 thrpt 5 0.111 ± 0.009 ops/us
BitSetBenchmark.andOpen 1232896 thrpt 5 0.131 ± 0.010 ops/us
BitSetBenchmark.cardinalityClassic 1232896 thrpt 5 0.174 ± 0.012 ops/us
BitSetBenchmark.cardinalityOpen 1232896 thrpt 5 0.049 ± 0.004 ops/us
BitSetBenchmark.getClassic 1232896 thrpt 5 298.027 ± 40.317 ops/us
BitSetBenchmark.getOpen 1232896 thrpt 5 243.472 ± 87.491 ops/us
BitSetBenchmark.getOpenFast 1232896 thrpt 5 248.743 ± 79.071 ops/us
BitSetBenchmark.orClassic 1232896 thrpt 5 0.135 ± 0.017 ops/us
BitSetBenchmark.orOpen 1232896 thrpt 5 0.131 ± 0.021 ops/us
BitSetBenchmark.setClassic 1232896 thrpt 5 525.137 ± 11.849 ops/us
BitSetBenchmark.setNextClassic 1232896 thrpt 5 597.890 ± 51.158 ops/us
BitSetBenchmark.setNextOpen 1232896 thrpt 5 485.154 ± 63.016 ops/us
BitSetBenchmark.setOpen 1232896 thrpt 5 524.989 ± 27.977 ops/us
BitSetBenchmark.setOpenFast 1232896 thrpt 5 532.943 ± 74.671 ops/us
Удивительно, не так ли? Что мы можем извлечь из результатов?
- Получить и установить (включая быстрые версии) равные по производительности. Их результаты лежат в одинаковых границах ошибок, трудно отличить любую разницу без надлежащей нанообъектности, поэтому с точки зрения использования битрейта в типичной реализации приложения не имеет никакого значения, и еще одна ветвь не имеет значения. Таким образом, утверждение о
OpenBitSet
get/set повышает производительность false. UPD: nanobenchmark методов get также не показывает различий, результаты здесь. - Мощность
BitSet
может быть рассчитана намного быстрее (~ 3 раза для размеров 1k и 1kk), поэтому утверждение о "сверхбыстрой мощности" false. Но цифры бессмысленны без фактического ответа, почему производительность отличается, поэтому позвольте копать немного. Для подсчета бит в словахBitSet
используетсяLong#bitCount
, который является Hotspot intrinsic. Это означает, что весь методbitCount
будет скомпилирован в одну инструкцию (для любопытных это будет x86popcnt
). В то время какOpenBitSet
использует ручной расчёт бит, используя трюки от Hacker Delight (см.org.apache.lucene.util.BitUtil#pop_array
). Неудивительно, почему классическая версия сейчас быстрее. -
Методы группового набора, такие как и/или оба, одинаковы, поэтому здесь нет выигрыша от производительности. Но интересная вещь:
BitSet
реализация отслеживает максимальный индекс слова, в котором задан хотя бы один бит, и выполняются операции и/или/мощности только в пределах [0, maxIndex], поэтому мы можем сравнивать конкретные случаи, когда набор имеет только первый 1/10/50% бит, а остальное нет (с одинаковым коэффициентом заполнения 50% для данной части). Тогда производительностьBitSet
должна отличаться, аOpenBitSet
остается неизменной. Пусть validate (контрольный код):Benchmark (fillFactor) (setSize) Mode Cnt Score Error Units BitSetBenchmark.andClassic 0.01 1232896 thrpt 5 32.036 ± 1.320 ops/us BitSetBenchmark.andClassic 0.1 1232896 thrpt 5 3.824 ± 0.896 ops/us BitSetBenchmark.andClassic 0.5 1232896 thrpt 5 0.330 ± 0.027 ops/us BitSetBenchmark.andClassic 1 1232896 thrpt 5 0.140 ± 0.017 ops/us BitSetBenchmark.andOpen 0.01 1232896 thrpt 5 0.142 ± 0.008 ops/us BitSetBenchmark.andOpen 0.1 1232896 thrpt 5 0.128 ± 0.015 ops/us BitSetBenchmark.andOpen 0.5 1232896 thrpt 5 0.112 ± 0.015 ops/us BitSetBenchmark.andOpen 1 1232896 thrpt 5 0.132 ± 0.018 ops/us BitSetBenchmark.orClassic 0.01 1232896 thrpt 5 27.826 ± 13.312 ops/us BitSetBenchmark.orClassic 0.1 1232896 thrpt 5 3.727 ± 1.161 ops/us BitSetBenchmark.orClassic 0.5 1232896 thrpt 5 0.342 ± 0.022 ops/us BitSetBenchmark.orClassic 1 1232896 thrpt 5 0.133 ± 0.021 ops/us BitSetBenchmark.orOpen 0.01 1232896 thrpt 5 0.133 ± 0.009 ops/us BitSetBenchmark.orOpen 0.1 1232896 thrpt 5 0.118 ± 0.007 ops/us BitSetBenchmark.orOpen 0.5 1232896 thrpt 5 0.127 ± 0.018 ops/us BitSetBenchmark.orOpen 1 1232896 thrpt 5 0.148 ± 0.023 ops/us
Нижняя часть набора заполнена, тем быстрее BitSet
есть и когда бит распределяется равномерно, тогда производительность BitSet
и OpenBitSet
становится равной, подтверждается теория. Поэтому для конкретных неравномерных распределений битов бит классический BitSet
работает быстрее для групповых операций. Заявление о очень быстрых групповых операциях в OpenBitSet
false.
Резюме
Этот ответ и контрольные показатели не намереваются показать, что OpenBitSet
плохой, или что авторы - лжецы. Действительно, согласно их тестовым станкам (AMD Opteron и Pentium 4) и версии Java (1.5), легко поверить, что ранее BitSet
был менее оптимизирован, компилятор Hotspot был не очень умным, popcnt
инструкция не существовала, а затем OpenBitSet
была хорошей идеей и была намного более результативной. Более того, BitSet
не раскрывает свой внутренний массив слов, поэтому невозможно создать пользовательский мелкозернистый синхронизированный битовый набор или гибкую сериализацию и то, что нужно Lucene. Итак, для Lucene это еще разумный выбор, в то время как для обычных пользователей лучше использовать стандартный BitSet
, который быстрее (в некоторых случаях, как правило) и принадлежит к стандартной библиотеке. Временные изменения, старые результаты производительности изменяются, поэтому всегда проверяйте и проверяйте свои конкретные случаи, возможно, для некоторых из них (например, не тестируемый итератор или различный коэффициент заполнения) OpenBitSet
будет быстрее.