Вернувшись с JPoint 2017, где с огромнейшим удовольствием пообщался с большим числом мегакрутых программистов решил надеть шляпу и покопаться в разного рода оптимизациях.


https://xkcd.com/1781/



JPoint 2017 открывался с мега пленарного доклада Алексея Шипилёва, который он анонсировал ёщё в октябре 2016 на Joker — тогда я смотрел его online и проникся Кривой им Ш:


Кривая им. Ш


И если с зелёной и красной зоной как-то более-менее ясно-понятно, то вот с границей зеленой-жёлтой не всё так очевидно — что это? можно пример ?


System.arraycopy


Начнём с эпичного срача вопроса — что быстрее — ручной джедайский меч


for (int i = 0; i < src.length; i++)
   target[i] = src[i];

или расово верный System.arraycopy.


На данный момент векторизация и intrinsic делают оба варианта равнозначными — вопрос вкусовых предпочтений.


collection.toArray()


Есть коллекция, например, строк, и хотим получить массив.


Лучшее (враг хорошего) решение:


String[] array = collection.toArray(new String[collection.size]);

Забудь!


Arrays of Wisdom of the Ancients учит, что новое это хорошо забытое старое


String[] array = collection.toArray(new String[0]);

new ArrayList


Раньше (когда деревья были высокими) при создании экземпляра ArrayList / HashMap / etc внутри создавался массив с начальным размером ( 10 для ArrayList, 0x10 для HashMap ).


Другое дело, когда вы заранее знали, какое количество элементов вы собираетесь положить в коллекцию — опытные джедаи создают сразу что-то типа


List<String> strings = new ArrayList<>( values.length );

сразу запихиваете свои values и счастье — никаких паразитных аллокаций от 10 элементов до вашей цели.


Но что, если самих значений ещё нет — с null возиться не хочется, но знаем, что будет не больше, например, трёх?


List<String> strings = new ArrayList<>( 3 );

И погнали — профит в 7 сэкономленных ячеек на каждый такой список ( ~7 * 16 байт / список ), который когда-то да будет заполнен.


Забудь!


Optimize empty HashMap and ArrayList коммит в openjdk — теперь все ArrayList / HashMap, созданные от конструктора по-умолчанию держат пустую static заглушку — и первое же добавление создаст массив на 10 элементов (для ArrayList, 16 для HashMap), тогда как new ArrayList( int ) создаёт массив энергично и сразу.


String.split(regex)


Что знают джедаи про регулярные выражения?
https://xkcd.com/208/


Компилировать regexp каждый раз — дорого, опытные падаваны делают вместо stringValue.split(regexp) что-то типа


private static final Pattern PATTERN = Pattern.compile(regex);

....
String[] strings = PATTERN.split(stringValue);

Профит в том, что шаблон не компилируется каждый раз.


Забудь!


Забудь!


Lightweight implementation of String.split for simple use case


Если у вас односимвольный разделитель — именно разделитель типа ( пробел ) или , ( запятая ), а не спец. символ регулярного выражения типа . ( точка ) — то никакое регулярное выражение даже и близко не создаётся.


Код, который пахнет


Я не буду даже говорить про код, который явно пахнет типа


return new StringBuilder().append("value:").append(value).toString();

пользы от него никакой — зато читаемость никакая.


XX-флаги


Можно очень кропотливо подбирать размеры кучи и прочие XX-флаги — но со временем, когда в код проекта влито ещё 1000 и 1 одна бизнес фича все эти флаги могут более, чем запросто не приносить желаемого результата быстроты. Считайте они протухли.


Выводы


Если ваш проект жив и процветает, а вас раз в сто-двести лет просят устроить перформанс (хотя танцы голым на пьяных столах тоже перформанс, но я немного о другом ) — то появляются всякого рода оптимизации, и не исключено, что из той самой жёлтой зоны, которые по истечению срока годности начинают не просто пахнуть, но и делать обратную задачу от изначальной — тормозить приложение.



Надежды нет, что анализаторы типа PMD, или ваша любимая IDE имеют актуальные рекомендации именно для той версии java, на которой вы запускаеть в PROD ==>


Проводите ревизию магических приёмов знаний.

Поделиться с друзьями
-->

Комментарии (13)


  1. nehaev
    11.04.2017 19:23
    +5

    Ко всему этому еще стоит добавить, что оптимизировать глядя только на код — это вообще малоперспективное занятие. По уму вам надо сначала взять профайлер, найти узкие места и оптимизировать только их. Более того, необходимо с помощью соответсвующих тулов убеждаться, что каждая внесенная оптимизация дает измеримый прирост производительности.


    1. vladimir_dolzhenko
      12.04.2017 09:43
      +2

      Об использовании любого типа профилировщика — даже из желудей и палок и про то, что стоит, а что не стоит оптимизировать, говорит Алексей в докладе.

      Это шаг №0 и я его неявно подразумеваю.


      1. m08pvv
        12.04.2017 10:56

        Это шаг №0 и я его неявно подразумеваю.

        Из-за подобного самолёты разбивались. Кто-то что-то неявно подразумевал и всё. А учитывая, что порог вхождения в программирование постоянно стараются снизить, не стоит опускать, пусть и кажущиеся очевидными, но при этом критически важные, шаги.


  1. sergey-b
    11.04.2017 23:11
    +6

    Поддержу. Как-то раз, чтобы заново подобрать оптимальные настройки -XX, удалил их все разом. Производительность увеличилась сразу на 40%. После этого я решил их не трогать, так как даже если я их сейчас подберу так, что разгоню систему процентов на 5, то в будущем все равно может оказаться, что эти настройки замедляют систему.


    1. 23derevo
      12.04.2017 13:24
      +2

      люто, бешено плюсую.


  1. moonster
    12.04.2017 14:37
    +2

    Сколько раз на технических интервью на меня косо смотрели, когда говорил, что всякие -XX нужно трогать только от безысходности, если других способов улучшить производительность нет совсем. Теперь есть пруф, что так и надо. )


    1. vladimir_dolzhenko
      12.04.2017 14:43
      +3

      -XX:+MakeMeAlwaysHappy


  1. lany
    13.04.2017 02:59
    +2

    System.arraycopy — довольно сложная штука и по одному тесту я бы вывод не делал. Например, если массивы разных типов (копируем из Object[] в String[]), то она вставит проверку типа каждого элемента, ни о какой векторизации речи не идёт. Зато, например, при копировании из String[] в CharSequence[] проверка типов пропадёт. Кроме того, если вы копируете какой-то диапазон элементов и статически не определяется, что выход за границы не происходит, для arraycopy совершенно точно проверка границ будет снаружи цикла. А вот удастся ли JIT-компилятору вынести её наружу из ручного цикла — я не уверен на 100%. В общем, я бы не стал всегда полагаться, что ручное копирование будет так же эффективно.


    Вообще сам код интринсика в C2 вот он. Там 200 строк основного метода и это только верхушка айсберга, он вызывает ещё кучу вспомогательных. Можете посмотреть, сколько там разных веток и случаев.


    1. vladimir_dolzhenko
      13.04.2017 09:55
      +1

      И ещё есть узкоспециализированные реализации для примитивов — если я правильно понял, то arrayCopy для long[] вырождается в такую простыню (для x86) — и вот их оптимизировать это скорее гадание на кофейной гуще — что лучше сработает векторизация, или intrinsic.


  1. SelecTee
    13.04.2017 11:14
    +1

    Optimize empty HashMap and ArrayList коммит в openjdk

    и тут же откатили

    открыл щас JDK 1.8.0.121

        /**
         * Shared empty array instance used for default sized empty instances. We
         * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
         * first element is added.
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
        /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    


    1. vladimir_dolzhenko
      13.04.2017 11:16

      это уже не тут же, но это ещё лишний раз показывает то, что оптимизации от одной minor-версии до другой minor-версии могут как протухать, так и жить и эволюционировать


    1. vladimir_dolzhenko
      13.04.2017 11:27

      и потом — через 10 дней накатили снова


  1. lany
    13.04.2017 17:48
    +1

    А вот и устаревшие внутри-JDK-шные советы по оптимизации правят:


    -        // (3)getClass().getClassLoader0() is expensive
    -        // (4)There might be a timing gap in isTrusted setting. getClassLoader0()
    +        // (3)There might be a timing gap in isTrusted setting. getClassLoader0()