lany
+1

Подозреваю, что вы value types за аллокацию на стеке приняли. В девятке их всё равно не будет. И аллокация на стеке ортогональна value-типам. Джава уже давно умеет не то что на стеке аллоцировать, а в регистры раскидывать объект вообще без аллокации. Или в принципе избавиться от объекта, если позволяет ситуация.

lany
0

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

lany
+1

А вот почему не стоит переходить. Компилятор до ума довели, вопросов нет. Но поддержка в IDE страдает. Каждый раз, когда я влезаю в Котлин, напарываюсь на какую-нибудь неприятность вроде этой. Тонна Java-инспекций в Котлине не работает, хотя имеет смысл, если активно используешь всякие Java API.

lany
+3

С аллокацией на стеке? Вы про что и кто это обещал? Что-то из разряда "слышал звон".

lany
0
Если вижу в заголовке восклицательные знаки и надписи, что все сломалось и ничего не работает, ничего не запускается, то да, на такие вопросы стараюсь отвечать в первую очередь.

Лайфхак: хотите, чтобы на ваш запрос в саппорт быстрее ответили? Добавьте больше восклицательных знаков и в сабжекте напишите, что всё сломалось.

lany
0

Заметь, кстати, что аллокация гигабайта каждые три секунды замедлила программу всего на 2.5% (что вообще не превышает погрешность) по сравнению с программой без аллокаций. Этот комментарий для тех, кто кричит, что garbage collector'ы тормозят безбожно :-)

lany
0

У нас за 377 рублей на яндексе можно не менее 20 километров проехать. Если в москвах прожить нельзя, значит новосибирские таксисты в глубокий минус работают :-)

lany
+1

Новосибирские яндекс-таксисты ноют, что по 25% отдают. Может врут, конечно, я свечку не держал. Говорят, дальние поездки совсем не выгодны. Предпочитают договориться с пассажиром и закончить поездку досрочно, чтобы меньше отдавать.

lany
+4

Плюсанул статью авансом, но у меня к ней много придирок :-)


Во-первых, измерять такую системно-зависимую штуку явно стоит на разных ОС. И уж как минимум, упомянуть в статье, к какой ОС относятся результаты.


Во-вторых, вывод, что clock.instant() дороже, чем clockMicros, неверен. На графиках диапазон результатов с учётом рисок погрешности существенно пересекается, однозначно говорить о победе одного варианта над другим нельзя.


В-третьих, если хочется убедиться, что аллокаций не происходит (то есть escape-анализ работает), надо аллокации и измерять (например, через -prof:gc), а не по наносекундам делать косвенный вывод. Вполне возможно, что аллокация и происходит, просто tlab-аллокация очень быстрая (об этом ты сам говорил на JPoint), и разница не превышает погрешность. А GC тоже может работать очень быстро, так как выживших объектов вообще нет. Scavenger посещает только живые объекты от рутсета, которые попадают в то же поколение (определяется сравнением адреса с границами поколения). Как только все живые объекты (инфраструктура джавы и самого бенчмарка) запромотились на первой сборке, каждая последующая минорная сборка будет выкидывать весь Eden целиком, даже не пробегаясь по нему, а просто проверяя, что ни один объект из рутсета не указывает в Eden. Конечно, если голосовать вслепую, я всё же верю в escape-анализ здесь, но инженер не должен верить, а должен измерять :-)


Наконец, даже если ClockUTC.micros() выдал 1494398039238498, нельзя сделать вывод, что точность повысилась. Кто знает, может последние цифры просто рандомные или обновляются редко? Здесь полезен бенчмарк на гранулярность, который наш любимый Лёша делал несколько лет назад. Вот для nanotime на Windows у Алексея получаются интересные результаты. Хотя там не нолики на конце, но гранулярность вовсе не 1 нс, а где-то 370 нс. Подобная информация прекрасно дополнила бы эту статью. Вообще, конечно, раз проводишь исследование, стоит ознакомиться с предыдущими работами на эту тему, и Nanotrusting the Nanotime тут просто напрашивается быть номером один в литературном обзоре :-)

lany
0

В данном случае, если вы требуете конкретную сигнатуру, как раз лучше сделать инлайнинг, чем мутные аннотации:


inline BigInteger multiply(long x, long y) throws Throwable {
  return (BigInteger)Intrinsics.invokedynamic(MULT, "foo", x, y);
}

Однако часть рулезности invokedynamic ещё и в самой полиморфной сигнатуре. К примеру то самое, о чём я писал — если переданы два инта, то можно дополнительно оптимизировать, а здесь получится, что они сразу расширятся до лонгов.

lany
0

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

lany
0

Да, в текущем прототипе есть такая проблема. Но ведь никто не сказал, что так и будет всегда. Если есть идеи лучше, высказывайтесь в мейлинг-листе! От Intrinsics. уже сейчас легко избавиться статическим импортом. Я пытаюсь протолкнуть мысль, что BootstrapSpecifier может быть параметризован возвращаемым типом, и тогда удастся обойтись без каста, что уже полдела. Пока не проталкивается.

lany
0

Ну можно как минимум hardware performance counters заиспользовать, они стабильнее. Количество выполненных инструкций, например, считать. Хотя суперстабильности всё равно не будет. JIT-компилятор в Java, например, легко выдаст неустойчивый результат в зависимости от гонки между потоками. То есть гонка на ранней стадии работы программы повлияет на сгенерированный ассемблерный код, который будет с вами до конца работы программы и может дать вполне ощутимую разницу в производительности.


А тут ещё в IO многое упирается. На рамдиске наверняка будут существенно другие результаты. Поэтому замеры чисто процессорного времени непрактичны.

lany
0

О, DrJava. Забавная поделка, никогда не слышал о ней.

lany
+4

Почему же не были? В чём-то согласились с RedHat, в чём-то нашли компромисс. Какие-то вещи технически сложны и при этом могут быть реализованы в будущих версиях, поэтому их отложили. Какие-то вещи концептуально не подходят для JPMS.


Например, RedHat топит за циклические зависимости. Конечно, с точки зрения поддержки легаси-кода это может быть важно, но с точки зрения нормальной архитектуры — бред. Не можешь избавиться от циклов — не переходи на JPMS. Тут, я считаю, Марк правильно не прогибается. Вот pjBooms рассказывал в красках на JBreak, что не так с циклическими зависимостями в OSGi. Вроде бы они есть, но это хрупкий песчаный замок: порядок инициализации циклически-зависимых OSGi-модулей оказался зависим от неспецифицированных деталей реализации процесса загрузки классов в HotSpot. Приложения, которые хотят циклические зависимости, как правило, ещё и на этот порядок закладываются. Немного меняешь процесс загрузки классов в JVM (не нарушая спецификацию), и большие OSGi-приложения вроде Eclipse (привет IBM) дохнут, не успев запуститься. А другие большие приложения, но не OSGi (типа IntelliJ IDEA) продолжают работать. И если я правильно понял, в рамках спецификации OSGi проблема в принципе неразрешима. Если тащить циклические зависимости в JPMS, придётся тащить и эту проблему. Даже если предположить, что в JPMS протащат циклические зависимости, никто не будет реализовывать Jigsaw, чтобы он эмулировал в точности поведение OSGi. А значит, существующие OSGi-приложения всё равно развалятся и их всё равно придётся переделывать. А раз переделывать, то лучше и циклы выкорчевать, тогда порядок инициализации станет стабильным.

lany
+1

Потому что они высказались. По крайней мере RedHat высказывал конкретные претензии множество раз. Почитайте мейлинг-лист jpms-spec-observers. В первую очередь сообщения от Дэвида Ллойда и ответы от Марка. Там уже не первый год отнюдь не дружелюбное общение.

lany
+6

Из какой конкретно строки в оригинальной статье вы сделали вывод, что «Выход Java 9 — новой версии платформы — был отложен»? Что-то я не вижу этого в явном виде. Более того, статья написана неделю назад, задолго до конца голосования, когда исход был ещё неясен. Официально об откладывании Java 9 никто пока не объявил. Захотелось заголовок погромче сделать? :-)

lany
0

Каждое упоминание Intrinsics.invokedynamic вставляет инструкцию invokedynamic, а она уже сохраняет точку вызова, привязанную к конкретному месту в байткоде, что по большому счёту есть лениво инициализируемое статическое поле в этом классе.


MULT для удобства сделан, можно его заинлайнить в точки использования, эффект будет тот же, только ещё больше энтерпрайза.

lany
0

Ну так напиши в мейлинг-лист о своём наболевшем :-) Как я понимаю, AOT-компиляция Oracle тоже интересует.

lany
+5

Ещё есть волшебный ключик --permit-illegal-access.

lany
+3

Если вы мыслите в масштабах Вселенной, вас больно укусит теория относительности, ещё до машин времени. Абсолютной шкалы времени в принципе нет, и с этим надо жить.

lany
+1

Альтернативный вариант:


if(x == NULL) { ... }
else {
  assert(x != NULL);
  ...
}

Так вы и документировали вторую ветку (на случай если первая разрастётся), и добавили отладочную проверку. Можно бы было ещё так:


if(x == NULL) { ... }
else {
  assert(x != NULL); // PVS-Studio: static assert
  ...
}

Комментарий специального вида после ассерта должен указывать, что PVS-Studio в состоянии статически вывести, что этот ассерт всегда истинный. Если со временем окажется, что это не так (например, условие в верхнем if поменяется, а про else все забудут), то PVS-Studio начнёт ругаться, что больше не может вывести истинность утверждения, помеченного комментарием.

lany
+2

Нет-нет, я не с целью поддеть вас, у меня исключительно профессиональный интерес ;-)

lany
+1

Тогда ещё вопрос:


int *x = ...
if(x == NULL) { ... }
else if(x != NULL) { ... }

Тут тоже молчите, что x != NULL всегда истинно?

lany
+1
Член off является переменной беззнакового типа, а значит он всегда больше или равен нулю. Таким образом, условие (!(ce->off >= 0)) всегда ложно.

На это автор кода может возразить, что так задумано на случай, если тип поменяется в будущем на знаковый. Учитывая, что тип поля объявлен не напрямую, а через typedef, шансы на это вполне себе возрастают. Если дальше пойдёт какая-то магия с указателями, то лучше уж при потенциальной замене типа в будущем на goto fail пойти, чем на UB напороться.


Кстати, вопрос вам. Такой код:


int cmp = compare(a, b);
if(cmp < 0) { ... }
else if(cmp == 0) { ... }
else if(cmp > 0) { ... }

Будет ли PVS-Studio ругаться в последней строчке, что условие всегда истинно?

lany
0

Это если возвращаемое значение в блоке кода одно и нет нелокального control flow. А если нет? Extract method object обычно генерирует результат далёкий от оптимального. И даже если и с возвращаемым значением всё нормально, сигнатура результирующего метода может оказаться перегруженной и запутанной. Чтобы сделать всё красиво, зачастую всё равно думать приходится. Да и не весь код в мире написан в те годы, когда в IDE Extract method уже хорошо работал.

lany
0

О, мнения разошлись :-) Я всё же с вами не соглашусь. Когда человек пишет что-то руками, он существенно больше внимания обращает на то, что пишет.

lany
0

Обычно да, но конкретно тут не подойдёт: нужно ещё выполнить некоторый код безусловно после проверки. В принципе это всё сигнализирует, что хорошо бы весь метод порефакторить и разбить на более мелкие, но в реальной жизни не бывает так, что весь код выглядит идеально.

lany
0

Вот, кстати, похожий конкретный пример из реального кода. Было:


if (elements.length > 0 && matchContext.getPattern()
        .getStrategy().continueMatching(elements[0].getParent())) ...

Стало:


PsiElement parent = elements[0].getParent();
if (elements.length > 0 && matchContext.getPattern()
        .getStrategy().continueMatching(parent != null ? parent : elements[0])) ...

Так как захотели elements[0].getParent() переиспользовать, вынесли в переменную с помощью автоматического рефакторинга. Но так как посреди условия нельзя объявить переменную, IDE объявила её перед условием, в том числе перед важной короткозамкнутой проверкой elements.length > 0. Думаю, если б человек вручную переменную создал, он бы заметил проблему.

lany
0
Тест считался ненадёжным, если показывал хотя бы один ненадёжный результат в течение недели.

Тут непонятно, а что такое "ненадёжный результат". Скажем, произошла регрессия, тест попадал два дня, потом пофиксали регрессию, тест перестал падать. Это явно не flaky. У TeamCity есть четыре эвристики для определения flaky-тестов:


  • Частая смена состояния между "падает" и "проходит" в одной и той же билд-конфигурации и/или на одном и том же агенте за определённый период времени (как я понимаю, пороги настраиваются)
  • Различный результат для одного и того же коммита, но разных билд-конфигураций (например, под разной OS или на разном железе)
  • Различный результат при перезапуске билда без изменений из VCS
  • Различный результат при повторном прогоне теста внутри одного билда (например, если задан invocationCount для TestNG-теста)

А что в этой статье имелось в виду?

lany
0

Интересно, есть ли устоявшийся перевод flaky? Мы такие тесты называем "мигающими", а тут "ненадёжные". А ещё кто-нибудь как-нибудь называет?

lany
+1

Ну так я говорю не в смысле "бегом побежали добавлять", а в смысле что сравнивать надо беспристрастнее. Если добавление примитивов в RxJava — сотни часов работы, значит, не надо в табличку галочку про примитивы ставлять?


Параллелизм опять же. Позволяет ли RxJava разбивать источник на части и обрабатывать их независимо? Насколько я понимаю, у них только producer-consumer модель. Производительность может быть существенно разной. В общем, картина неполная и выглядит ангажировано.

lany
+2

Неочевидно, кому нужен operator fusion. Stream API разрабатывали ребята из JDK и параллельно шли допилы виртуальной машины с тем, чтобы дополнительный какой-то явный fusion был не нужен. Крайне редко вы напоретесь на ситуацию, когда два подряд идущих map или filter будут медленнее, чем один.


Ну и сравнение, понятно, производится по пунктам, интересным авторам сравнения. К примеру, RxJava 1 боксит просто нереально много и тормозит из-за этого. RxJava 2 уже умнее, но всё равно прямой поддержки небоксированных типов нету. Где колонка с примитивной специализацией? Не хотелось ставить крестик в строчке с RxJava? Пользы в плане быстродействия от неё существенно больше, чем от operator fusion.

lany
+1

Правильно, я тоже предлагаю. Надо просто понимать, что автоматический исправлятор тоже может накосячить. Голову отключать никогда не надо и весь свой код перечитывать перед коммитом обязательно. Ну и статический анализ использовать — он заметит то, чего вы не заметите :-)

lany
+5

Пропустил статью :-) Мне регулярным евангелизмом статического анализа лень заниматься, так что дарю вам ещё довод, раз уж вы занимаетесь. Ошибку вам может внести сама IDE, если быть невнимательным. Пусть у вас такой Java-код:


void processList(boolean flag, List<String> list) {
    if(flag) {
        if(list.isEmpty()) return;
        if(list.get(0).equals("foo")) {
            System.out.println("...some long processing...");
            // ... many lines of code
        }
    } else {
        if(list.size() < 3) return;
        if(list.get(0).equals("bar")) { // курсор здесь
            System.out.println("...another long processing using "+list.get(0));
            // ... many lines of code
        }
    }
}

С этим кодом всё нормально, ошибок нет. Вы его когда-то написали целиком с нуля, а так как вы профессиональный программист, то ошибок вы не допускаете. Но посмотрев на код уже после написания, вы замечаете, что как-то list.get(0) повторяется в соседних строчках, некрасиво. Надо бы в переменную вынести. Вы активируете соответствующий рефакторинг в IDE, встав на это выражение. IDE вас спрашивает: «вам только это вхождение заменить на новую переменную или все?» Вы, естественно, говорите, все, и IDE генерирует такой код:


void processList(boolean flag, List<String> list) {
    String firstElement = list.get(0);
    if(flag) {
        if(list.isEmpty()) return;
        if(firstElement.equals("foo")) {
            System.out.println("...some long processing...");
            // ... many lines of code
        }
    } else {
        if(list.size() < 3) return;
        if(firstElement.equals("bar")) {
            System.out.println("...another long processing using "+ firstElement);
            // ... many lines of code
        }
    }
}

И код стал некорректным, потому что было ещё одно вхождение list.get(0) в другой ветке и IDE его тоже вынесла в переменную, создав её до необходимых проверок на длину списка. Если слишком довериться IDE и не обратить внимания на то, что она сделала, можно здорово обжечься. Я недавно написал диагностику, которая отлавливает статически такой код. В нашем проекте нашлось несколько мест, где он появился именно в результате применения рефакторинга.

lany
0

Тесты производительности замеряют не только собственно производительность, но и создание/планировку/сбор результатов CompletableFuture. Пробовали @Group, @GroupThreads?

lany
+5

Вообще можно было и на VarHandle написать, Unsafe как-то выходит из моды ;-)

lany
+2

Не, не равны. Эклипс всегда может отмазаться, что они тут ни при чём, это джава. А IDEA не может — у неё ж свой форк джавы и баги в джаве тоже можно фиксать :-)

lany
0
Помню, мой друг Майкл Франц, профессор в UC Irvine, показал, что у них есть алгоритм порядка 4-й степени сложности, если вы отправите этот сложный тест, что-то наподобие байт-кода верификатора, это займёт неожиданно много времени. И на верификаторы можно совершать атаки типа отказа в обслуживании (DoS).

Перевод корявый, смысл в том, что можно задосить верификатор, если послать ему специально сконструированную лапшу из байт-кода. Но вообще я хотел сказать, что в Java эту проблему давным-давно решили с помощью split-верификатора. Его обязательно использовать для байткода Java 7 и выше (которая вышла 6 лет назад), а сам split-верификатор появился ещё раньше. Не вижу, почему нельзя было адаптировать эту идею к WebAssembly.

lany
0
Грубо говоря, там потратил 10 дней и получил профит 100 рублей, а здесь потратишь 100 дней и получишь 20 рублей.

Вы совершенно не представляете, сколько на самом деле стоило добавить иконки. Не так-то очевидно, какая из этих задач сложнее. И иконки просили четыре года, а тут только полгода просят. Скорость набора голосов выше.