Обновить
170
0
Андрей @apangin

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

Отправить сообщение
Типичный сценарий использования ThreadLocal — делать его самого static.
Нестатичный ThreadLocal — экзотика, которая как раз и приводит к описанным проблемам.

Кстати, вместо getOrCreate() есть ThreadLocal.initialValue().
Мне нравится ход ваших мыслей, но насчет анонимных классов все равно не соглашусь.
Анонимный класс — вполне определенная конструкция языка, и лямбды таковой не являются.
Может, хотя бы это вас убедит:
  Runnable anonymous = new Runnable() {
      @Override
      public void run() {
          System.out.println("Hello from anonymous");
      }
  };

  Runnable lambda = () -> System.out.println("Hello from lambda");

  System.out.println(anonymous.getClass().isAnonymousClass());  // true
  System.out.println(lambda.getClass().isAnonymousClass());  // false
Не соглашусь.

Во-первых, с помощью лямбд можно вполне себе элегантно реализовать функции высшего порядка:
Function<Double, Double> f = Math::sin;
Function<Double, Double> g = f.andThen(Math::sqrt).andThen((x) -> 2 * x);
System.out.println(g.apply(Math.PI / 6));

Во-вторых, никаких анонимных классов нет.
Лямбды компилируются в invokedynamic со вспомогательными методами в том же классе.
И все-таки путаете.
  1. synchronized не порождает nop в байткоде;
  2. JIT иногда генерирует nop в машинном коде исключительно в целях выравнивания, а именно:
    a) выравнивания backward branch targets в горячих циклах;
    b) выравнивания точки входа в метод (т.н. verified entry) для возможности атомарного патчинга, поскольку запись машинного слова в память на x86 гарантировано атомарна только по выровненному адресу.
Пожимаю руку: хороший анализ и правильные выводы. Многие после первого же теста сделали бы вывод, что байткод NOP выполняется N наносекунд :)
По вопросам:
1. Сложно представить, во что вообще JIT может скомпилировать NOP, кроме пустого места. Разве что в машинную инструкцию nop, на которую, в свою очередь, даже процессор не тратит ни такта.
2. Виртуальные методы ни при чем. Во-первых, в данном случае вызов будет девиртуализован. Во-вторых, размер таблицы виртуальных методов зависит только от количества методов, но не от их размера.
3. 8000 и 325 — размеры все-таки в байтах, а не в инструкциях. Почему именно такие, наверное, уже никто не вспомнит — за последние лет 7 они не менялись.
Кстати говоря, я на эти лимиты наталкивался в реальном коде. Исследуя, почему зверски тормозит GeoIP библиотека MaxMind, обнаружил, что в методе regionNameByCode есть огромный switch со всеми возможными вариантами регионов, в результате чего этот метод вообще никогда не компилировался.
Порадовал чем? Там же очередной лжебенчмарк, замеряющий время интерпретации + время JIT-компиляции. Небось, еще и на клиентской 32-битной JVM. Если измерять по-честному, разница получается мизерная, причем то в пользу одного, то в пользу другого варианта, в зависимости от опций JVM (UseCompressedOops).

[sarcasm]Чего уж там, давайте сразу на 10 итераций ручной loop unrolling сделаем — глядишь, будет еще эффективнее.[/sarcasm]
Ничего загадочного. В «оптимизированном» варианте просто-напросто больше операций. Гораздо больше.
Если в простом примере на одной итерации внутреннего цикла, грубо говоря, делается один store (не зависящий от предыдущих), то во втором примере на каждой итерации делается:
  1. load указателя на j-ю строку;
  2. load длинны массива;
  3. bounds check;
  4. и два store.
причем эти операции не могут выполняться конвейерно, т.к. зависят от результатов предыдущих операций.
Добавим ко всему этому, что обращение к памяти сильно разнесено, а, значит, кеши и префетчи не работают, куча TLB промахов и всё такое.
У Оракла много разных JVM, так что лучше уточнять, о которой речь.

Про Jazelle писал: раз, два. JVM с JIT ее не используют, т.к. от нее только хуже.

В основе Java SE Embedded лежит Hotspot JVM. Выпускается в нескольких сборках — в зависимости от версии архитектуры (ARM v5, v6, v7) и поддержки VFP. Кроме того, JVM в рантайме определяет поддерживаемый набор инструкций для более оптимальной JIT-компиляции. В частности, версия, собранная под ARM v6, может генерировать код с использованием ARM v7 инструкций.
Полноценно поддерживаются многоядерные CPU. Навороченный JIT-компилятор с промежуточным представлением, глубоким инлайнингом, спекулятивной девиртуализацией и т.д.

В основе Java ME лежит CLDC HI JVM. Тоже выпускается в разных сборках для разных платформ. Прежде всего заточена на маленькие объемы памяти. Поэтому большинство фич включаются/выключаются на этапе сборки под конкретную платформу. Также имеет JIT компилятор, заточенный под ARM, хотя и гораздо проще (можно сказать, однопроходный). Более того, есть сборки и без JIT-компилятора вовсе. Подозреваю, что рекорды по занимаемому месту, достигнуты именно на такой сборке.
1. Oracle Java SE Embedded (в отличие от OpenJDK), действительно, представляет собой оптимизированный под ARM порт JDK, включая С1 (aka Client) JIT компилятор. Тот же самый, что на «большой» Java, только с ARM бэкендом. Поэтому и производительность соответствующая.
Не знаю, как сейчас, но раньше он обгонял Dalvik VM в 2-3 раза на идентичном железе.

2. На слово «java» в cpu features даже не смотрите, оно ничего не значит. Вернее, оно означает, что процессор поддерживает технологию Jazelle DBX, которая безнадежно устарела еще лет 8 назад. Никакая JVM с JIT компилятором ее не использует.
но, к сожалению, документация дает неоднозначное представление о данном вопросе

Может, в первую очередь нужно зарепортить баг всё-таки на документацию?
И чего? :) Как нам это поможет отличить объект вложенного класса от enum константы?

enum E {
    first,
    second {
        public String toString() {
            return "not first";
        }
    };

    static class Helper { }
    
    public static final Helper helper = new Helper();
}


E.second.getClass().getEnclosingClass().isEnum() вернет true;
E.helper.getClass().getEnclosingClass().isEnum() вернет true.

Я хотел сказать, что проверять, является ли объект enum константой, через obj.getClass().isEnum() — неправильно.
А правильно будет obj instanceof Enum.
С enum еще интересней:
enum E {
    first,
    second {
        public String toString() {
            return "not first";
        }
    }
}

E.first.getClass().isEnum() вернет true;
E.second.getClass().isEnum() вернет false!
На сервере с бизнес-логикой — всего 1% прироста к производительности. На синтетических тестах на сериализацию — порядка 30%.
Если не секрет, то, реально интересно, что послужило толчком к поиску именно такого, скажем прямо, нетрадиционного решения?

Любознательность :) На самом деле, острой необходимости и не было. Пару раз возникало желание включить/выключить на лету TraceClassLoading, TraceClassUnloading и PrintCompilation. Или еще один раз проверить эффект от смены hashCode под нагрузкой. Но в итоге на production серверы все равно такой хак решили не внедрять.
Проверил у себя — получилось успешно. На каком примере и на какой версии SIGSEGV валится?

С OutOfMemoryError и StackOverflowError есть особенность, что они преаллоцируются на старте JVM (иначе при попытке выполнения честного конструктора может не хватить памяти или стека). Поэтому, например, если запустили Java с -XX:-StackTraceInThrowable, а потом в рантайме выставили в true, то эти ошибки все равно вылетят без трейса.
Не выйдет.
-client / -server вообще не являются флагами JVM.
До JDK 7 эти параметры разбирал launcher (иначе говоря, запускаемый бинарник) и выбирал соответствующую версию libjvm.

Начиная с JDK 7 «серверная» библиотека libjvm включает в себя оба компилятора: C1 и C2. Есть возможность включить так называемую ступенчатую компиляцию ключиком -XX:+TieredCompilation. Но только из командной строки. Установка флага в runtime ни к чему не приведет, т.к. для поддержки ступенчатой компиляции требуется модифицированная версия интерпретатора байткода, которая генерируется лишь единожды при запуске JVM.
Разумеется, не все флаги получится менять на лету. Многие из них обрабатываются лишь один раз на старте JVM. В частности, все, что касается размеров Java Heap, выбора сборщика мусора и т.п. Эффект будет лишь от смены тех флагов, которые проверяются постоянно, например, различные опции трассировки. По-хорошему, все их надо сделать manageable, тогда и не придется плясать с бубном.
Ага. Зато есть, о чем потом на конференции рассказать :) Вот, на ближайшем Joker как раз доклад на тему, как разбирать последствия таких выстрелов :)
Да: когда очередной put() делал слишком много проб, таблица ресайзилась. Критерий «слишком много» каждый может определить сам — идея лишь в том, чтобы поддерживать производительность мапы на приемлемом уровне. Понятно, что с произвольными данными со стороны такой фокус не пройдет. Но в нашем случае, например, порог достигался при более чем 90% заполненности таблицы.
Заниматься мелочёвкой вроде перестановки условий и ручного разворачивания циклов я бы не стал, тем более, что JIT в определённой степени это сам делает. Много на этом не сэкономить: один единственный cache miss будет стоить сотни тактов и перекроет всю выгоду от микрооптимизаций.

Я в свое время экспериментировал с разными алгоритмами open-address hashing и остановился на одной хеш-функции с вариацией quadratic probing (с увеличивающимися шагами: 1, 2, 3...). Такой вариант на порядок лучше linear probing по среднему числу итераций, и, в отличие от двойного хеширования, обладает хоть каким-то cache locality.

К loadFactor'у у меня был другой подход: полное отсутствие loadFactor как такового :) Вместо него считалось число итераций, и, когда оно переваливало за некий порог (скажем, capacity ^ 1/3), таблица увеличивалась.

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Работает в
Зарегистрирован
Активность