В примере Semaphore парковка не синхронизирована между заехавшими на неё автомобилями. В результате несколько автомобилей могут одновременно занять одно место.
Неудачный пример CountDownLatch: команды "Внимание", "Марш" могут быть даны ещё до прибытия автомобилей на старт, при этом команда "Марш" сама не является сигналом к началу гонки.
С Phaser вообще беда. В обоих циклах while очевидный race condition: фаза может измениться после проверки условия. Вообще, конструкция phaser.awaitAdvance(phaser.getPhase()) не имеет смысла по причине своей неатомарности. Кроме того, в примере пассажир может пропустить автобус, даже если пришёл на остановку вовремя.
Добавлю, что давать ссылки на документацию по Java 7 (устаревшую) — плохой тон. И, как уже было замечено, в русском языке thread принять переводить "поток", а не "нить".
Не знаю, в чём прикол, но картинки на самом деле разные. Если тот же www-hitchhiker-vfluKv9vH.png перекодировать в lossless webp, то он будет занимать уже 25 КБ вместо 28.
он старается разносить во времени малые и старшие сборки мусора, чтобы они совместно не создавали продолжительных пауз в работе приложения
На самом деле, ровно наоборот. Перед запуском CMS цикла JVM по возможности дожидается сборки в молодом поколении (-XX:CMSWaitDuration), чтобы уменьшить время Initial mark. Аналогично перед remark фазой зачастую просят CMS опять дождаться сборки в молодом поколении (-XX:+CMSScavengeBeforeRemark), чтобы уменьшить длительность remark.
серьезно рассматривают его на роль сборщика по умолчанию
Уже сделали.
-XX:AggressiveOpts
Не надо советовать эту опцию. Во-первых, она не имеет отношения к GC, во-вторых, не предназначена для использования в production, и порой даже вредна.
Посмотрел, действительно, в JDK 9 сгенерированный код слегка распух. «Магия» — это G1 барьеры, коими сопровождается каждый апдейт ссылки в хипе. В JDK 9 G1 стал дефолтным коллектором, отсюда и разница. Стало быть, для честного сравнения надо запускать с -XX:+UseParallelGC.
Ещё одно подтверждение тому, что микробенчмарки зачастую измеряют совсем другое, чем хотелось, и без должного анализа смысла не имеют. Если кто-то говорит, «мой код быстрее в 10 раз, я проверил бенчмарком», и при этом даже не смотрит на логи компилятора, не говоря уж об ассемблере, значит, он безбожно врёт :)
В аду приготовлен отдельный котёл для тех, кто делает выводы о производительности по одному бенчмарку без какого-либо анализа :) Этот бенчмарк, как зачастую и бывает, жульничает :)
Ничего не скажу про Java 7u60, т.к. под рукой есть только 7u80, который ведёт себя так же, как и Java 8. А что касается Java 8u60, дела обстоят так. В бенчмарке по умолчанию у списка выставлен initialCapacity = 32, но при этом добавляется лишь 15 элементов. Т.е. выхода за пределы массива никогда не происходит. Естественно, JIT это спекулятивно оптимизирует, ставя uncommon trap и выкидывая эту ветку вообще. В итоге оба варианта компилируются одинаково.
Зато, если поставить initialCapacity=14 или меньше, в профиле останется статистика, что исключение выкидывалось, и JIT уже по-честному скомпилирует ветку для расширения массива. При этом результаты для варианта с try-catch окажутся удручающими:
Впрочем, и такому бенчмарку нельзя верить, потому как реальный сценарий на продакшне может оказаться совсем другим. И не дай бог кто-то вдруг заиспользует List, основанный на try/catch, не для долгоживущих списков, а для типичного сценария «создал-поработал-забыл», где исключения начнут выскакивать часто, и тогда начнётся самое интересное.
Как говорится, бывает ложь, большая ложь, и бенчмарки :) Всегда можно написать бенчмарк, который «докажет» что угодно, если не понимать, что именно он измеряет. А в случае с пулами, очевидно, запустив пустой getConnection() в 8 потоках, автор измерил стоимость contended блокировки. Очень полезно. Особенно, когда в продакшне даже при 5000 запросах в секунду у нас contention наблюдается в < 0.5% случаев. Хотя uncontended случай, на котором one-datasource оказался внезапно быстрее, куда ближе к реальности, на самом деле, и он абсолютно бесполезен, покуда в жизни приложение обычно занимается запросами к базе, а не синхронизацией на пуле.
Окей, пускай даже автор сэкономил 500 наносекунд на доставании коннекшна из пула, и тут же потерял целую миллисекунду (в 2000 раз больше!) на валидации этого коннекшна. То, что товарищ назвал недостатком one-datasource, на самом деле сделано специально: мы целенаправлено избавились от валидации, заменив её ретраями уровнем выше, чтобы в два раза сократить количество запросов к базе и значительно снизить latency.
Забавно, что автор Hikari указывает на наши якобы проблемы с транзакциями. При том, что его пул не поддерживает работу с TransactionManager в принципе! Т.е. вообще не работает! Если приглядеться, все озвученные «проблемы» — это неправильное использование DataSourceImpl. one-datasource используется только внутри наших проектов в контролируемом контексте. У нас нет планов делать из него open source продукт и, тем более, мериться с кем-либо. Я его выложил на GitHub, только чтобы показать несостоятельность бенчмарка.
Спасибо за ссылку.
Действительно, бенчмарк оказался ни о чём. Проверяются пустые стабы, не имеющие ничего общего с реальными Connection и Statement.
В таком случае наш Datasource почти в 4 раза быстрее на том же бенчмарке, и тогда заголовок статьи про «самый быстрый пул соединений» — тем более надувательство.
длина метода в байткодах для инлайнинга, дефолтная она 35 байткодов
Это называется «слышал звон, да не знает, где он». Есть параметр JVM MaxInlineSize=35, но он означает совсем не то, что думает автор. Это лишь некая эвристика для инлайнинга, и она не мешает заинлайниться и более длинному методу, если JIT сочтёт нужным. Попытки «помочь» JIT-компилятору могут привести ровно к обратному эффекту. Метод checkException не листовой. Принудительно заинлайнив его, автор, например, рискует лишиться инлайнинга вложенных методов, тем самым получив вместо одного вызова два или три.
Там говорится, что у JIT больше возможностей по оптимизации в случае invokestatic.
А уж чего стоит аргумент про уменьшение стека с 5 до 4 элементов! С таким же успехом можно сказать, что программа будет работать быстрее и занимать меньше памяти, если названия всех переменных сократить до 1-2 букв :)
Лучше приведите исходники этих бенчмарков (на гитхабе проекта не нашёл), и я скажу, где они врут.
Вы перевели всё честно, здесь спору нет, но ерунда написана в оригинальной статье.
лимит инлайнинга в Hostpot JVM — 35 байткод инструкций
После этой ерунды можно дальше не читать. И что invokestatic якобы эффективнее invokevirtual, и что «лишние» push/pop якобы влияют на скорость — всё от начала и до конца — сплошная неправда. Уберите, пожалуйста, статью, чтоб не путать людей.
«Оптимизировать» программу на уровне байткода — всё равно что улучшать автомобиль, выкидывая «лишние» детали.
phaser.awaitAdvance(phaser.getPhase())
не имеет смысла по причине своей неатомарности. Кроме того, в примере пассажир может пропустить автобус, даже если пришёл на остановку вовремя.Добавлю, что давать ссылки на документацию по Java 7 (устаревшую) — плохой тон. И, как уже было замечено, в русском языке thread принять переводить "поток", а не "нить".
cwebp.exe -z 9 www-hitchhiker-vfluKv9vH.png -o out.webp
В WebP есть lossless режим. Первый же PNG по ссылке из статьи сжимается с 27 килобайт до 11 в WebP без потери качества!
int
'а.Даже в байткоде ни одного условного оператора.
На самом деле, ровно наоборот. Перед запуском CMS цикла JVM по возможности дожидается сборки в молодом поколении (
-XX:CMSWaitDuration
), чтобы уменьшить время Initial mark. Аналогично перед remark фазой зачастую просят CMS опять дождаться сборки в молодом поколении (-XX:+CMSScavengeBeforeRemark
), чтобы уменьшить длительность remark.Уже сделали.
Не надо советовать эту опцию. Во-первых, она не имеет отношения к GC, во-вторых, не предназначена для использования в production, и порой даже вредна.
-XX:+UseParallelGC
.Ещё одно подтверждение тому, что микробенчмарки зачастую измеряют совсем другое, чем хотелось, и без должного анализа смысла не имеют. Если кто-то говорит, «мой код быстрее в 10 раз, я проверил бенчмарком», и при этом даже не смотрит на логи компилятора, не говоря уж об ассемблере, значит, он безбожно врёт :)
Ничего не скажу про Java 7u60, т.к. под рукой есть только 7u80, который ведёт себя так же, как и Java 8. А что касается Java 8u60, дела обстоят так. В бенчмарке по умолчанию у списка выставлен initialCapacity = 32, но при этом добавляется лишь 15 элементов. Т.е. выхода за пределы массива никогда не происходит. Естественно, JIT это спекулятивно оптимизирует, ставя uncommon trap и выкидывая эту ветку вообще. В итоге оба варианта компилируются одинаково.
Зато, если поставить initialCapacity=14 или меньше, в профиле останется статистика, что исключение выкидывалось, и JIT уже по-честному скомпилирует ветку для расширения массива. При этом результаты для варианта с try-catch окажутся удручающими:
Впрочем, и такому бенчмарку нельзя верить, потому как реальный сценарий на продакшне может оказаться совсем другим. И не дай бог кто-то вдруг заиспользует List, основанный на try/catch, не для долгоживущих списков, а для типичного сценария «создал-поработал-забыл», где исключения начнут выскакивать часто, и тогда начнётся самое интересное.
Окей, пускай даже автор сэкономил 500 наносекунд на доставании коннекшна из пула, и тут же потерял целую миллисекунду (в 2000 раз больше!) на валидации этого коннекшна. То, что товарищ назвал недостатком one-datasource, на самом деле сделано специально: мы целенаправлено избавились от валидации, заменив её ретраями уровнем выше, чтобы в два раза сократить количество запросов к базе и значительно снизить latency.
Забавно, что автор Hikari указывает на наши якобы проблемы с транзакциями. При том, что его пул не поддерживает работу с TransactionManager в принципе! Т.е. вообще не работает! Если приглядеться, все озвученные «проблемы» — это неправильное использование DataSourceImpl. one-datasource используется только внутри наших проектов в контролируемом контексте. У нас нет планов делать из него open source продукт и, тем более, мериться с кем-либо. Я его выложил на GitHub, только чтобы показать несостоятельность бенчмарка.
Действительно, бенчмарк оказался ни о чём. Проверяются пустые стабы, не имеющие ничего общего с реальными Connection и Statement.
В таком случае наш Datasource почти в 4 раза быстрее на том же бенчмарке, и тогда заголовок статьи про «самый быстрый пул соединений» — тем более надувательство.
MaxInlineSize=35
, но он означает совсем не то, что думает автор. Это лишь некая эвристика для инлайнинга, и она не мешает заинлайниться и более длинному методу, если JIT сочтёт нужным. Попытки «помочь» JIT-компилятору могут привести ровно к обратному эффекту. МетодcheckException
не листовой. Принудительно заинлайнив его, автор, например, рискует лишиться инлайнинга вложенных методов, тем самым получив вместо одного вызова два или три.А это тоже неправда. Вот, навскидку, ровно противоположный пример.
А уж чего стоит аргумент про уменьшение стека с 5 до 4 элементов! С таким же успехом можно сказать, что программа будет работать быстрее и занимать меньше памяти, если названия всех переменных сократить до 1-2 букв :)
Если всё ещё не убедительно, проверьте сами.
Заодно и развеем миф invokestatic vs. invokevirtual:
Или запустите с -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining:
Почему это не оптимизация, а совсем наоборот, я рассказывал на JPoint в презентации про устройство виртуальной машины HotSpot.
Вы перевели всё честно, здесь спору нет, но ерунда написана в оригинальной статье.
«Оптимизировать» программу на уровне байткода — всё равно что улучшать автомобиль, выкидывая «лишние» детали.