web-dev-qa-db-fra.com

Les compilateurs JIT de JVM génèrent-ils du code qui utilise des instructions vectorielles en virgule flottante?

Disons que le goulot d'étranglement de mon programme Java est vraiment des boucles serrées pour calculer un tas de produits vectoriels à points. Oui, j'ai profilé, oui c'est le goulot d'étranglement, oui c'est important, oui c'est juste comme l'algorithme est, oui j'ai exécuté Proguard pour optimiser le code d'octet, etc.

Le travail est essentiellement des produits scalaires. Comme dans, j'ai deux float[50] et je dois calculer la somme des produits par paire. Je sais que des jeux d'instructions de processeur existent pour effectuer ce type d'opérations rapidement et en masse, comme SSE ou MMX.

Oui, je peux probablement y accéder en écrivant du code natif dans JNI. L'appel JNI s'avère assez cher.

Je sais que vous ne pouvez pas garantir ce qu'un JIT compilera ou ne compilera pas. Est-ce que quelqu'un jamais a entendu parler d'un code de génération JIT qui utilise ces instructions? et si oui, y a-t-il quelque chose dans le code Java qui aide à le rendre compilable de cette façon?

Probablement un "non"; mérite d'être demandé.

89
Sean Owen

Donc, fondamentalement, vous voulez que votre code s'exécute plus rapidement. JNI est la réponse. Je sais que vous avez dit que cela ne fonctionnait pas pour vous, mais permettez-moi de vous montrer que vous avez tort.

Voici Dot.Java:

import Java.nio.FloatBuffer;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;

@Platform(include="Dot.h", compiler="fastfpu")
public class Dot {
    static { Loader.load(); }

    static float[] a = new float[50], b = new float[50];
    static float dot() {
        float sum = 0;
        for (int i = 0; i < 50; i++) {
            sum += a[i]*b[i];
        }
        return sum;
    }
    static native @MemberGetter FloatPointer ac();
    static native @MemberGetter FloatPointer bc();
    static native float dotc();

    public static void main(String[] args) {
        FloatBuffer ab = ac().capacity(50).asBuffer();
        FloatBuffer bb = bc().capacity(50).asBuffer();

        for (int i = 0; i < 10000000; i++) {
            a[i%50] = b[i%50] = dot();
            float sum = dotc();
            ab.put(i%50, sum);
            bb.put(i%50, sum);
        }
        long t1 = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
            a[i%50] = b[i%50] = dot();
        }
        long t2 = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
            float sum = dotc();
            ab.put(i%50, sum);
            bb.put(i%50, sum);
        }
        long t3 = System.nanoTime();
        System.out.println("dot(): " + (t2 - t1)/10000000 + " ns");
        System.out.println("dotc(): "  + (t3 - t2)/10000000 + " ns");
    }
}

et Dot.h:

float ac[50], bc[50];

inline float dotc() {
    float sum = 0;
    for (int i = 0; i < 50; i++) {
        sum += ac[i]*bc[i];
    }
    return sum;
}

Nous pouvons compiler et exécuter cela avec JavaCPP en utilisant les commandes suivantes:

$ javac -cp javacpp.jar Dot.Java
$ Java -jar javacpp.jar Dot
$ Java -cp javacpp.jar:. Dot

Avec un processeur Intel Core i7-3632QM à 2,20 GHz, Fedora 20, GCC 4.8.3 et OpenJDK 7 ou 8, j'obtiens ce type de sortie:

dot(): 37 ns
dotc(): 23 ns

Ou environ 1,6 fois plus rapide. Nous devons utiliser des tampons NIO directs au lieu des tableaux, mais HotSpot peut accéder aux tampons NIO directs aussi rapidement que les tableaux . D'un autre côté, le déroulement manuel de la boucle ne fournit pas une augmentation mesurable des performances, dans ce cas.

41
Samuel Audet

Pour répondre à certains des scepticismes exprimés par d'autres ici, je suggère à tous ceux qui veulent se prouver à eux-mêmes ou à d'autres d'utiliser la méthode suivante:

  • Créer un projet JMH
  • Écrivez un petit extrait de mathématiques vectorisables.
  • Exécutez leur référence en basculant entre -XX: -UseSuperWord et -XX: + UseSuperWord (par défaut)
  • Si aucune différence de performances n'est observée, votre code n'a probablement pas été vectorisé
  • Pour vous en assurer, exécutez votre benchmark de sorte qu'il imprime l'assemblage. Sous linux, vous pouvez profiter du profileur de perfasme ('- prof perfasm') jeter un œil et voir si les instructions que vous attendez sont générées.

Exemple:

@Benchmark
@CompilerControl(CompilerControl.Mode.DONT_INLINE) //makes looking at Assembly easier
public void inc() {
    for (int i=0;i<a.length;i++)
        a[i]++;// a is an int[], I benchmarked with size 32K
}

Le résultat avec et sans indicateur (sur un ordinateur portable Haswell récent, Oracle JDK 8u60): -XX: + UseSuperWord: 475.073 ± 44.579 ns/op (nanosecondes par op) -XX: -UseSuperWord: 3376.364 ± 233.211 ns/op

L'assemblage de la boucle chaude est un peu trop à formater et à coller ici, mais voici un extrait (hsdis.so ne parvient pas à formater certaines des instructions vectorielles AVX2, j'ai donc exécuté avec -XX: UseAVX = 1): -XX: + UseSuperWord (avec '-prof perfasm: intelSyntax = true')

  9.15%   10.90%  │││ │↗    0x00007fc09d1ece60: vmovdqu xmm1,XMMWORD PTR [r10+r9*4+0x18]
 10.63%    9.78%  │││ ││    0x00007fc09d1ece67: vpaddd xmm1,xmm1,xmm0
 12.47%   12.67%  │││ ││    0x00007fc09d1ece6b: movsxd r11,r9d
  8.54%    7.82%  │││ ││    0x00007fc09d1ece6e: vmovdqu xmm2,XMMWORD PTR [r10+r11*4+0x28]
                  │││ ││                                                  ;*iaload
                  │││ ││                                                  ; - psy.lob.saw.VectorMath::inc@17 (line 45)
 10.68%   10.36%  │││ ││    0x00007fc09d1ece75: vmovdqu XMMWORD PTR [r10+r9*4+0x18],xmm1
 10.65%   10.44%  │││ ││    0x00007fc09d1ece7c: vpaddd xmm1,xmm2,xmm0
 10.11%   11.94%  │││ ││    0x00007fc09d1ece80: vmovdqu XMMWORD PTR [r10+r11*4+0x28],xmm1
                  │││ ││                                                  ;*iastore
                  │││ ││                                                  ; - psy.lob.saw.VectorMath::inc@20 (line 45)
 11.19%   12.65%  │││ ││    0x00007fc09d1ece87: add    r9d,0x8            ;*iinc
                  │││ ││                                                  ; - psy.lob.saw.VectorMath::inc@21 (line 44)
  8.38%    9.50%  │││ ││    0x00007fc09d1ece8b: cmp    r9d,ecx
                  │││ │╰    0x00007fc09d1ece8e: jl     0x00007fc09d1ece60  ;*if_icmpge

Amusez-vous à prendre d'assaut le château!

37
Nitsan Wakart

Dans les versions HotSpot commençant par Java 7u40, le compilateur de serveur prend en charge la vectorisation automatique. Selon JDK-6340864

Cependant, cela ne semble être vrai que pour les "boucles simples" - du moins pour le moment. Par exemple, l'accumulation d'un tableau ne peut pas encore être vectorisée JDK-719238

26
Vedran

Voici un bel article sur l'expérimentation de Java et les instructions SIMD écrites par mon ami: http://prestodb.rocks/code/simd/

Son résultat général est que vous pouvez vous attendre à ce que JIT utilise certaines opérations SSE en 1.8 (et d'autres en 1.9). Bien que vous ne deviez pas en attendre beaucoup et que vous deviez être prudent.

5
kokosing

Vous pouvez écrire le noyau OpenCl pour faire l'informatique et l'exécuter à partir de Java http://www.jocl.org/ .

Le code peut être exécuté sur le CPU et/ou le GPU et le langage OpenCL prend également en charge les types vectoriels, vous devriez donc être en mesure d'exploiter explicitement, par exemple Instructions SSE3/4.

4
Mikael Lepistö

Je suppose que vous avez écrit cette question avant de découvrir netlib-Java ;-) il fournit exactement l'API native dont vous avez besoin, avec des implémentations optimisées pour la machine, et n'a aucun coût à la frontière native grâce à l'épinglage de la mémoire.

3
fommil

Jetez un œil à Comparaison des performances entre Java et JNI pour une implémentation optimale des micro-noyaux de calcul . Ils montrent que Java HotSpot = VM prend en charge la vectorisation automatique à l'aide du parallélisme de niveau Super-Word, qui est limité aux cas simples de parallélisme à l'intérieur de la boucle. Cet article vous indiquera également si la taille de vos données est suffisamment grande pour justifier la route JNI.

3
Paul Jurczak