क्या कोई जेवीएम का जेआईटी संकलक कोड उत्पन्न करता है जो वेक्टराइज़्ड फ़्लोटिंग पॉइंट निर्देशों का उपयोग करता है?


95

मान लें कि मेरे जावा प्रोग्राम की अड़चन वास्तव में वेक्टर डॉट उत्पादों के एक समूह की गणना करने के लिए कुछ तंग छोर हैं। हाँ, मैंने प्रोफाइल किया है, हाँ यह अड़चन है, हाँ यह महत्वपूर्ण है, हाँ यह सिर्फ इतना है कि एल्गोरिथ्म कैसा है, हाँ मैंने बाइट कोड का अनुकूलन करने के लिए प्रोगार्ड चलाया है, आदि।

काम, अनिवार्य रूप से, डॉट उत्पादों है। जैसा कि, मेरे पास दो हैं float[50]और मुझे जोड़ीदार उत्पादों के योग की गणना करने की आवश्यकता है। मुझे पता है कि SSE या MMX की तरह इस तरह के ऑपरेशन को जल्दी और थोक में करने के लिए प्रोसेसर निर्देश सेट मौजूद हैं।

हां, मैं शायद जेएनआई में कुछ मूल कोड लिखकर इन तक पहुंच बना सकता हूं। जेएनआई कॉल बहुत महंगा निकला।

मुझे पता है कि आप इस बात की गारंटी नहीं दे सकते हैं कि एक JIT संकलन या संकलन नहीं करेगा। क्या किसी ने कभी जेआईटी जनरेटिंग कोड के बारे में सुना है जो इन निर्देशों का उपयोग करता है? और यदि हां, तो क्या जावा कोड के बारे में कुछ ऐसा है जो इसे इस तरह से बनाने में मदद करता है?

शायद एक "नहीं"; पूछने लायक।


4
यह पता लगाने का सबसे आसान तरीका संभवतः सबसे आधुनिक जेआईटी है जिसे आप पा सकते हैं और इसके साथ उत्पन्न विधानसभा का उत्पादन कर सकते हैं -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation। आपको एक कार्यक्रम की आवश्यकता होगी जो इसे "गर्म" बनाने के लिए वेक्टर करने योग्य विधि को पर्याप्त बार चलाता है।
लुई वासरमैन

1
या स्रोत पर एक नजर है। download.java.net/openjdk/jdk7
बिल

1
"जल्द ही आ रहा है" आपके पास एक jdk: mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2012-July/…
जोनाथन एस। फिशर

3
दरअसल, इस ब्लॉग के अनुसार , "सही ढंग से" उपयोग किए जाने पर जेएनआई तेजी से हो सकता है।
ज़िग्सिस्टार

2
इस पर एक प्रासंगिक ब्लॉग पोस्ट यहां पाया जा सकता है: psy-lob-saw.blogspot.com/2015/04/… सामान्य संदेश के साथ कि वैश्वीकरण हो सकता है, और होता है। विशिष्ट मामलों को वेक्टर करने के अलावा (Arrays.fill) (/ equals (char []) / arrayCopy) JVM ऑटो-सुपरवाइज़र्स को सुपरवर्ड लेवल पैरलाइज़ेशन का उपयोग करता है। संबंधित कोड superword.cpp में है और कागज इसके आधार पर यहां है: group.csail.mit.edu/cag/slp/SLP-PLDI-2000.pdf
निट्सन वकार्ट

जवाबों:


44

इसलिए, मूल रूप से, आप चाहते हैं कि आपका कोड तेज़ी से चले। जेएनआई इसका जवाब है। मुझे पता है कि आपने कहा था कि यह आपके लिए काम नहीं करता है, लेकिन मैं आपको दिखाता हूं कि आप गलत हैं।

यहाँ है 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 का उपयोग किया है? आम धारणा के विपरीत, वे समान नहीं हैं।
जोनाथन एस। फिशर

@exabrial यह वही है जो इस मशीन पर "java -version" अभी देता है: java संस्करण "1.6.0_22" OpenJDK रनटाइम एनवायरनमेंट (IcedTea6 1.10.6) (फेडोरा -63.1.10.6 .fc15-x86_64) OpenJDK 64-बिट सर्वर VM (बिल्ड 20.0-b11, मिश्रित मोड)
सैमुअल

1
उस लूप की संभावना एक लूप निर्भरता है। आप दो या अधिक बार लूप को अनियंत्रित करके एक और गति प्राप्त कर सकते हैं।

3
@ ओएलवी जीसीसी एसएसई के साथ कोड को वेक्टर करता है, लेकिन इस तरह के छोटे डेटा के लिए, जेएनआई कॉल ओवरहेड दुर्भाग्य से बहुत बड़ा है।
शमूएल ऑडिट

2
JDK 13 के साथ मेरे A6-7310 पर, मुझे मिलता है: dot (): 69 ns / dotc (): 95 ns। जावा जीत!
स्टीफन रीच

39

दूसरों द्वारा व्यक्त किए गए कुछ संशयवाद को संबोधित करने के लिए मैं यहां किसी को भी सुझाव देता हूं जो खुद को साबित करना चाहता है या अन्य निम्नलिखित विधि का उपयोग करते हैं:

  • एक JMH प्रोजेक्ट बनाएं
  • वेक्टर करने योग्य गणित का एक छोटा सा स्निपेट लिखें।
  • उनके बेंचमार्क को -XX के बीच फ़्लिप करें: -UseSuperWord और -XX: + UseSuperWord (डिफ़ॉल्ट)
  • यदि प्रदर्शन में कोई अंतर नहीं देखा जाता है, तो आपका कोड शायद सदिश नहीं हुआ है
  • यह सुनिश्चित करने के लिए, अपने बेंचमार्क को ऐसे चलाएं कि यह असेंबली को प्रिंट करता है। लिनक्स पर आप परफ्यूम प्रोफाइलर का आनंद ले सकते हैं ('- प्रोफ परफ्यूम') एक नज़र और देखें कि क्या आपके द्वारा अपेक्षित निर्देश उत्पन्न होते हैं।

उदाहरण:

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

परिणाम और झंडे के साथ (बिना हाल के लैपटॉप, ओरेकल JDK 8u60 पर): -XX: + UseSuperWord: 475.073 ± 44.579 ns / op (nanoseconds per op) -XX: -UseSuperWord: 3376.364 ± 233.211 ns / op

हॉट लूप के लिए असेंबली यहां प्रारूप और छड़ी के लिए थोड़ी अधिक है, लेकिन यहां एक स्निपेट (hsdis.so है, जो AVX2 वेक्टर निर्देशों में से कुछ को प्रारूपित करने में विफल हो रहा है, इसलिए मैं -XX के साथ भाग गया: UseAVX = 1): -XX: + UseSuperWord ('परफ्यूम परफ्यूम के साथ: 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 disassembler आउटपुट बताता है कि वास्तव में यह सबसे इष्टतम SIMD निर्देशों को कॉल करने और उनके समयबद्धन के मामले में कुशल नहीं है। JVM JIT कंपाइलर (हॉटस्पॉट) स्रोत के माध्यम से एक त्वरित शिकार से पता चलता है कि यह इसके कारण है। पैक किए गए SIMD अनुदेश कोड की गैर-मौजूदगी। " SSE रजिस्टरों का उपयोग स्केलर मोड में किया जा रहा है।
अलेक्सांद्र डबिन्सकी

1
@AleksandrDubinsky कुछ मामलों में शामिल हैं, कुछ नहीं हैं। क्या आपके पास एक ठोस मामला है जिसमें आप रुचि रखते हैं?
निट्सन वकार्ट

2
आइए इस प्रश्न को पलटें और पूछें कि क्या JVM किसी भी अंकगणितीय ऑपरेशन को स्वचालित करेगा? क्या आप एक उदाहरण प्रदान कर सकते हैं? मेरे पास एक लूप है जिसे मुझे हाल ही में आंतरिक का उपयोग करके बाहर निकालना और फिर से लिखना था। हालांकि, ऑटोवैक्टराइजेशन के लिए आशा के बजाय, मैं स्पष्ट वेक्टराइजेशन / इंट्रिनिक्स ( agner.org/optimize/vectorclass.pdf के समान ) के लिए समर्थन देखना चाहूंगा । इससे भी बेहतर होगा कि आपरपी के लिए एक अच्छा जावा बैकेंड लिखें (हालाँकि उस परियोजना के नेतृत्व में कुछ गलत लक्ष्य हैं)। क्या आप जेवीएम पर काम करते हैं?
अलेक्जेंडर डबलिनस्की

1
@AleksandrDubinsky मुझे आशा है कि विस्तारित उत्तर मदद करता है, अगर शायद कोई ईमेल नहीं होता। यह भी ध्यान दें कि "आंतरिक का उपयोग करके फिर से लिखना" से तात्पर्य है कि आपने नए आंतरिक को जोड़ने के लिए जेवीएम कोड को बदल दिया है, क्या आपका मतलब है? मुझे लगता है कि आप
जेएनआई के

1
धन्यवाद। यह अब आधिकारिक जवाब होना चाहिए। मुझे लगता है कि आपको कागज के संदर्भ को हटा देना चाहिए, क्योंकि यह पुराना है और वैश्वीकरण को प्रदर्शित नहीं करता है।
हांग्जो डबिन्स्की

26

जावा 7u40 के साथ शुरू होने वाले हॉटस्पॉट संस्करणों में, सर्वर कंपाइलर ऑटो-वैक्टराइजेशन के लिए समर्थन प्रदान करता है। जेडीके -6340864 के अनुसार

हालांकि, यह केवल "सरल छोरों" के लिए सही प्रतीत होता है - कम से कम फिलहाल। उदाहरण के लिए, एक सरणी को जमा करना अभी तक JDK-7192383 को वेक्टर नहीं किया जा सकता है


JDK6 के साथ-साथ कुछ मामलों के लिए वैश्वीकरण है, हालांकि लक्षित SIMD निर्देश सेट उतना व्यापक नहीं है।
निट्सन वकार्ट

3
इंटेल द्वारा योगदान के कारण हॉटस्पॉट में कंपाइलर वेक्टराइज़ेशन सपोर्ट को हाल ही में (जून 2017) में काफी सुधार किया गया था। प्रदर्शन-वार अभी तक असंबंधित jdk9 (b163 और बाद में) वर्तमान में jdk8 पर जीतता है, जो बग फिक्स के कारण AVX2 को सक्षम करता है। लूप्स को ऑटो-वेक्टराइज़ेशन के लिए काम करने के लिए कुछ बाधाओं को पूरा करना होगा, उदाहरण के लिए: int काउंटर, निरंतर काउंटर वेतन वृद्धि, लूप-इनवेरिएंट चर के साथ एक समाप्ति स्थिति, विधि कॉल (?) के बिना लूप बॉडी, कोई मैनुअल लूप अनफॉलो करना! विवरण में उपलब्ध हैं: cr.openjdk.java.net/~vlivanov/talks/...
Vedran

वेक्टरयुक्त फ्यूज्ड-मल्टीपल-ऐड (FMA) समर्थन वर्तमान में अच्छा नहीं लगता (जून 2017 तक): यह या तो वेक्टराइज़ेशन या स्केलर FMA (?) है। हालाँकि, ओरेकल ने जाहिरा तौर पर हॉटस्पॉट में इंटेल के योगदान को स्वीकार कर लिया है, जो एवीएक्स -51 का उपयोग करके एफएमए वेक्टराइजेशन को सक्षम बनाता है। ऑटो-वेक्टरकरण प्रशंसकों और उन भाग्यशाली लोगों की खुशी के लिए AVX-512 हार्डवेयर तक पहुंच है, यह अगले कुछ jdk9 ईए बिल्ड (b175 से परे) में से एक में दिखाई दे सकता है।
वेदरन

पिछले कथन (RFR (M): 8181616: FMA वेक्टराइजेशन ऑन x86) का समर्थन करने के लिए एक लिंक: mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2017-June/…
Vedran

2
एक छोटा सा बेंचमार्क त्वरण AVX2 निर्देश का उपयोग कर पाश vectorization के माध्यम से पूर्णांकों पर 4 का एक पहलू से प्रदर्शन: prestodb.rocks/code/simd
Vedran

6

मेरे मित्र द्वारा लिखे गए जावा और SIMD निर्देशों के साथ प्रयोग करने के बारे में अच्छा लेख यहाँ है: http://prestodb.rocks/code/simim/

इसका सामान्य परिणाम यह है कि आप उम्मीद कर सकते हैं कि JIT 1.8 (और 1.9 में कुछ अधिक) में कुछ SSE संचालन का उपयोग करेगा। हालांकि आपको ज्यादा उम्मीद नहीं करनी चाहिए और आपको सावधान रहने की जरूरत है।


1
यदि आप जिस लेख से जुड़े हैं, उसकी कुछ प्रमुख अंतर्दृष्टि को संक्षेप में रखने में मदद मिलेगी।
हांग्जो डबिन्स्की

4

आप कंप्यूटिंग करने के लिए OpenCl कर्नेल लिख सकते हैं और इसे java http://www.jocl.org/ से चला सकते हैं ।

कोड सीपीयू और / या जीपीयू पर चलाया जा सकता है और ओपनसीएल भाषा भी वेक्टर प्रकारों का समर्थन करती है ताकि आप उदाहरण के लिए एसएसई 3/4 निर्देशों का स्पष्ट रूप से लाभ उठा सकें।


4

कम्प्यूटेशनल माइक्रो-कर्नेल के इष्टतम कार्यान्वयन के लिए जावा और जेएनआई के बीच प्रदर्शन तुलना पर एक नज़र डालें । वे दिखाते हैं कि जावा हॉटस्पॉट वीएम सर्वर कंपाइलर सुपर-शब्द स्तर समानांतरवाद का उपयोग करते हुए ऑटो-वेक्टराइजेशन का समर्थन करता है, जो लूप समानता के अंदर के सरल मामलों तक सीमित है। यह लेख आपको कुछ मार्गदर्शन भी देगा कि क्या आपका डेटा आकार जेएनआई मार्ग को सही ठहराने के लिए पर्याप्त है या नहीं।


3

नेटलिब-जावा के बारे में पता लगाने से पहले मैं आपको यह प्रश्न लिख रहा हूं ;-) यह आपके द्वारा आवश्यक मशीन को लागू किए गए कार्यान्वयन के साथ मूल एपीआई प्रदान करता है, और मेमोरी पिनिंग के कारण मूल सीमा पर कोई लागत नहीं है।


1
हाँ, बहुत समय पहले। मैं यह सुनने के लिए अधिक उम्मीद कर रहा था कि यह स्वचालित रूप से वेक्टर किए गए निर्देशों का अनुवाद है। लेकिन स्पष्ट रूप से यह मुश्किल नहीं है कि इसे मैन्युअल रूप से हो।
सीन ओवेन

-4

मुझे विश्वास नहीं है कि इस तरह के अनुकूलन के लिए कोई भी वीएम कभी भी पर्याप्त स्मार्ट हैं। निष्पक्ष होने के लिए अधिकांश अनुकूलन बहुत सरल होते हैं, जैसे कि दो की शक्ति के गुणा के बजाय स्थानांतरण। मोनो परियोजना ने प्रदर्शन में मदद करने के लिए अपने स्वयं के वेक्टर और देशी बैकिंग के साथ अन्य तरीकों को पेश किया।


3
वर्तमान में, कोई जावा हॉटस्पॉट संकलक ऐसा नहीं करता है, लेकिन यह उन चीजों की तुलना में बहुत कठिन नहीं है जो वे करते हैं। वे कई सरणी मूल्यों को एक साथ कॉपी करने के लिए SIMD निर्देशों का उपयोग करते हैं। आपको बस कुछ और पैटर्न मिलान और कोड जनरेशन कोड लिखना होगा, जो कि कुछ लूप के अनियंत्रित होने के बाद बहुत सीधा है। मुझे लगता है कि सन में लोग बस आलसी हो गए, लेकिन ऐसा लग रहा है कि यह अब ओरेकल (याय व्लादिमीर में होगा! यह हमारे कोड को बहुत मदद करनी चाहिए!): Mail.openjdk.java.net/pipermail/hotspot.compiler-dev/dev ...
क्रिस्टोफर मैनिंग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.