คอมไพเลอร์ JIT ของ JVM สร้างโค้ดที่ใช้คำแนะนำจุดลอยตัวแบบเวกเตอร์หรือไม่


95

สมมติว่าปัญหาคอขวดของโปรแกรม Java ของฉันคือลูปที่แน่นพอที่จะคำนวณผลิตภัณฑ์จุดเวกเตอร์จำนวนมาก ใช่ฉันได้ทำโปรไฟล์แล้วใช่มันเป็นคอขวดใช่มันสำคัญใช่นั่นเป็นเพียงวิธีการที่อัลกอริทึมใช่ฉันใช้ Proguard เพื่อเพิ่มประสิทธิภาพโค้ดไบต์ ฯลฯ

งานคือโดยพื้นฐานแล้วผลิตภัณฑ์ดอท เช่นเดียวกับฉันมีสองfloat[50]และฉันต้องคำนวณผลรวมของผลิตภัณฑ์คู่กัน ฉันรู้ว่าชุดคำสั่งโปรเซสเซอร์มีอยู่เพื่อดำเนินการประเภทนี้อย่างรวดเร็วและจำนวนมากเช่น SSE หรือ MMX

ใช่ฉันสามารถเข้าถึงสิ่งเหล่านี้ได้โดยการเขียนโค้ดเนทีฟใน JNI การโทรของ JNI นั้นค่อนข้างแพง

ฉันรู้ว่าคุณไม่สามารถรับประกันได้ว่า JIT จะรวบรวมหรือไม่รวบรวมอะไร มีใครเคยได้ยินเกี่ยวกับรหัสสร้าง JIT ที่ใช้คำแนะนำเหล่านี้หรือไม่? และถ้าเป็นเช่นนั้นมีอะไรเกี่ยวกับโค้ด Java ที่ช่วยให้คอมไพล์ได้ด้วยวิธีนี้หรือไม่?

น่าจะเป็น "ไม่"; คุ้มค่าที่จะถาม


4
วิธีที่ง่ายที่สุดที่จะหาน่าจะเป็นที่จะได้รับ JIT -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilationที่ทันสมัยที่สุดที่คุณสามารถค้นหาและมีมันส่งออกที่สร้างขึ้นประกอบกับ คุณจะต้องมีโปรแกรมที่เรียกใช้วิธีการแบบเวกเตอร์ได้หลายครั้งเพียงพอที่จะทำให้ "ร้อน"
หลุยส์วาสเซอร์แมน

1
หรือดูที่มา. download.java.net/openjdk/jdk7
บิล

1
"เร็ว ๆ นี้" ไปยัง jdk ที่อยู่ใกล้คุณ: mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2012-July/…
Jonathan S. Fisher

3
จริงๆแล้วตามบล็อกนี้ JNI สามารถทำได้ค่อนข้างเร็วหากใช้ "อย่างถูกต้อง"
ziggystar

2
คุณสามารถดูโพสต์บล็อกที่เกี่ยวข้องได้ที่นี่: psy-lob-saw.blogspot.com/2015/04/…พร้อมข้อความทั่วไปว่า vectorization สามารถเกิดขึ้นได้และจะเกิดขึ้น นอกเหนือจาก vectorizing กรณีเฉพาะ (Arrays.fill () / equals (char []) / arrayCopy) JVM auto-vectorizes โดยใช้ Superword Level Parallelization รหัสที่เกี่ยวข้องอยู่ใน superword.cpp และเอกสารอ้างอิงอยู่ที่นี่: groups.csail.mit.edu/cag/slp/SLP-PLDI-2000.pdf
Nitsan Wakart

คำตอบ:


45

โดยพื้นฐานแล้วคุณต้องการให้โค้ดของคุณทำงานได้เร็วขึ้น JNI คือคำตอบ ฉันรู้ว่าคุณบอกว่ามันไม่ได้ผลสำหรับคุณ แต่ให้ฉันแสดงให้คุณเห็นว่าคุณคิดผิด

นี่คือ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");
    }
}

และ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;
}

เราสามารถคอมไพล์และรันด้วยJavaCPPโดยใช้คำสั่งนี้:

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

ด้วย Intel (R) Core (TM) i7-7700HQ CPU @ 2.80GHz, Fedora 30, GCC 9.1.1 และ OpenJDK 8 หรือ 11 ฉันจะได้ผลลัพธ์ประเภทนี้:

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

หรือเร็วกว่าประมาณ 2.4 เท่า เราจำเป็นต้องใช้บัฟเฟอร์ NIO โดยตรงแทนของอาร์เรย์ แต่HotSpot สามารถเข้าถึงบัฟเฟอร์ NIO โดยตรงให้เร็วที่สุดเท่าอาร์เรย์ ในทางกลับกันการคลายลูปด้วยตนเองไม่ได้ช่วยเพิ่มประสิทธิภาพที่วัดได้ในกรณีนี้


3
คุณใช้ OpenJDK หรือ Oracle HotSpot หรือไม่ ตรงกันข้ามกับความเชื่อที่นิยมพวกเขาไม่เหมือนกัน
Jonathan S. Fisher

@exabrial นี่คือสิ่งที่ "java -version" ส่งคืนในเครื่องนี้ในขณะนี้: เวอร์ชัน 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 (สร้าง 20.0-b11 โหมดผสม)
Samuel Audet

1
ลูปนั้นน่าจะมีการพึ่งพาลูปแบบพกพา คุณอาจได้รับการเร่งความเร็วเพิ่มเติมโดยการคลายการวนซ้ำสองครั้งขึ้นไป

3
@Oliv GCC vectorizes รหัสด้วย SSE ใช่ แต่สำหรับข้อมูลขนาดเล็กเช่นนี้ค่าโสหุ้ยการโทรของ JNI นั้นใหญ่เกินไป
Samuel Audet

2
ใน A6-7310 ของฉันกับ JDK 13 ฉันได้รับ: dot (): 69 ns / dotc (): 95 ns Java ชนะ!
Stefan Reich

40

เพื่อจัดการกับความสงสัยของผู้อื่นที่นี่ฉันขอแนะนำให้ทุกคนที่ต้องการพิสูจน์ตัวเองหรือคนอื่น ๆ ใช้วิธีการต่อไปนี้:

  • สร้างโครงการ JMH
  • เขียนส่วนย่อยของคณิตศาสตร์แบบเวกเตอร์ได้เล็กน้อย
  • รันการพลิกมาตรฐานระหว่าง -XX: -UseSuperWord และ -XX: + UseSuperWord (ค่าเริ่มต้น)
  • หากไม่พบความแตกต่างในประสิทธิภาพโค้ดของคุณอาจไม่ได้รับการแปลงเป็นเวกเตอร์
  • เพื่อให้แน่ใจให้รันเกณฑ์มาตรฐานของคุณเพื่อที่จะพิมพ์ชุดประกอบออกมา บนลินุกซ์คุณสามารถเพลิดเพลินไปกับโปรไฟล์การปรุ ('- prof perfasm') ดูและดูว่าคำแนะนำที่คุณคาดหวังจะสร้างขึ้นหรือไม่

ตัวอย่าง:

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

ผลลัพธ์ที่มีและไม่มีแฟล็ก (บนแล็ปท็อป Haswell ล่าสุด Oracle JDK 8u60): -XX: + UseSuperWord: 475.073 ± 44.579 ns / op (นาโนวินาทีต่อ op) -XX: -UseSuperWord: 3376.364 ± 233.211 ns / op

การประกอบสำหรับ hot loop นั้นค่อนข้างมากในการจัดรูปแบบและติดที่นี่ แต่นี่คือตัวอย่าง (hsdis.so ล้มเหลวในการจัดรูปแบบคำแนะนำเวกเตอร์ AVX2 บางส่วนดังนั้นฉันจึงรันด้วย -XX: UseAVX = 1): -XX: + UseSuperWord (ด้วย '-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

ขอให้สนุกกับการบุกปราสาท!


1
จากเอกสารเดียวกัน: "เอาต์พุตตัวแยกชิ้นส่วน JITed แสดงให้เห็นว่ามันไม่ได้มีประสิทธิภาพในแง่ของการเรียกคำสั่ง SIMD ที่เหมาะสมที่สุดและการตั้งเวลาการค้นหาอย่างรวดเร็วผ่านซอร์สโค้ดคอมไพเลอร์ JVM JIT (Hotspot) แสดงให้เห็นว่านี่เป็นเพราะ ไม่มีรหัสคำสั่ง SIMD ที่บรรจุอยู่ " กำลังใช้การลงทะเบียน SSE ในโหมดสเกลาร์
Aleksandr Dubinsky

1
@AleksandrDubinsky บางกรณีครอบคลุมบางกรณีไม่ได้ คุณมีเคสคอนกรีตที่คุณสนใจหรือไม่?
Nitsan Wakart

2
ลองพลิกคำถามและถามว่า JVM จะดำเนินการคำนวณทางคณิตศาสตร์โดยอัตโนมัติหรือไม่? คุณสามารถให้ตัวอย่างได้หรือไม่? ฉันมีลูปที่ฉันต้องดึงออกและเขียนใหม่โดยใช้อินทรินซิกเมื่อเร็ว ๆ นี้ อย่างไรก็ตามแทนที่จะหวังว่าจะมี autovectorization ฉันต้องการเห็นการสนับสนุนสำหรับ vectorization / intrinsics ที่ชัดเจน (คล้ายกับagner.org/optimize/vectorclass.pdf ) ที่ดีไปกว่านั้นคือการเขียนแบ็กเอนด์ Java ที่ดีสำหรับ Aparapi (แม้ว่าความเป็นผู้นำของโครงการนั้นจะมีเป้าหมายที่ผิด) คุณทำงานกับ JVM หรือไม่?
Aleksandr Dubinsky

1
@AleksandrDubinsky ฉันหวังว่าคำตอบเพิ่มเติมจะช่วยได้ถ้าไม่ใช่อีเมล โปรดทราบว่า "เขียนใหม่โดยใช้อินทรินนิกส์" หมายความว่าคุณเปลี่ยนโค้ด JVM เพื่อเพิ่มอินทรินนิกใหม่นั่นคือสิ่งที่คุณหมายถึงหรือไม่? ฉันเดาว่าคุณหมายถึงการแทนที่โค้ด Java ของคุณด้วยการโทรไปยังการใช้งานแบบเนทีฟผ่าน JNI
Nitsan Wakart

1
ขอขอบคุณ. ตอนนี้ควรเป็นคำตอบอย่างเป็นทางการ ฉันคิดว่าคุณควรลบการอ้างอิงไปยังกระดาษเนื่องจากมันล้าสมัยและไม่ได้แสดงให้เห็นถึงเวกเตอร์
Aleksandr Dubinsky

26

ในเวอร์ชัน HotSpot ที่เริ่มต้นด้วย Java 7u40 คอมไพเลอร์เซิร์ฟเวอร์จะให้การสนับสนุนสำหรับการสร้างเวกเตอร์อัตโนมัติ อ้างอิงจากJDK-6340864

อย่างไรก็ตามสิ่งนี้ดูเหมือนจะเป็นจริงสำหรับ "ลูปธรรมดา" - อย่างน้อยก็ในช่วงเวลานี้ ตัวอย่างเช่นการสะสมอาร์เรย์ไม่สามารถทำเป็นเวกเตอร์ได้ แต่JDK-7192383


Vectorization มีอยู่ใน JDK6 เช่นกันสำหรับบางกรณีแม้ว่าชุดคำสั่ง SIMD เป้าหมายจะไม่กว้างเท่า
Nitsan Wakart

3
การสนับสนุนการทำเวคเตอร์คอมไพเลอร์ใน HotSpot ได้รับการปรับปรุงอย่างมากเมื่อเร็ว ๆ นี้ (มิถุนายน 2017) เนื่องจากการสนับสนุนของ Intel ประสิทธิภาพที่ชาญฉลาด jdk9 (b163 และใหม่กว่า) ที่ยังไม่ได้เปิดตัวในขณะนี้ชนะมากกว่า jdk8 เนื่องจากการแก้ไขข้อบกพร่องที่เปิดใช้งาน AVX2 ลูปต้องตอบสนองข้อ จำกัด บางประการสำหรับการทำให้เวกเตอร์อัตโนมัติทำงานได้เช่นการใช้: ตัวนับ int, การเพิ่มขึ้นของตัวนับคงที่, เงื่อนไขการสิ้นสุดหนึ่งรายการที่มีตัวแปรที่ไม่แปรผันของลูป, เนื้อลูปที่ไม่มีการเรียกใช้เมธอด (?), ไม่มีการคลี่ลูปด้วยตนเอง! ดูรายละเอียดได้ใน: cr.openjdk.java.net/~vlivanov/talks/…
Vedran

การสนับสนุน Vectorized fused-multiple-add (FMA) ดูไม่ดีในปัจจุบัน (ณ เดือนมิถุนายน 2017): อาจเป็น vectorization หรือ scalar FMA (?) อย่างไรก็ตาม Oracle ได้ยอมรับการมีส่วนร่วมของ Intel ใน HotSpot ที่เปิดใช้งาน FMA vectorization โดยใช้ AVX-512 เพื่อความพึงพอใจของแฟน ๆ auto-vectorization และผู้โชคดีที่สามารถเข้าถึงฮาร์ดแวร์ AVX-512 สิ่งนี้อาจ (ด้วยความโชคดี) ปรากฏในหนึ่งใน jdk9 EA รุ่นถัดไป (นอกเหนือจาก b175)
Vedran

ลิงค์สำหรับสนับสนุนคำสั่งก่อนหน้า (RFR (M): 8181616: FMA Vectorization บน x86): mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2017-June/…
Vedran

2
เกณฑ์มาตรฐานขนาดเล็กแสดงให้เห็นถึงการเร่งด้วยปัจจัย 4 บนจำนวนเต็มผ่านการวนซ้ำเวกเตอร์โดยใช้คำแนะนำ AVX2: prestodb.rocks/code/simd
Vedran

6

นี่คือบทความที่ดีเกี่ยวกับการทดลองกับคำแนะนำ Java และ SIMD ที่เขียนโดยเพื่อนของฉัน: http://prestodb.rocks/code/simd/

ผลลัพธ์ทั่วไปคือคุณสามารถคาดหวังให้ JIT ใช้การดำเนินการ SSE บางอย่างใน 1.8 (และอื่น ๆ ใน 1.9) แม้ว่าคุณไม่ควรคาดหวังมากและคุณต้องระวัง


1
จะช่วยได้หากคุณสรุปข้อมูลเชิงลึกที่สำคัญบางส่วนของบทความที่คุณลิงก์ไป
Aleksandr Dubinsky

4

คุณสามารถเขียนเคอร์เนล OpenCl เพื่อทำการคำนวณและเรียกใช้จาก java http://www.jocl.org/ http://www.jocl.org/

โค้ดสามารถทำงานบน CPU และ / หรือ GPU และภาษา OpenCL ยังรองรับประเภทเวกเตอร์ดังนั้นคุณควรใช้ประโยชน์จากคำสั่ง SSE3 / 4 อย่างชัดเจน


4

ดูการเปรียบเทียบประสิทธิภาพระหว่าง Java และ JNI เพื่อการใช้งานไมโครเคอร์เนลที่เหมาะสมที่สุด พวกเขาแสดงให้เห็นว่าคอมไพเลอร์เซิร์ฟเวอร์ Java HotSpot VM สนับสนุนการสร้างเวกเตอร์อัตโนมัติโดยใช้ Super-word Level Parallelism ซึ่ง จำกัด เฉพาะกรณีธรรมดา ๆ ภายในลูปขนาน บทความนี้จะให้คำแนะนำว่าขนาดข้อมูลของคุณใหญ่พอที่จะปรับเปลี่ยนเส้นทาง JNI หรือไม่


3

ฉันเดาว่าคุณเขียนคำถามนี้ก่อนที่คุณจะพบเกี่ยวกับ netlib-java ;-) มันมี API ดั้งเดิมที่คุณต้องการพร้อมการใช้งานที่ปรับให้เหมาะสมกับเครื่องและไม่มีค่าใช้จ่ายใด ๆ ที่ขอบเขตดั้งเดิมเนื่องจากการตรึงหน่วยความจำ


1
เย้นานมาแล้ว ฉันหวังมากกว่าที่จะได้ยินว่านี่ได้รับการแปลโดยอัตโนมัติเป็นคำแนะนำแบบเวกเตอร์ แต่เห็นได้ชัดว่าไม่ใช่เรื่องยากที่จะทำให้เกิดขึ้นด้วยตนเอง
Sean Owen

-4

ฉันไม่เชื่อที่สุดว่า VM ใด ๆ ที่ฉลาดพอสำหรับการเพิ่มประสิทธิภาพประเภทนี้ เพื่อความเป็นธรรมการเพิ่มประสิทธิภาพส่วนใหญ่จะง่ายกว่ามากเช่นการขยับแทนการคูณเมื่อกำลังของสอง โครงการโมโนแนะนำเวกเตอร์ของตนเองและวิธีการอื่น ๆ พร้อมการสนับสนุนแบบเนทีฟเพื่อช่วยในการทำงาน


3
ปัจจุบันไม่มีคอมไพเลอร์ Java hotspot ทำสิ่งนี้ แต่ก็ไม่ได้ยากไปกว่าสิ่งที่พวกเขาทำ พวกเขาใช้คำแนะนำ SIMD เพื่อคัดลอกค่าอาร์เรย์หลายค่าพร้อมกัน คุณเพียงแค่ต้องเขียนการจับคู่รูปแบบและโค้ดการสร้างโค้ดเพิ่มเติมซึ่งค่อนข้างตรงไปตรงมาหลังจากคลายลูปแล้ว ฉันคิดว่าคนที่ซันขี้เกียจ แต่ดูเหมือนว่าตอนนี้จะเกิดขึ้นที่ Oracle (เย้วลาดิเมียร์สิ่งนี้จะช่วยโค้ดของเราได้มาก!): mail.openjdk.java.net/pipermail/hotspot-compiler-dev/ …
Christopher Manning
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.