Влияет ли модификатор Java strictfp на современные процессоры?
Я знаю значение strictfp
модификатор методов (и классов), согласно JLS:
Эффект модификатора strictfp заключается в том, чтобы сделать все выражения с плавающей запятой или двойные выражения в теле метода явным образом строгим по FP (§15.4).
JLS 15.4 FP-строгие выражения:
В выражении строгого FP все промежуточные значения должны быть элементами набора значений с плавающей запятой или набора двойных значений, подразумевая, что результаты всех выражений строгого FP должны быть теми, которые предсказаны арифметикой IEEE 754 для операндов, представленных с использованием одинарного и двойного форматов.,
В выражении, которое не является строгим по FP, предоставляется некоторая свобода для реализации, чтобы использовать расширенный диапазон показателей для представления промежуточных результатов; общий эффект, грубо говоря, заключается в том, что вычисление может дать "правильный ответ" в ситуациях, когда исключительное использование набора значений с плавающей запятой или набора двойных значений может привести к переполнению или недостаточному заполнению.
Я пытался придумать способ получить реальную разницу между выражением в strictfp
метод и тот, который не strictfp
, Я пробовал это на двух ноутбуках, один с процессором Intel Core i3 и один с процессором Intel Core i7. И я не могу получить никакой разницы.
Многие посты предполагают, что родная с плавающей точкой, не используя strictfp
, может использовать 80-битные числа с плавающей запятой и иметь дополнительные представимые числа ниже наименьшего возможного java double (ближайшего к нулю) или выше максимально возможного 64-битного java double.
Я попробовал этот код ниже с и без strictfp
модификатор и он дает точно такие же результаты.
public static strictfp void withStrictFp() {
double v = Double.MAX_VALUE;
System.out.println(v * 1.0000001 / 1.0000001);
v = Double.MIN_VALUE;
System.out.println(v / 2 * 2);
}
На самом деле, я предполагаю, что любая разница будет проявляться только тогда, когда код скомпилирован в сборку, поэтому я запускаю его с -Xcomp
Аргумент JVM. Но без разницы.
Я нашел другой пост, объясняющий, как вы можете получить код сборки, сгенерированный HotSpot ( документация OpenJDK). Я запускаю свой код с java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
, Первое выражение (v * 1.0000001 / 1.0000001
) с strictfp
Модификатор, а также то же самое без него, компилируется в:
0x000000010f10a0a9: movsd -0xb1(%rip),%xmm0 # 0x000000010f10a000
; {section_word}
0x000000010f10a0b1: mulsd -0xb1(%rip),%xmm0 # 0x000000010f10a008
; {section_word}
0x000000010f10a0b9: divsd -0xb1(%rip),%xmm0 # 0x000000010f10a010
; {section_word}
В этом коде нет ничего, что урезало бы результат каждого шага до 64 бит, как я ожидал. Поиск документации movsd
, mulsd
а также divsd
все они упоминают, что эти (SSE) инструкции работают с 64-битными значениями с плавающей запятой, а не с 80-битными, как я ожидал. Таким образом, кажется логичным, что двойной набор значений, над которым работают эти инструкции, уже является набором значений IEEE 754, поэтому не будет никакой разницы между strictfp
и не имея этого.
Мои вопросы:
- Является ли этот анализ правильным? Я не часто использую сборку Intel, поэтому я не уверен в своем заключении.
- Существует ли какая-либо (другая) современная архитектура ЦП (которая имеет JVM), для которой есть разница между работой с и без
strictfp
модификатор?
1 ответ
Если под "современным" вы имеете в виду процессоры, поддерживающие тот тип инструкций SSE2, которые вы цитируете в своем вопросе и которые выдает ваш компиляторmulsd
, …), Тогда ответ - нет, strictfp
не имеет значения, потому что набор инструкций не позволяет воспользоваться отсутствием strictfp
, Доступные инструкции уже оптимальны для точных вычислений strictfp
, Другими словами, на таком современном процессоре вы получаете strictfp
семантика все время по одной цене.
Если под "современным" вы подразумеваете исторический 387 FPU, то можно наблюдать разницу, если промежуточное вычисление будет переполнено или переполнено в strictfp
режим (разница в том, что он может не переполняться или, при недостатке, сохранять больше битов точности, чем ожидалось).
Типичный strictfp
вычисления, скомпилированные для 387, будут выглядеть как сборка в этом ответе, с правильно размещенными умножениями на хорошо выбранные степени двойки, чтобы заставить underflow вести себя так же, как в двоичном IEEE 754. Обход результатов по 64-битной ячейке памяти затем устраняет переполнения.
То же вычисление составлено без strictfp
будет производить одну 387-ю инструкцию на одну базовую операцию, например только инструкцию умножения fmulp
для умножения на уровне источника. (387 был бы настроен на использование того же значения и ширины, что и бинарный 64, 53 бита, в начале программы.)