Herhangi bir JVM'nin JIT derleyicileri, vektörleştirilmiş kayan nokta komutlarını kullanan kod üretir mi?


95

Diyelim ki Java programımın darboğazı, bir grup vektör nokta ürününü hesaplamak için bazı sıkı döngüler. Evet profili çıkardım, evet bu darboğaz, evet önemli, evet algoritma böyle, evet, bayt kodunu optimize etmek için Proguard çalıştırdım, vb.

Esas olarak iş, nokta ürünlerdir. İçinde olduğu gibi, iki tane var float[50]ve ikili ürünlerin toplamını hesaplamam gerekiyor. SSE veya MMX gibi bu tür işlemleri hızlı ve toplu olarak gerçekleştirmek için işlemci komut setlerinin var olduğunu biliyorum.

Evet, bunlara muhtemelen JNI'de bazı yerel kodlar yazarak erişebilirim. JNI çağrısının oldukça pahalı olduğu ortaya çıktı.

Bir JIT'in neyi derleyeceğini veya derlemeyeceğini garanti edemeyeceğinizi biliyorum. Herkes Has hiç bu talimatları kullanan bir JIT üretim kodunun duydu? ve eğer öyleyse, Java kodunun bu şekilde derlenebilir olmasına yardımcı olacak herhangi bir şey var mı?

Muhtemelen bir "hayır"; sormaya değer.


4
Bulmanın en kolay yolu, muhtemelen bulabileceğiniz en modern JIT'i elde etmek ve oluşturulan montajın çıktısını almaktır -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation. Vektörize edilebilir yöntemi "sıcak" hale getirmek için yeterince çalıştıran bir programa ihtiyacınız olacak.
Louis Wasserman

1
Veya kaynağa bir bakın. download.java.net/openjdk/jdk7
Bill


3
Aslında bu bloga göre , JNI "doğru" kullanılırsa oldukça hızlı olabilir.
ziggystar

2
Bununla ilgili bir blog yazısı şurada bulunabilir: psy-lob-saw.blogspot.com/2015/04/… vektörleştirmenin olabileceği ve gerçekleştiği genel mesajıyla birlikte. JVM, belirli durumları vektörleştirmenin yanı sıra (Arrays.fill () / equals (char []) / arrayCopy), Superword Level Parallelization'ı kullanarak JVM'yi otomatik olarak vektörleştirir. İlgili kod superword.cpp'de ve dayandığı kağıt burada: groups.csail.mit.edu/cag/slp/SLP-PLDI-2000.pdf
Nitsan Wakart

Yanıtlar:


45

Yani temelde kodunuzun daha hızlı çalışmasını istiyorsunuz. Cevap JNI. Senin için işe yaramadığını söylediğini biliyorum, ama sana yanıldığını göstereyim.

İşte 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 @NoException 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");
    }
}

ve 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;
}

Bu komutu kullanarak JavaCPP ile derleyip çalıştırabiliriz :

$ java -jar javacpp.jar Dot.java -exec

Intel (R) Core (TM) i7-7700HQ CPU @ 2.80GHz, Fedora 30, GCC 9.1.1 ve OpenJDK 8 veya 11 ile şu tür çıktılar elde ediyorum:

dot(): 39 ns
dotc(): 16 ns

Veya kabaca 2,4 kat daha hızlı. Diziler yerine doğrudan NIO arabellekleri kullanmamız gerekir, ancak HotSpot doğrudan NIO arabelleklerine diziler kadar hızlı erişebilir . Öte yandan, döngüyü manuel olarak açmak, bu durumda performansta ölçülebilir bir artış sağlamaz.


3
OpenJDK veya Oracle HotSpot kullandınız mı? Popüler inanışın aksine, aynı değiller.
Jonathan S. Fisher

@exabrial Şu anda bu makinede "java-sürümü" şu anda döndürür: java sürümü "1.6.0_22" OpenJDK Runtime Environment (IcedTea6 1.10.6) (fedora-63.1.10.6.fc15-x86_64) OpenJDK 64-Bit Sunucu VM (sürüm 20.0-b11, karma mod)
Samuel Audet

1
Bu döngü muhtemelen bir taşınan döngü bağımlılığına sahiptir. Döngüyü iki veya daha fazla kez açarak daha fazla hızlanma elde edebilirsiniz.

3
@Oliv GCC kodu SSE ile vektörleştirir, evet, ancak bu kadar küçük veriler için JNI çağrı ek yükü maalesef çok büyük.
Samuel Audet

2
JDK 13 ile A6-7310'umda: nokta (): 69 ns / dotc (): 95 ns elde ediyorum. Java kazandı!
Stefan Reich

40

Burada başkaları tarafından ifade edilen şüpheciliğin bir kısmına değinmek için, kendilerine veya başkalarına kanıtlamak isteyen herkese aşağıdaki yöntemi kullanmalarını öneriyorum:

  • Bir JMH projesi oluşturun
  • Küçük bir vektörleştirilebilir matematik parçası yazın.
  • Karşılaştırmalarını -XX: -UseSuperWord ve -XX: + UseSuperWord (varsayılan) arasında çalıştırın
  • Performansta herhangi bir fark gözlenmezse, kodunuz muhtemelen vektörleştirilmemiştir
  • Emin olmak için, karşılaştırmanızı, montajı yazdıracak şekilde çalıştırın. Linux'ta perfasm profil oluşturucusunun ('- prof perfasm') keyfini çıkarabilir ve beklediğiniz talimatların oluşturulup oluşturulmadığını görebilirsiniz.

Misal:

@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
}

İşaretli ve işaretsiz sonuç (son Haswell dizüstü bilgisayarında, Oracle JDK 8u60): -XX: + UseSuperWord: 475.073 ± 44.579 ns / op (işlem başına nanosaniye) -XX: -UseSuperWord: 3376.364 ± 233.211 ns / op

Sıcak döngü derlemesi burada biçimlendirmek ve yapıştırmak için biraz fazla ama burada bir parça var (hsdis.so AVX2 vektör talimatlarından bazılarını biçimlendiremiyor, bu yüzden -XX: UseAVX = 1 ile çalıştım): -XX: + UseSuperWord ('-prof perfasm: intelSyntax = true' ile)

  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

Kaleye saldırırken iyi eğlenceler!


1
Aynı makaleden: "JIT'li sökücü çıktısı, en uygun SIMD talimatlarını çağırma ve bunların zamanlaması açısından aslında o kadar verimli olmadığını gösteriyor. JVM JIT derleyicisi (Hotspot) kaynak kodunun hızlı bir şekilde araştırılması, bunun şu nedenlerden kaynaklandığını gösteriyor: paketlenmiş SIMD talimat kodlarının olmaması. " SSE kayıtları skaler modda kullanılıyor.
Aleksandr Dubinsky

1
@AleksandrDubinsky bazı vakalar kapsanır, bazıları değildir. İlgilendiğiniz somut bir vakanız var mı?
Nitsan Wakart

2
Soruyu tersine çevirelim ve JVM'nin herhangi bir aritmetik işlemi otomatikleştirip yapmayacağını soralım. Bir örnek verebilir misiniz? Son zamanlarda intrinsics kullanarak çıkarmam ve yeniden yazmam gereken bir döngüm var. Bununla birlikte, otovektorizasyon için umut vermek yerine, açık vektörleştirme / içsel öğeler için destek görmek istiyorum ( agner.org/optimize/vectorclass.pdf gibi ). Daha da iyisi, Aparapi için iyi bir Java arka uç yazmak olacaktır (bu projenin liderliğinin bazı yanlış hedefleri olmasına rağmen). JVM üzerinde mi çalışıyorsunuz?
Aleksandr Dubinsky

1
@AleksandrDubinsky Umarım genişletilmiş yanıt yardımcı olur, belki bir e-posta olmazsa. Ayrıca "içsel bilgileri kullanarak yeniden yaz" ın, JVM kodunu yeni iç bilgiler eklemek için değiştirdiğinizi ima ettiğini unutmayın, demek istediğiniz bu mu? JNI
Nitsan Wakart

1
Teşekkür ederim. Artık resmi cevap bu olmalı. Güncel olmadığı ve vektörleştirmeyi göstermediği için kağıda yapılan referansı kaldırmanız gerektiğini düşünüyorum.
Aleksandr Dubinsky

26

Java 7u40 ile başlayan HotSpot sürümlerinde, sunucu derleyicisi otomatik vektörleştirme için destek sağlar. Göre JDK-6340864

Ancak, bu sadece "basit döngüler" için - en azından şu an için - doğru görünüyor. Örneğin, bir dizi biriktirmek henüz vektörleştirilemez JDK-7192383


Vektörizasyon, bazı durumlarda JDK6'da da mevcuttur, ancak hedeflenen SIMD komut seti o kadar geniş değildir.
Nitsan Wakart

3
HotSpot'ta derleyici vektörleştirme desteği, Intel'in katkıları nedeniyle son zamanlarda (Haziran 2017) çok geliştirildi. Performans açısından henüz piyasaya sürülmemiş jdk9 (b163 ve sonrası), AVX2'yi etkinleştiren hata düzeltmeleri nedeniyle şu anda jdk8'i kazanıyor. Döngüler, otomatik vektörleştirmenin çalışması için birkaç kısıtlamayı karşılamalıdır, örneğin kullanım: int sayacı, sabit sayaç artışı, döngü değişmez değişkenlerle bir sonlandırma koşulu, yöntem çağrıları olmadan döngü gövdesi (?), Manuel döngü açılımı yok! Ayrıntılar şu adreste mevcuttur: cr.openjdk.java.net/~vlivanov/talks/…
Vedran

Vectorized fused-multiple-add (FMA) desteği şu anda iyi görünmüyor (Haziran 2017 itibariyle): ya vektörleştirme veya skaler FMA (?). Ancak Oracle, Intel'in AVX-512 kullanarak FMA vektörleştirmesini sağlayan HotSpot'a katkısını kabul etti. Otomatik vektörleştirme hayranlarının ve şanslı olanların AVX-512 donanımına erişme zevkine göre, bu (biraz şansla) sonraki jdk9 EA sürümlerinden birinde (b175'in ötesinde) görünebilir.
Vedran

Önceki ifadeyi desteklemek için bir bağlantı (RFR (M): 8181616: FMA Vectorization on x86): mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2017-June/…
Vedran

2
AVX2 talimatlarını kullanarak döngü vektörleştirme yoluyla tamsayılar üzerinde 4 faktörüyle ivmeyi gösteren küçük bir kıyaslama: prestodb.rocks/code/simd
Vedran

6

Arkadaşım tarafından yazılan Java ve SIMD talimatlarını denemeyle ilgili güzel bir makale: http://prestodb.rocks/code/simd/

Genel sonucu, JIT'in 1.8'de bazı SSE işlemlerini (ve 1.9'da biraz daha fazlasını) kullanmasını beklemenizdir. Yine de çok şey beklememelisin ve dikkatli olmalısın.


1
Bağlantı verdiğiniz makalenin bazı temel içgörülerini özetlemeniz yardımcı olacaktır.
Aleksandr Dubinsky

4

Hesaplamayı yapmak için OpenCl çekirdeği yazabilir ve java http://www.jocl.org/ adresinden çalıştırabilirsiniz .

Kod, CPU ve / veya GPU üzerinde çalıştırılabilir ve OpenCL dili, vektör türlerini de destekler, bu nedenle, örneğin SSE3 / 4 komutlarından açıkça yararlanabilmeniz gerekir.



3

Bu soruyu netlib-java hakkında öğrenmeden önce yazdığınızı tahmin ediyorum ;-) makine için optimize edilmiş uygulamalarla tam olarak ihtiyaç duyduğunuz yerel API'yi sağlar ve bellek pinlemesi sayesinde yerel sınırda herhangi bir maliyeti yoktur.


1
Evet, uzun zaman önce. Bunun otomatik olarak vektörleştirilmiş talimatlara dönüştürüldüğünü duymayı umuyordum. Ama açıkça bunu manuel olarak yapmak o kadar da zor değil.
Sean Owen

-4

Çoğu sanal makinenin bu tür optimizasyonlar için yeterince akıllı olduğuna inanmıyorum. Dürüst olmak gerekirse, çoğu optimizasyon çok daha basittir, örneğin ikinin gücü olduğunda çarpma yerine kaydırma yapmak gibi. Mono proje, performansa yardımcı olmak için kendi vektörlerini ve diğer yöntemlerini yerel destekleri ile tanıttı.


3
Şu anda, hiçbir Java etkin nokta derleyicisi bunu yapmaz, ancak yaptıklarından çok daha zor değildir. Birden çok dizi değerini aynı anda kopyalamak için SIMD talimatlarını kullanırlar. Sadece biraz daha kalıp eşleştirme ve kod üretme kodu yazmanız gerekiyor, bu biraz döngü açtıktan sonra oldukça basit. Sanırım Sun'daki
Christopher Manning
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.