Apakah ada kompiler JIT JVM yang menghasilkan kode yang menggunakan instruksi titik mengambang vektor?


95

Katakanlah hambatan program Java saya sebenarnya adalah beberapa loop ketat untuk menghitung sekumpulan produk titik vektor. Ya, saya telah membuat profil, ya itu hambatannya, ya itu signifikan, ya begitulah algoritmanya, ya saya telah menjalankan Proguard untuk mengoptimalkan kode byte, dll.

Karya tersebut, pada dasarnya, adalah produk titik. Seperti, saya memiliki dua float[50]dan saya perlu menghitung jumlah produk berpasangan. Saya tahu set instruksi prosesor ada untuk melakukan operasi semacam ini dengan cepat dan dalam jumlah besar, seperti SSE atau MMX.

Ya, saya mungkin dapat mengaksesnya dengan menulis beberapa kode asli di JNI. Telepon JNI ternyata cukup mahal.

Saya tahu Anda tidak dapat menjamin JIT akan dikompilasi atau tidak. Adakah yang pernah mendengar tentang kode yang menghasilkan JIT yang menggunakan instruksi ini? dan jika demikian, apakah ada sesuatu tentang kode Java yang membantu membuatnya dapat dikompilasi dengan cara ini?

Mungkin "tidak"; layak untuk ditanyakan.


4
Cara termudah untuk mengetahuinya mungkin dengan mendapatkan JIT paling modern yang dapat Anda temukan dan mengeluarkannya dari perakitan yang dihasilkan -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation. Anda memerlukan program yang menjalankan metode vectorizable cukup sering untuk membuatnya "panas".
Louis Wasserman

1
Atau lihat sumbernya. download.java.net/openjdk/jdk7
Tagihan


3
Sebenarnya menurut blog ini , JNI bisa agak cepat jika digunakan "dengan benar".
ziggystar

2
Sebuah posting blog yang relevan mengenai hal ini dapat ditemukan di sini: psy-lob-saw.blogspot.com/2015/04/… dengan pesan umum bahwa vektorisasi dapat terjadi, dan memang terjadi. Selain melakukan vektorisasi kasus tertentu (Arrays.fill () / sama dengan (char []) / arrayCopy) JVM melakukan vektorisasi otomatis menggunakan Paralelisasi Tingkat Kata Super. Kode yang relevan ada di superword.cpp dan makalah yang didasarkan padanya
Nitsan Wakart

Jawaban:


44

Jadi, pada dasarnya, Anda ingin kode Anda berjalan lebih cepat. JNI adalah jawabannya. Saya tahu Anda mengatakan itu tidak berhasil untuk Anda, tetapi izinkan saya menunjukkan kepada Anda bahwa Anda salah.

Berikut ini 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");
    }
}

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

Kita dapat mengkompilasi dan menjalankannya dengan JavaCPP menggunakan perintah ini:

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

Dengan CPU Intel (R) Core (TM) i7-7700HQ @ 2.80GHz, Fedora 30, GCC 9.1.1, dan OpenJDK 8 atau 11, saya mendapatkan output semacam ini:

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

Atau kira-kira 2,4 kali lebih cepat. Kita perlu menggunakan buffer NIO langsung daripada array, tetapi HotSpot dapat mengakses buffer NIO langsung secepat array . Di sisi lain, membuka loop secara manual tidak memberikan peningkatan kinerja yang terukur, dalam kasus ini.


3
Apakah Anda menggunakan OpenJDK atau Oracle HotSpot? Berlawanan dengan kepercayaan populer, mereka tidaklah sama.
Jonathan S. Fisher

@exabrial Inilah yang "java -version" kembalikan pada mesin ini sekarang: versi java "1.6.0_22" OpenJDK Runtime Environment (IcedTea6 1.10.6) (fedora-63.1.10.6.fc15-x86_64) OpenJDK 64-Bit Server VM (build 20.0-b11, mode campuran)
Samuel Audet

1
Loop tersebut kemungkinan besar memiliki dependensi loop yang dibawa. Anda bisa mendapatkan percepatan lebih lanjut dengan membuka gulungan loop dua kali atau lebih.

3
@Oliv GCC melakukan vektorisasi kode dengan SSE, ya, tetapi untuk data sekecil itu, sayangnya overhead panggilan JNI terlalu besar.
Samuel Audet

2
Pada A6-7310 saya dengan JDK 13, saya mendapatkan: dot (): 69 ns / dotc (): 95 ns. Jawa menang!
Stefan Reich

39

Untuk mengatasi beberapa skeptisisme yang diungkapkan oleh orang lain di sini, saya menyarankan siapa pun yang ingin membuktikan kepada diri sendiri atau orang lain menggunakan metode berikut:

  • Buat proyek JMH
  • Tulis potongan kecil matematika yang dapat di-vektorisasi.
  • Jalankan tolok ukurnya membalik antara -XX: -UseSuperWord dan -XX: + UseSuperWord (default)
  • Jika tidak ada perbedaan kinerja yang diamati, kode Anda mungkin tidak menjadi vektor
  • Untuk memastikan, jalankan benchmark Anda sedemikian rupa sehingga mencetak perakitan. Di linux Anda dapat menikmati perfasm profiler ('- prof perfasm') untuk melihat dan melihat apakah instruksi yang Anda harapkan dihasilkan.

Contoh:

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

Hasil dengan dan tanpa flag (pada laptop Haswell terbaru, Oracle JDK 8u60): -XX: + UseSuperWord: 475.073 ± 44.579 ns / op (nanodetik per op) -XX: -UseSuperWord: 3376.364 ± 233.211 ns / op

Perakitan untuk hot loop agak banyak untuk memformat dan menempel di sini tetapi ini cuplikannya (hsdis.so gagal memformat beberapa instruksi vektor AVX2 jadi saya menjalankan dengan -XX: UseAVX = 1): -XX: + UseSuperWord (dengan '-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

Selamat bersenang-senang menyerbu kastil!


1
Dari makalah yang sama: "output disassembler JITed menunjukkan bahwa sebenarnya tidak seefisien itu dalam hal memanggil instruksi SIMD yang paling optimal dan penjadwalannya. Pencarian cepat melalui kode sumber JVM JIT compiler (Hotspot) menunjukkan bahwa hal ini disebabkan tidak adanya kode instruksi SIMD yang dikemas. " Register SSE sedang digunakan dalam mode skalar.
Aleksandr Dubinsky

1
@AleksandrDubinsky beberapa kasus tercakup, beberapa tidak. Apakah Anda memiliki kasing konkret yang Anda minati?
Nitsan Wakart

2
Mari balik pertanyaannya dan tanyakan apakah JVM akan melakukan autovektorisasi pada operasi aritmatika? Bisakah Anda memberikan contoh? Saya memiliki loop yang harus saya tarik dan tulis ulang menggunakan intrinsics baru-baru ini. Namun, daripada berharap untuk otovektorisasi, saya ingin melihat dukungan untuk vektorisasi / intrinsik eksplisit (mirip dengan agner.org/optimize/vectorclass.pdf ). Lebih baik lagi menulis backend Java yang bagus untuk Aparapi (meskipun kepemimpinan proyek itu memiliki beberapa tujuan yang salah). Apakah Anda bekerja di JVM?
Aleksandr Dubinsky

1
@AleksandrDubinsky Saya harap jawaban yang diperluas membantu, jika tidak mungkin email akan membantu. Perhatikan juga bahwa "rewrite using intrinsics" berarti Anda mengubah kode JVM untuk menambahkan intrinsik baru, apakah itu yang Anda maksud? Saya menduga Anda bermaksud mengganti kode Java Anda dengan panggilan ke implementasi asli melalui JNI
Nitsan Wakart

1
Terima kasih. Ini sekarang harus menjadi jawaban resmi. Saya pikir Anda harus menghapus referensi ke kertas, karena sudah ketinggalan zaman dan tidak menunjukkan vektorisasi.
Aleksandr Dubinsky

26

Dalam versi HotSpot yang dimulai dengan Java 7u40, kompiler server menyediakan dukungan untuk vektorisasi otomatis. Menurut JDK-6340864

Namun, ini tampaknya benar hanya untuk "loop sederhana" - setidaknya untuk saat ini. Misalnya, mengumpulkan sebuah array belum dapat di-vektorisasi JDK-7192383


Vektorisasi juga ada di JDK6 untuk beberapa kasus, meskipun set instruksi SIMD yang ditargetkan tidak terlalu lebar.
Nitsan Wakart

3
Dukungan vektorisasi kompiler di HotSpot sangat meningkat akhir-akhir ini (Juni 2017) karena kontribusi Intel. Dari segi kinerja, jdk9 yang belum dirilis (b163 dan yang lebih baru) saat ini menang atas jdk8 karena perbaikan bug yang mengaktifkan AVX2. Pengulangan harus memenuhi beberapa batasan agar vektorisasi otomatis berfungsi, misalnya gunakan: penghitung int, kenaikan penghitung konstan, satu kondisi penghentian dengan variabel invarian-loop, badan perulangan tanpa pemanggilan metode (?), Tidak ada pengulangan pengulangan manual! Detail tersedia di: cr.openjdk.java.net/~vlivanov/talks/…
Vedran

Dukungan fused-multiple-add (FMA) vektorisasi tidak terlihat bagus saat ini (per Juni 2017): baik vektorisasi atau skalar FMA (?). Namun, Oracle rupanya baru saja menerima kontribusi Intel pada HotSpot yang memungkinkan vektorisasi FMA menggunakan AVX-512. Untuk menyenangkan para penggemar vektorisasi otomatis dan mereka yang beruntung memiliki akses ke perangkat keras AVX-512, ini mungkin (dengan sedikit keberuntungan) muncul di salah satu build EA jdk9 berikutnya (di luar b175).
Vedran

Tautan untuk mendukung pernyataan sebelumnya (RFR (M): 8181616: Vektorisasi FMA pada x86): mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2017-June/…
Vedran

2
Tolok ukur kecil yang menunjukkan percepatan dengan faktor 4 pada bilangan bulat melalui vektorisasi loop menggunakan instruksi AVX2: prestodb.rocks/code/simd
Vedran

6

Berikut adalah artikel bagus tentang bereksperimen dengan Java dan instruksi SIMD yang ditulis oleh teman saya: http://prestodb.rocks/code/simd/

Hasil umumnya adalah Anda dapat mengharapkan JIT menggunakan beberapa operasi SSE di 1.8 (dan beberapa lagi di 1.9). Padahal Anda seharusnya tidak berharap banyak dan Anda perlu berhati-hati.


1
Akan membantu jika Anda meringkas beberapa wawasan utama dari artikel yang Anda tautkan.
Aleksandr Dubinsky

4

Anda dapat menulis kernel OpenCl untuk melakukan komputasi dan menjalankannya dari java http://www.jocl.org/ .

Kode dapat dijalankan pada CPU dan / atau GPU dan bahasa OpenCL juga mendukung jenis vektor sehingga Anda harus dapat memanfaatkan secara eksplisit misalnya instruksi SSE3 / 4.



3

Saya menduga Anda telah menulis pertanyaan ini sebelum Anda mengetahui tentang netlib-java ;-) menyediakan persis API asli yang Anda butuhkan, dengan implementasi yang dioptimalkan mesin, dan tidak memiliki biaya apa pun di batas asli karena berkat penyematan memori.


1
Ya, dulu sekali. Saya lebih berharap mendengar bahwa ini secara otomatis diterjemahkan ke instruksi vektor. Namun yang jelas tidak sulit untuk mewujudkannya secara manual.
Sean Owen

-4

Saya tidak percaya sebagian besar jika ada VM yang cukup pintar untuk pengoptimalan semacam ini. Agar adil, sebagian besar pengoptimalan jauh lebih sederhana, seperti menggeser alih-alih perkalian saat pangkat dua. Proyek mono memperkenalkan vektor mereka sendiri dan metode lain dengan dukungan asli untuk membantu kinerja.


3
Saat ini, tidak ada kompiler hotspot Java yang melakukan ini, tetapi itu tidak jauh lebih sulit daripada hal-hal yang mereka lakukan. Mereka menggunakan instruksi SIMD untuk menyalin beberapa nilai array sekaligus. Anda hanya perlu menulis beberapa pencocokan pola dan kode pembuatan kode, yang cukup mudah setelah melakukan beberapa loop membuka gulungan. Saya pikir orang-orang di Sun baru saja malas, tetapi sepertinya sekarang akan terjadi di Oracle (yay Vladimir! Ini akan sangat membantu kode kami!): Mail.openjdk.java.net/pipermail/hotspot-compiler-dev/ …
Christopher Manning
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.