Автовекторизация Java

Я пытаюсь понять, когда JDK будет автоматически векторизоваться. У меня есть следующий набор вопросов (несмотря на гугление, чтение, эксперименты и т.д.). Учитывая простой цикл следующим образом:

for(int i=0; size = size(); i < size; i++) {
   a[i] = b[i] * c[i];
   method1();
   // someObject.method2();
   // someHashMap.put(b[i], c[i]);
}
  1. Почему необходимо, чтобы вызов метода «method1» (который появляется в цикле) был встроен для автовекторизации? (Я не могу понять, почему это должно быть необходимым....)
  2. Возможно, это "глупый" вопрос, но что, если "someObject.method2()" раскомментировать. (И давайте предположим, что method2 - это огромный метод, т.е. много строк). Это также предотвратит автовекторизацию? Что, если бы method2 был крошечным методом (например, всего 1 или 2 строки и т. д.?)
  3. Что, если бы строка «someHashMap» была раскомментирована? Не приведет ли тот факт, что у нас есть объект/переменная, которые будут общими для всех SIMD, автовекторизация также не удастся? (Я не понимаю, как это могло бы работать, если бы jdk каким-то образом не вставлял ключевое слово «syncronization» автоматически при доступе к общему объекту/var «someHashMap»
  4. Мне кажется, что «потоковый» интерфейс решит проблему, подразумеваемую в вопросе № 3 непосредственно выше, поскольку логика «сборщика» в потоках автоматически позаботится об объединении отдельных хэш-карт, и поэтому нам не понадобится «синхронизированное» слово. (И в целом кажется, что потоковый API — это идеальный API, позволяющий jdk автоматически использовать автовекторизацию, если при создании потокового кода нет «внешних переменных» (т.е. нет побочных эффектов). /jit компилятор автоматически выполняет автовекторизацию в результате, когда код написан с использованием стандартного потокового интерфейса?Если нет, то не имеет ли смысла это делать (возможно, в будущей версии jdk или, возможно, jdk от какого-то другого поставщика?)
  5. Если тело цикла содержит много-много операторов if и т. д. (множество ветвлений и, скажем далее, что каждая ветвь выполняет много вычислений), означает ли это, что а) автовекторизация, вероятно, ПЛОХАЯ идея (точно так же, как это было бы для графического процессора ) и б) компилятор jit достаточно умен, чтобы определить, что автовекторизация - плохая идея, и поэтому он не будет автовекторизировать?
  6. В настоящее время я использую Oracle jdk8, но меняются ли ответы выше, если вы используете jdk9 или jdk10 и т. д.?

person Jonathan Sylvester    schedule 03.09.2018    source источник
comment
Пожалуйста, взгляните на этот вопрос. Это зависит от версии Java и JIT-компилятора. Еще одно хорошее объяснение здесь.   -  person ltlBeBoy    schedule 03.09.2018
comment
Если method1() может изменить a[], b[] или c[], то, очевидно, он должен быть встроенным. Если escape-анализ может доказать, что массивы являются частными, то я уверен, что вы могли бы автоматически векторизовать и вызывать функцию 4 раза для каждого вектора SIMD, если бы ваш компилятор был достаточно умен, чтобы сделать это. Однако автовекторизация в JIT-компиляторе уже сложна (потому что она должна компилироваться быстро) по сравнению с опережающим C-компилятором.   -  person Peter Cordes    schedule 03.09.2018
comment
@Peter - я бы не сказал, что это очевидно - на самом деле это скорее ограничение текущего компилятора Java: он не выполняет IPA, кроме как косвенно через встраивание. Конечно, более сложный компилятор мог бы проанализировать функцию на наличие различных атрибутов, таких как чистота, и использовать их для оптимизации сайтов вызовов даже без встраивания. Некоторые компиляторы (например, GCC) делают это, и в принципе в Java это проще, поскольку у вас нет всей этой проблемы интерпозиции.   -  person BeeOnRope    schedule 04.09.2018


Ответы (1)


Чтобы ответить на ваш вопрос (1), в принципе, компилятор Java мог бы оптимизировать при наличии невстроенного вызова method1(), если он проанализировал method1() и определил, что он не имеет побочных эффектов. это повлияет на автоматическую векторизацию. В частности, компилятор мог доказать, что метод был «константным» (без побочных эффектов и без чтения из глобальной памяти), что в целом позволило бы провести множество оптимизаций на месте вызова без встраивания. Возможно, это также могло бы доказать более ограниченные свойства, такие как запрет на чтение или запись в массивы определенного типа, чего также было бы достаточно, чтобы в этом случае можно было продолжить автоматическую векторизацию.

Однако на практике я не знаю ни одного компилятора Java, который мог бы выполнить эту оптимизацию сегодня. Если верить этому ответу, в Hotspot: "вызов [не встроенного] метода обычно непрозрачен для JIT-компилятор." Большинство компиляторов Java так или иначе основаны на Hotspot, поэтому я не ожидаю, что существует сложный компилятор Java, который может сделать это, если Hotspot не может.

Этот ответ также охватывает некоторые причины, по которым такой межпроцедурный анализ (IPA), вероятно, будет трудным и не особенно полезным. В частности, методы, с помощью которых можно доказать нетривиальные вещи, часто достаточно малы, чтобы их все равно встраивали. Я не уверен, согласен ли я полностью: можно также утверждать, что Java агрессивно встраивает отчасти потому, что он не выполняет IPA, поэтому сильный IPA, возможно, откроет возможность делать меньше встраивания и, следовательно, уменьшить Объем кода среды выполнения и время JIT.

Другие варианты метода, о которых вы спрашиваете в (2) или (3), ничего не меняют: компилятору все равно потребуется IPA, чтобы разрешить его векторизацию, и, насколько я знаю, у компиляторов Java его нет.

(4) и (5) кажется, что их следует задавать как совершенно разные вопросы.

О (6) Я не думаю, что он изменился, но это хороший вопрос для списков рассылки OpenJDK: я думаю, вы получите хороший ответ.

Наконец, стоит отметить, что даже в отсутствие IPA и ничего не зная о method1(), компилятор может оптимизировать математику для a, b и c, если он сможет доказать, что ни один из них не сбежал. Хотя в целом это кажется довольно бесполезным: это означало бы, что все эти переменные были бы выделены в этой функции (или какой-то функции, встроенной в эту), тогда как я полагаю, что в большинстве реалистичных сценариев по крайней мере одна из трех передается в вызывающим абонентом.

person BeeOnRope    schedule 03.09.2018
comment
Раньше существовал gcj, опережающий внешний интерфейс компилятора Java для gcc. Я предполагаю, что он может выполнять весь спектр межпроцедурных оптимизаций gcc. Но он заброшен, последний выпуск с gcc6.4 в 2017 году и некоторое время до этого находился в режиме только обслуживания, поэтому больше не считается современным компилятором Java. - person Peter Cordes; 04.09.2018