Pull to refresh
170
0
Андрей @apangin

Пользователь

Send message
Ужасные примеры с кучей ошибок.

  1. В примере Semaphore парковка не синхронизирована между заехавшими на неё автомобилями. В результате несколько автомобилей могут одновременно занять одно место.
  2. Неудачный пример CountDownLatch: команды "Внимание", "Марш" могут быть даны ещё до прибытия автомобилей на старт, при этом команда "Марш" сама не является сигналом к началу гонки.
  3. С Phaser вообще беда. В обоих циклах while очевидный race condition: фаза может измениться после проверки условия. Вообще, конструкция phaser.awaitAdvance(phaser.getPhase()) не имеет смысла по причине своей неатомарности. Кроме того, в примере пассажир может пропустить автобус, даже если пришёл на остановку вовремя.

Добавлю, что давать ссылки на документацию по Java 7 (устаревшую) — плохой тон. И, как уже было замечено, в русском языке thread принять переводить "поток", а не "нить".
У cwebp по умолчанию lossy режим. Надо с параметром -z:
cwebp.exe -z 9 www-hitchhiker-vfluKv9vH.png -o out.webp
Не знаю, в чём прикол, но картинки на самом деле разные. Если тот же www-hitchhiker-vfluKv9vH.png перекодировать в lossless webp, то он будет занимать уже 25 КБ вместо 28.
Статья основана на ложных предпосылках.
Не понимаю вообще смысла в продвижении формата webp. Если бы он был lossless, тогда пожалуйста.
Но PNG он портит вообще коварно.
В WebP есть lossless режим. Первый же PNG по ссылке из статьи сжимается с 27 килобайт до 11 в WebP без потери качества!
Вот вам честная сортировка без условий, не ограниченная размером int'а.
Даже в байткоде ни одного условного оператора.

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.IntSupplier;

public class Unconditional {

    static int ifLess(long a, long b, IntSupplier trueBranch, IntSupplier falseBranch) {
        int cond = (int) ((a - b) >>> 63);
        return new IntSupplier[]{falseBranch, trueBranch}[cond].getAsInt();
    }

    static int inc(long[] array, int i, long pivot) {
        return ifLess(array[i], pivot, () -> inc(array, i + 1, pivot), () -> i);
    }

    static int dec(long[] array, int j, long pivot) {
        return ifLess(pivot, array[j], () -> dec(array, j - 1, pivot), () -> j);
    }

    static void swap(long[] array, int i, int j) {
        long tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

    static int partition(long[] array, int lo, int hi, long pivot) {
        int i = inc(array, lo, pivot);
        int j = dec(array, hi, pivot);
        return ifLess(i, j, () -> {
            swap(array, i, j);
            return partition(array, i + 1, j - 1, pivot);
        }, () -> j);
    }

    static int qsort(long[] array, int lo, int hi) {
        return ifLess(lo, hi, () -> {
            int p = partition(array, lo, hi, array[lo + (hi - lo) / 2]);
            return qsort(array, lo, p) | qsort(array, p + 1, hi);
        }, () -> 0);
    }

    public static void main(String[] args) {
        long[] array = ThreadLocalRandom.current().longs(100, 0, 1000).toArray();
        qsort(array, 0, array.length - 1);
        System.out.println(Arrays.toString(array));
    }
}
Неплохая статья. Небольшие ремарки:
он старается разносить во времени малые и старшие сборки мусора, чтобы они совместно не создавали продолжительных пауз в работе приложения
На самом деле, ровно наоборот. Перед запуском 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 окажутся удручающими:

Benchmark               (initCapacity)  (listImpl)   Mode  Cnt      Score     Error   Units
FastListBench.testList              14         new  thrpt    6   6444,598 ± 195,891  ops/ms
FastListBench.testList              14        orig  thrpt    6  18426,890 ± 790,905  ops/ms
FastListBench.testList              15         new  thrpt    6  19122,083 ± 436,809  ops/ms
FastListBench.testList              15        orig  thrpt    6  18567,706 ± 101,215  ops/ms

Впрочем, и такому бенчмарку нельзя верить, потому как реальный сценарий на продакшне может оказаться совсем другим. И не дай бог кто-то вдруг заиспользует 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 раза быстрее на том же бенчмарке, и тогда заголовок статьи про «самый быстрый пул соединений» — тем более надувательство.
А как, по-вашему, JVM понимает, что пора выкинуть exception, не проверяя условия?
Избежать условия не получится. В принципе. По ссылке выше рассказано, почему.
длина метода в байткодах для инлайнинга, дефолтная она 35 байткодов
Это называется «слышал звон, да не знает, где он». Есть параметр JVM MaxInlineSize=35, но он означает совсем не то, что думает автор. Это лишь некая эвристика для инлайнинга, и она не мешает заинлайниться и более длинному методу, если JIT сочтёт нужным. Попытки «помочь» JIT-компилятору могут привести ровно к обратному эффекту. Метод checkException не листовой. Принудительно заинлайнив его, автор, например, рискует лишиться инлайнинга вложенных методов, тем самым получив вместо одного вызова два или три.

Там говорится, что у JIT больше возможностей по оптимизации в случае invokestatic.
А это тоже неправда. Вот, навскидку, ровно противоположный пример.

А уж чего стоит аргумент про уменьшение стека с 5 до 4 элементов! С таким же успехом можно сказать, что программа будет работать быстрее и занимать меньше памяти, если названия всех переменных сократить до 1-2 букв :)
Так а в чём здесь оптимизация, поясните?
Потому что лимит задаётся совсем другими параметрами JVM. HotSpot JVM может инлайнить методы гораздо длиннее 35 байткодов. Смотрите FreqInlineSize.

Если всё ещё не убедительно, проверьте сами.
Заодно и развеем миф invokestatic vs. invokevirtual:

Benchmark                     Mode  Cnt    Score   Error   Units
Inlining.inlineStaticSmall   thrpt    5  298,373 ± 5,752  ops/us
Inlining.inlineVirtualLarge  thrpt    5  298,515 ± 9,902  ops/us

Или запустите с -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining:

    @ 16   bench.Inlining::inlineStaticSmall (4 bytes)
      @ 0   bench.Inlining::smallMethod (25 bytes)   inline (hot)

    @ 16   bench.Inlining::inlineVirtualLarge (5 bytes)
      @ 1   bench.Inlining::largeMethod (109 bytes)   inline (hot)
Зато я нашёл в исходниках ещё одну замечательную «оптимизацию» :)

      try {
         elementData[size++] = element;
      }
      catch (ArrayIndexOutOfBoundsException e) {
         // overflow-conscious code
         final int oldCapacity = elementData.length;
         final int newCapacity = oldCapacity << 1;
         @SuppressWarnings("unchecked")
         final T[] newElementData = (T[]) Array.newInstance(clazz, newCapacity);
         System.arraycopy(elementData, 0, newElementData, 0, oldCapacity);
         newElementData[size - 1] = element;
         elementData = newElementData;
      }

Почему это не оптимизация, а совсем наоборот, я рассказывал на JPoint в презентации про устройство виртуальной машины HotSpot.
Лучше приведите исходники этих бенчмарков (на гитхабе проекта не нашёл), и я скажу, где они врут.
Вы перевели всё честно, здесь спору нет, но ерунда написана в оригинальной статье.
лимит инлайнинга в Hostpot JVM — 35 байткод инструкций
После этой ерунды можно дальше не читать. И что invokestatic якобы эффективнее invokevirtual, и что «лишние» push/pop якобы влияют на скорость — всё от начала и до конца — сплошная неправда. Уберите, пожалуйста, статью, чтоб не путать людей.

«Оптимизировать» программу на уровне байткода — всё равно что улучшать автомобиль, выкидывая «лишние» детали.

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Works in
Registered
Activity