Bereiten Sie eine Umgebung vor, in der Java strictfp wirklich Sinn macht

Java hat einen Modifikator namens "strictfp". Ich habe die Code- und Ausführungsumgebung vorbereitet, deren Verhalten sich tatsächlich abhängig von "strictfp" ändert.

Maschine

** strictfp hat keine Bedeutung, wenn Java auf modernen Computern ausgeführt wird. ** Sollte sich gemäß IEEE mit oder ohne strictfp verhalten.

(Daher gibt es einen JEP, der versucht, ein IEEE-kompatibles Verhalten mit oder ohne "strictfp" zu erreichen: JEP 306: Immer strikte Gleitkommasemantik wiederherstellen / 306)))

strictfp ist sinnvoll für x86-basierte CPUs, auf denen SSE2 nicht implementiert ist. Im Fall von Intel ist SSE2 in Pentium 4 oder höher um 2000 implementiert. Wenn Sie also eine Maschine ohne SSE2 vorbereiten möchten, benötigen Sie eine Maschine früher.

Es ist jedoch schwierig, eine Maschine vorzubereiten, die älter als 20 Jahre ist (ich bin kein Retro-PC-Enthusiast). Daher werden wir die Emulation verwenden. Betrachten Sie zwei Emulatoren, Intel SDE und QEMU.

Unter den CPU-Typen, die von Intel SDE angegeben werden können, scheint es einen "-quark" zu geben, der SSE2 nicht implementiert. Die nächstkleinere Funktion ist "-p4", dh Pentium 4. Wenn Sie also eine Umgebung ohne SSE2 mit Intel SDE vorbereiten möchten, können Sie "-quark" auswählen.

Sie können in QEMU "-cpu pentium", "-cpu pentium2", "-cpu pentium3" usw. verwenden. Unter Linux ist dies praktisch, da Sie das Programm ausführen können, ohne eine virtuelle Maschine namens User Space Emulation vorzubereiten. Dieser Artikel verwendet auch Linux.

Die JVM verwendet Java 8 Update 261, das von Oracle vertrieben wird. Dieser Typ scheint mit Pentium II oder höher kompatibel zu sein. Ich habe nicht bestätigt, wie die neue JRE ist.

Programm

Es scheint, dass "strictfp" sinnvoll ist, wenn ein Überlauf oder Unterlauf mitten in einer Gleitkommaoperation auftritt. Wenn "strictfp" angegeben ist, verhält es sich gemäß IEEE, und wenn "strictfp" nicht angegeben ist, kann ein Überlauf oder Unterlauf vermieden oder ein anderer Wert zurückgegeben werden.

Hier,

Denn wir werden eine Version mit und ohne strictfp vorbereiten. Lassen Sie sie dann diese Werte für bestimmte Werte berechnen. Führen Sie für 3 Multiplikationen 100.000 Mal aus, in der Erwartung, dass die JIT-Kompilierung durchgeführt wird, und zeigen Sie die Werte davor und danach an.

class StrictfpTest
{
    static double multiplyDefault(double x, double y)
    {
        return x * y;
    }
    static strictfp double multiplyStrict(double x, double y)
    {
        return x * y;
    }
    static double multiplyThreeDoublesDefault(double x, double y, double z)
    {
        return x * y * z;
    }
    static strictfp double multiplyThreeDoublesStrict(double x, double y, double z)
    {
        return x * y * z;
    }
    static float multiplyThreeFloatsDefault(float x, float y, float z)
    {
        return x * y * z;
    }
    static strictfp float multiplyThreeFloatsStrict(float x, float y, float z)
    {
        return x * y * z;
    }
    public static void main(String[] args)
    {
        {
            double x = 0x1.00002fff0p0, y = 0x1.000000008p0;
            System.out.printf("multiplyDefault(%a, %a) = %a\n", x, y, multiplyDefault(x, y));
            System.out.printf("multiplyStrict(%a, %a) = %a\n", x, y, multiplyStrict(x, y));
        }
        {
            double x = 0x1.fffe0effffffep0, y = 0x1.0000000000001p0;
            System.out.printf("multiplyDefault(%a, %a) = %a\n", x, y, multiplyDefault(x, y));
            System.out.printf("multiplyStrict(%a, %a) = %a\n", x, y, multiplyStrict(x, y));
        }
        {
            double x = 0x1.fffe0effffffep-51, y = 0x1.0000000000001p-1000;
            System.out.printf("multiplyDefault(%a, %a) = %a\n", x, y, multiplyDefault(x, y));
            System.out.printf("multiplyStrict(%a, %a) = %a\n", x, y, multiplyStrict(x, y));
        }
        {
            double x = 0x1p-1000, y = 0x1p-1000, z = 0x1p1000;
            System.out.printf("multiplyThreeDoublesDefault(%a, %a, %a) = %a\n", x, y, z, multiplyThreeDoublesDefault(x, y, z));
            System.out.printf("multiplyThreeDoublesStrict(%a, %a, %a) = %a\n", x, y, z, multiplyThreeDoublesStrict(x, y, z));
            for (int i = 0; i < 100000; ++i) {
                multiplyThreeDoublesDefault(x, z, y);
                multiplyThreeDoublesStrict(x, z, y);
            }
            System.out.printf("multiplyThreeDoublesDefault(%a, %a, %a) = %a\n", x, y, z, multiplyThreeDoublesDefault(x, y, z));
            System.out.printf("multiplyThreeDoublesStrict(%a, %a, %a) = %a\n", x, y, z, multiplyThreeDoublesStrict(x, y, z));
        }
        {
            float x = 0x1p-100f, y = 0x1p-100f, z = 0x1p100f;
            System.out.printf("multiplyThreeFloatsDefault(%a, %a, %a) = %a\n", x, y, z, multiplyThreeFloatsDefault(x, y, z));
            System.out.printf("multiplyThreeFloatsStrict(%a, %a, %a) = %a\n", x, y, z, multiplyThreeFloatsStrict(x, y, z));
            for (int i = 0; i < 1000000; ++i) {
                multiplyThreeFloatsDefault(x, z, y);
                multiplyThreeFloatsStrict(x, z, y);
            }
            System.out.printf("multiplyThreeFloatsDefault(%a, %a, %a) = %a\n", x, y, z, multiplyThreeFloatsDefault(x, y, z));
            System.out.printf("multiplyThreeFloatsStrict(%a, %a, %a) = %a\n", x, y, z, multiplyThreeFloatsStrict(x, y, z));
        }
    }
}

Erstens ist das Ausführungsergebnis in der modernen Umgebung wie folgt. Ich habe es auf x86 \ _64 ausgeführt, aber wenn Sie einen x86-Prozessor mit SSE2 oder eine CPU wie AArch64 haben, sollten Sie das gleiche Ergebnis erhalten.

$ java StrictfpTest
multiplyDefault(0x1.00002fffp0, 0x1.000000008p0) = 0x1.00002fff80001p0
multiplyStrict(0x1.00002fffp0, 0x1.000000008p0) = 0x1.00002fff80001p0
multiplyDefault(0x1.fffe0effffffep0, 0x1.0000000000001p0) = 0x1.fffe0fp0
multiplyStrict(0x1.fffe0effffffep0, 0x1.0000000000001p0) = 0x1.fffe0fp0
multiplyDefault(0x1.fffe0effffffep-51, 0x1.0000000000001p-1000) = 0x0.0000000ffff07p-1022
multiplyStrict(0x1.fffe0effffffep-51, 0x1.0000000000001p-1000) = 0x0.0000000ffff07p-1022
multiplyThreeDoublesDefault(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x0.0p0
multiplyThreeDoublesStrict(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x0.0p0
multiplyThreeDoublesDefault(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x0.0p0
multiplyThreeDoublesStrict(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x0.0p0
multiplyThreeFloatsDefault(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0
multiplyThreeFloatsStrict(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0
multiplyThreeFloatsDefault(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0
multiplyThreeFloatsStrict(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0

Es ist ersichtlich, dass sich in einer modernen Umgebung die Ergebnisse nicht mit oder ohne "strictfp" ändern. Der Wert ändert sich vor und nach der JIT-Kompilierung nicht.

Verwenden Sie dann QEMU, um es auf Pentium II auszuführen. Ich habe die 32-Bit-Version des Java-Befehls in ~ / jre1.8.0_261 / bin / java eingefügt.

$ qemu-i386 -cpu pentium2 ~/jre1.8.0_261/bin/java StrictfpTest
multiplyDefault(0x1.00002fffp0, 0x1.000000008p0) = 0x1.00002fff80001p0
multiplyStrict(0x1.00002fffp0, 0x1.000000008p0) = 0x1.00002fff80001p0
multiplyDefault(0x1.fffe0effffffep0, 0x1.0000000000001p0) = 0x1.fffe0fp0
multiplyStrict(0x1.fffe0effffffep0, 0x1.0000000000001p0) = 0x1.fffe0fp0
multiplyDefault(0x1.fffe0effffffep-51, 0x1.0000000000001p-1000) = 0x0.0000000ffff08p-1022
multiplyStrict(0x1.fffe0effffffep-51, 0x1.0000000000001p-1000) = 0x0.0000000ffff07p-1022
multiplyThreeDoublesDefault(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x0.0p0
multiplyThreeDoublesStrict(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x0.0p0
multiplyThreeDoublesDefault(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x1.0p-1000
multiplyThreeDoublesStrict(0x1.0p-1000, 0x1.0p-1000, 0x1.0p1000) = 0x0.0p0
multiplyThreeFloatsDefault(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0
multiplyThreeFloatsStrict(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0
multiplyThreeFloatsDefault(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0
multiplyThreeFloatsStrict(0x1.0p-100, 0x1.0p-100, 0x1.0p100) = 0x0.0p0

Erstens verursacht das erste Beispiel "0x1.00002fffp0 * 0x1.000000008p0" keinen Überlauf oder Unterlauf, sodass sich das Ergebnis nicht mit oder ohne "strictfp" ändert. Gleiches gilt für das folgende Beispiel "0x1.fffe0effffffep0 * 0x1.0000000000001p0".

Andererseits tritt im dritten Beispiel "0x1.fffe0effffffep-51 * 0x1.0000000000001p-1000" ein Unterlauf auf und das Ergebnis ist eine nicht normalisierte Zahl. Und die letzte Ziffer wird abhängig vom Vorhandensein oder Fehlen von "strictfp" um 1 verschoben. Natürlich ist es das mit "strictfp", das IEEE 754-konform ist, und in diesem Fall das mit "strictfp", das nahe am wahren Wert liegt.

Im vierten Beispiel wird "double" verwendet, um "0x1p-1000 * 0x1p-1000 * 0x1p1000" ($ 2 ^ {-1000} \ times 2 ^ {-1000} \ times 2 ^ {1000} ) zu berechnen. Das Zwischenergebnis "0x1p-2000" ( 2 ^ {-2000} $) sollte "0" sein, da der Exponententeil zu klein ist, um in "double" ausgedrückt zu werden, und das Endergebnis sollte auch "0" sein. Tatsächlich geben diejenigen mit strictfp und diejenigen vor der JIT-Kompilierung 0x0.0p0 zurück.

Wenn Sie jedoch nicht "strictfp" hinzufügen, lautet das Ergebnis nach der JIT-Kompilierung "0x1p-1000". Dies bedeutet, dass der Exponententeil in einem größeren Bereich als das ursprüngliche "Doppel" berechnet wurde.

Im fünften Beispiel habe ich versucht, "0x1p-100 * 0x1p-100 * 0x1p100" mit "float" zu berechnen. Das Zwischenergebnis "0x1p-200" kann nicht durch "float" dargestellt werden, daher sollte das Endergebnis "0" sein. In der Tat ist es. Hier hat die JIT-Kompilierung das Ergebnis nicht verändert.

Übrigens, wenn es durch Angabe von "-quark" in Intel SDE ausgeführt wird, scheint es äquivalent zu nicht markiertem Pentium zu sein. Java unterstützt keine "Ausgeführte Anweisung, die für den angegebenen Chip nicht gültig ist (PENTIUM): 0xf7f61dd0: nop ebx, Ich bin in edi` gefallen.

Ich werde hier keine tiefe Erklärung geben

Ich werde hier keine tiefe Erklärung geben.

Sie könnten Artikel wie "x87 FPU Curse" oder "Wofür wurde Javas" strictfp "eingeführt und wie wurde es nicht mehr benötigt?" Schreiben.

Recommended Posts

Bereiten Sie eine Umgebung vor, in der Java strictfp wirklich Sinn macht
Bereiten Sie die Java-Entwicklungsumgebung mit Atom vor
Bereiten Sie die Java-Entwicklungsumgebung mit VS Code vor
Schnelles Lernen von Java "Einführung?" Teil 1 Erstellen einer Umgebung
Erstellen Sie eine E2E-Testumgebung mit Selenium (Java).
Bereiten Sie eine Scraping-Umgebung mit Docker und Java vor