यदि आपको बहुत उच्च गुणवत्ता वाले यादृच्छिकता की आवश्यकता नहीं है, और निकट-से-समान वितरण काफी अच्छा है, तो आप वास्तव में तेजी से जा सकते हैं , विशेष रूप से एक आधुनिक सीपीयू पर SSE2 या AVX2 के साथ x86 जैसे कुशल SIMD पूर्णांक वैक्टर।
यह @ नॉमिनलिमल के उत्तर की तरह है क्योंकि हम दोनों का विचार समान था, लेकिन x86 के लिए मैन्युअल रूप से वेक्टर किया गया था। (और खराब गुणवत्ता वाले रैंडम नंबरों के साथ, लेकिन बहुत सारे उपयोग के मामलों के लिए अभी भी काफी अच्छा है।) यह 2.5GHz इंटेल हैसवेल पर ASCII आउटपुट के ~ 13GB / s पर @ नॉमिनल कोड से लगभग 15 या 30 गुना तेज चलता है। AVX2 के साथ सीपीयू। यह अभी भी सैद्धांतिक अधिकतम मुख्य मेमोरी बैंडविड्थ (दोहरी चैनल DDR3-1600 के बारे में 25.6GB / s) से कम है, लेकिन मैं / dev / null को लिख रहा था इसलिए यह वास्तव में कैश में गर्म रहने वाले बफर को फिर से लिखना है। स्काईलेक को हसवेल की तुलना में इस कोड को काफी तेज चलाना चाहिए (इस उत्तर के निचले भाग को देखें)।
डिस्क / या कहीं पर पाइपिंग करने के लिए आपको वास्तव में आई / ओ पर अड़चन मानते हुए, तेजी से कार्यान्वयन का मतलब है कि आपके सीपीयू को निष्क्रिय से अधिक घड़ी भी नहीं है। यह परिणाम उत्पन्न करने के लिए बहुत कम कुल ऊर्जा का उपयोग करता है। (बैटरी जीवन / गर्मी / ग्लोबल वार्मिंग।)
यह इतना तेज़ है कि आप इसे डिस्क पर लिखना नहीं चाहेंगे। बस जरूरत के अनुसार फिर से उत्पन्न करें (उसी बीज से यदि आप फिर से वही डेटा चाहते हैं)। यहां तक कि अगर आप इसे एक बहु-थ्रेडेड प्रक्रिया में फीड करना चाहते हैं जो सभी सीपीयू का उपयोग कर सकते हैं, तो इसे चलाने के लिए डेटा को पाइप करने के लिए इसे L3 कैश में गर्म छोड़ देगा (और कोर पर L2 कैश जिसने इसे लिखा है), और बहुत उपयोग करें थोड़ा सीपीयू समय। (लेकिन ध्यान दें कि पाइपिंग बहुत सारे ओवरहेड बनाम राइटिंग को जोड़ता है /dev/null
। स्काइलेक i7-6700k पर, पाइपिंग wc -c
या किसी अन्य प्रोग्राम के लिए पाइपिंग जो सिर्फ अपने इनपुट को पढ़ता है + डिस्क्राइब करता है, यह लिखने की तुलना में लगभग 8x धीमा है/dev/null
, और केवल 70% सीपीयू। लेकिन 3.9GHz CPU पर अभी भी 4.0GB / s है।
पुन: उत्पन्न करने से तेज पीसीआई से जुड़े एसएसडी से भी इसे फिर से पढ़ने से तेज है, लेकिन आईडीके अगर यह अधिक शक्ति कुशल है (वेक्टर-पूर्णांक गुणक को बहुत व्यस्त रखा गया है, और यह संभवतः बहुत शक्तिशाली है, अन्य AV2 के साथ-साथ भूख लगी है) 256b वेक्टर ALU)। OTOH, मुझे नहीं पता कि डिस्क से पढ़ने का सीपीयू समय कितना है जो इस इनपुट को संसाधित करने वाले सभी कोर को अधिकतम कर रहा है। मुझे लगता है कि 128k चंक्स में फिर से उत्पन्न करने के लिए एक संदर्भ-स्विच फाइलसिस्टम / पेजकेच कोड चलाने और डिस्क से डेटा पढ़ने के लिए पृष्ठों को आवंटित करने के लिए प्रतिस्पर्धी हो सकता है। बेशक, अगर यह पहले से ही पेजकेस में गर्म है, तो यह मूल रूप से यादगार है। OTOH, हम पहले से ही के रूप में तेजी से memcpy के रूप में लिखते हैं! (जिसे पढ़ने और लिखने के बीच मुख्य मेमोरी बैंडविड्थ को विभाजित करना है)। (यह भी याद रखें कि स्मृति को लिखना है कि 'rep movsb
(माइक्रोकोड में अनुकूलित मेम्पी और मेमसेट, जो आरएफओ से बचा जाता है, क्योंकि पी 6 (पेंटियम प्रो) में एंडी ग्लीव के इसे लागू करने के बाद )।
अब तक यह केवल अवधारणा का प्रमाण है, और न्यूलाइन हैंडलिंग केवल लगभग सही है। यह एक शक्ति के -2 बफर के सिरों के आसपास गलत है। अधिक विकास समय के साथ। मुझे विश्वास है कि मुझे नई सुइयों को सम्मिलित करने के लिए एक अधिक कुशल तरीका मिल सकता है जो बिल्कुल सही है, ओवरहेड के साथ कम से कम कम से कम इस के रूप में (केवल रिक्त स्थान को आउटपुट करने की तुलना में)। मुझे लगता है कि यह 10 से 20% की तरह कुछ है। मुझे केवल यह जानने में दिलचस्पी है कि हम कितनी तेजी से यह रन बना सकते हैं, वास्तव में इसका पॉलिश संस्करण नहीं है, इसलिए मैं कुछ विचारों का वर्णन करने वाली टिप्पणियों के साथ पाठक के लिए एक अभ्यास के रूप में उस हिस्से को छोड़ दूंगा।
अपने 2.5GHz मैक्स टर्बो में एक हैडवेल i5 पर, DDR3-1600 मेगाहर्ट्ज रैम के साथ, 100GiB का उत्पादन किया, लेकिन कम हो गया। (Gcc5.4 के साथ Win10 पर cygwin64 पर लिया गया था -O3 -march=native
, -funroll-loops
क्योंकि मैं काफी मुश्किल समय इस उधार के लैपटॉप पर अच्छा समय पा रहा था , क्योंकि एक USB पर लिनक्स में बूट होना चाहिए)।
जब तक अन्यथा निर्दिष्ट न हो / लेखन / देव / अशक्त।
- जेम्स हैलिस: (परीक्षण नहीं)
- नाममात्र का लेखन संस्करण: ~ २.२१
- यह (SSE2): ~ 0.142 s ( बिना बिके समय = वास्तविक = 14.232s, उपयोगकर्ता = 13.999s, sys = 0.187s)।
- यह (AVX-128): ~ 0.140s
- यह (AVX2): ~ 0.073s ( अनसाल्टेड : वास्तविक = 0m7.291s, उपयोगकर्ता = 0m7.125s, sys = 0m0.155s)।
- यह (
wc -c
AVX2 ) साइबरविन पाइपिंग , 128kiB बफर साइज के साथ: 2.32GHz CPU के साथ 2.38GHz (अधिकतम डुअल-कोर टर्बो)। (अनसुलझा समय: वास्तविक = 32.466s उपयोगकर्ता = 11.468s sys = 41.092s, यह और दोनों सहित wc
)। केवल आधा डेटा वास्तव में कॉपी किया गया था, हालांकि, क्योंकि मेरा मूर्खतापूर्ण कार्यक्रम मानता है कि लिखना पूर्ण बफर करता है, भले ही ऐसा नहीं है और साइबरविन लिखते हैं () केवल एक पाइप में 64k प्रति कॉल करता है।
तो SSE2 के साथ यह @Nominal Animal के स्केलर कोड की तुलना में लगभग 15 गुना तेज है। AVX2 के साथ, यह लगभग 30 गुना तेज है। मैंने नाममात्र के कोड के एक संस्करण की कोशिश नहीं की, जो सिर्फ write()
इसके बजाय उपयोग करता है fwrite()
, लेकिन संभवतः बड़े बफ़र्स के लिए stdio ज्यादातर रास्ते से बाहर रहता है। यदि यह डेटा की प्रतिलिपि बना रहा है, तो यह बहुत मंदी के लिए जिम्मेदार होगा।
एक Core2Duo E6600 (मेरोम 2.4GHz, 32kiB निजी L1, 4MiB साझा L2 कैश), DDR2-533MHz पर 64-बिट लिनक्स 4.2 (Ubuntu 15.10) पर 1GB डेटा का उत्पादन करने के लिए टाइम्स । अभी भी लिखने के लिए एक 128kiB बफर आकार का उपयोग कर (), उस आयाम का पता नहीं लगाया है।
जब तक अन्यथा निर्दिष्ट न हो / लेखन / देव / अशक्त।
- (SSE2) ने न्यूलाइन हैंडलिंग के साथ और यादृच्छिक बाइट्स के प्रत्येक वेक्टर से अंकों के 4 वैक्टर: 0.183s (18.3 में 100GiB कर समय पर, लेकिन 1GiB रन के लिए इसी तरह के परिणाम)। प्रति चक्र 1.85 निर्देश।
- (SSE2) इस, पाइपिंग टू
wc -c
: 0.593s ( अनसाल्टेड : रियल = 59.266s यूजर = 20.148 sys = 1m6.548s, wc का CPU टाइम सहित)। समान संख्या में राइट () सिस्टम साइबरविन के साथ कॉल करता है, लेकिन वास्तव में सभी डेटा को पाइप करता है क्योंकि लिनक्स एक पाइप के लिए एक लेखन () के सभी 128k को संभालता है।
- नोमिनलिमल का
fwrite()
संस्करण (gcc5.2 -O3 -march=native
), साथ चलता है ./decdig 100 $((1024*1024*1024/200)) > /dev/null
: 3.19s +/- 0.1%, प्रति चक्र 1.40 निर्देश के साथ। -फिरोल-लूप्स ने शायद एक छोटे अंतर को बनाया। clang-3.8 -O3 -march=native
: 3.42s +/- 0.1%
- नाममात्र-
fwrite
पाइपिंग wc -c
: असली = 3.980s उपयोगकर्ता = 3.176 sys = 2.080s
- जेम्स हॉलिस का लाइन-ऑन-ए-टाइम संस्करण (
clang++-3.8 -O3 -march=native
): 22.885s +/- 0.07%, प्रति चक्र 0.84 निर्देश के साथ। (g ++ 5.2 थोड़ा धीमा था: 22.98s)। एक बार में केवल एक पंक्ति लिखने से संभवतः काफी चोट लगी है।
- स्टीफन चेज़लस
tr < /dev/urandom | ...
: रियल = 41.430s उपयोगकर्ता = 26.832 सेकेंड = 40.120 सेकेंड। tr
सभी समय के लिए सीपीयू कोर के सभी प्राप्त कर रहा था, कर्नेल चालक में लगभग अपना सारा समय यादृच्छिक बाइट्स बनाने और उन्हें एक पाइप पर कॉपी करने में खर्च कर रहा था। इस दोहरे कोर मशीन पर अन्य कोर बाकी पाइपलाइन चला रहा था।
time LC_ALL=C head -c512M </dev/urandom >/dev/null
: यानी बिना किसी पाइपिंग के बस इतना यादृच्छिकता पढ़ना: वास्तविक = 35.018s उपयोगकर्ता = 0.036 sys = 34.940 s।
- Lưu V (nh Phúc का पर्ल प्रोग्राम (Ubuntu15.10 से perl v5.20.2)
LANG=en_CA.UTF-8
:: वास्तविक = 4m32.634 उपयोगकर्ता = 4m3.288s sys = 0m29.364।
LC_ALL=C LANG=C
: real = 4m18.637s उपयोगकर्ता = 3m50.324s sys = 0m29.356s। अभी भी बहुत धीमी है।
- (SSE2) यह बिना किसी नई हैंडलिंग के साथ , और यादृच्छिक बाइट्स के प्रत्येक वेक्टर से अंकों के 3 या 4 वैक्टर (लगभग एक ही गति:
dig3 = v%10
कदम इस HW पर भी ब्रेक के बारे में है): 0.166s (1.82 निर्देश प्रति चक्र) । यह मूल रूप से उस सीमा की निचली सीमा है जो हम पूरी तरह से कुशल न्यूलाइन हैंडलिंग के साथ करीब आ सकते हैं।
- (SSE2) इसका कोई नया संस्करण नहीं है, लेकिन केवल एक अंक प्रति uint16_t तत्व का उपयोग करके
v%10
, 0.222 सेकंड +/- 0.4%, 2.12 निर्देश प्रति चक्र प्राप्त कर रहा है। (Gcc5.2 के साथ संकलित -march=native -O3 -funroll-loops
। इस हार्डवेयर पर इस कोड के लिए मदद करने के लिए अनियंत्रित लूप होते हैं। इसका उपयोग आँख बंद करके न करें, विशेषकर बड़े कार्यक्रमों के लिए)।
- (SSE2) इसका पुराना संस्करण, एक फाइल पर लिखना (3 फास्ट मैग्नेटिक हार्ड ड्राइव के RAID10f2 पर, लिखने के लिए बहुत अनुकूलित नहीं): ~ 4 सेकंड। लिखने से पहले बहुत अधिक गंदे डेटा को अनुमति देने के लिए कर्नेल I / O बफर सेटिंग्स को घुमाकर तेजी से जा सकते हैं। "सिस्टम" का समय अभी भी ~ 1.0 सेकंड है, "उपयोगकर्ता" समय से बहुत अधिक है। धीमे DDR2-533 रैम वाले इस पुराने सिस्टम पर, कर्नेल के लिए पेजकेच में डेटा को कम करने के लिए कर्नेल के लिए ~ 4x अधिक समय लगता है और मेरे लूप के लिए एक्सएफएस कार्यों को चलाने की तुलना में यह एक बफर में इसे फिर से लिखने के लिए रखता है - यह गर्म रहता है कैश।
यह कैसे किया है
एक तेजी से PRNG जाहिर है आवश्यक है। xorshift128 + को वेक्टर किया जा सकता है, इसलिए आपके पास सिमड वेक्टर के तत्वों में समानांतर में दो या चार 64-बिट जनरेटर होते हैं। प्रत्येक चरण यादृच्छिक बाइट्स की एक पूर्ण वेक्टर का उत्पादन करता है। ( इंटेल इंटिंसिक्स के साथ यहां 256b एवीएक्स 2 कार्यान्वयन )। मैंने इसे नॉमिनल की xorshift * की पसंद पर चुना, क्योंकि 64-बिट वेक्टर पूर्णांक गुणन केवल SSE2 / AVX2 में विस्तारित-सटीक तकनीकों के साथ संभव है ।
यादृच्छिक बाइट्स के एक वेक्टर को देखते हुए, हम प्रत्येक 16-बिट तत्व को कई दशमलव अंकों में काट सकते हैं। हम 16-बिट तत्वों के कई वैक्टर का उत्पादन करते हैं जो प्रत्येक एक ASCII अंक + ASCII स्थान हैं । हम सीधे अपने आउटपुट बफर में स्टोर करते हैं।
मेरा मूल संस्करण सिर्फ x / 6554
एक वेक्टर के हर uint16_t तत्व से एक यादृच्छिक अंक प्राप्त करने के लिए उपयोग किया जाता है। यह हमेशा 0 और 9 के बीच है, समावेशी है। यह पक्षपाती है 9
, क्योंकि (2^16 -1 ) / 6554
यह केवल 9.99923 है। (6554 = छत ((2 ^ 16-1) / 10), जो यह सुनिश्चित करता है कि भागफल हमेशा <10. है)
x/6554
एक "जादू" स्थिरांक ( निश्चित-बिंदु पारस्परिक ) द्वारा एक से गुणा किया जा सकता है और उच्च-आधे परिणाम का एक सही बदलाव। यह एक स्थिर द्वारा विभाजन के लिए सबसे अच्छा मामला है; कुछ भाजक अधिक ऑपरेशन करते हैं, और हस्ताक्षरित विभाजन अतिरिक्त काम लेता है। x % 10
एक समान पूर्वाग्रह है और गणना करने के लिए उतना सस्ता नहीं है। (gcc का asm आउटपुट बराबर है x - 10*(x/10)
, अर्थात एक मॉड्यूलर गुणन प्रतिलोम का उपयोग करके विभाजन के शीर्ष पर एक अतिरिक्त गुणा और घटाना है।) इसके अलावा, xorshift128 का सबसे कम बिट उच्च गुणवत्ता वाला नहीं है , इसलिए उच्च बिट्स से एंट्रॉपी लेने के लिए विभाजित करना बेहतर है ( गुणवत्ता के लिए और साथ ही गति) modulo से कम बिट्स से एंट्रोपी लेने के लिए।
हालाँकि, हम प्रत्येक दशमलव के अधिक अंक का उपयोग कर सकते हैं, कम दशमलव अंकों को देखकर, जैसे @ नाममात्र का digit()
कार्य। अधिकतम प्रदर्शन के लिए, मैंने कम 3 दशमलव अंक लेने का फैसला किया और x/6554
, एक PMULLW और PSUBW (और शायद कुछ MOVDQA) बनाम 4 निम्न दशमलव अंकों को लेने के उच्च गुणवत्ता विकल्प को बचाने के लिए। x / 6554 निम्न 3 दशमलव अंकों से थोड़ा प्रभावित होता है, इसलिए एक ही तत्व के अंकों (ASCII आउटपुट में 8 या 16 अंकों के अलगाव, वेक्टर चौड़ाई के आधार पर) के बीच कुछ सहसंबंध होता है।
मुझे लगता है कि जीसीसी 100 और 1000 से विभाजित कर रहा है, बजाय एक लंबी श्रृंखला के जो क्रमिक रूप से 10 से विभाजित होता है, इसलिए यह संभवतः गैर-लूप-निर्भर निर्भरता श्रृंखला की लंबाई को छोटा नहीं करता है जो प्रत्येक PRNG आउटपुट से 4 परिणाम उत्पन्न करता है। port0 (वेक्टर गुणा और शिफ्ट) मॉड्यूलर गुणक व्युत्क्रम और xorshift + में बदलाव के कारण अड़चन है, इसलिए वेक्टर-गुणा को बचाने के लिए यह निश्चित रूप से उपयोगी है।
xorshift + इतना तेज़ है कि हर 16 (यानी 20% दक्षता) से केवल 3.3 बिट्स यादृच्छिकता का उपयोग करते हुए भी इसे कई दशमलव अंकों में काट देने से बहुत धीमा नहीं है। हम केवल समान वितरण को अनुमानित करते हैं, क्योंकि यह उत्तर गति पर केंद्रित है जब तक कि गुणवत्ता बहुत खराब न हो।
किसी भी प्रकार का सशर्त व्यवहार जो तत्वों की एक चर संख्या रखता है, बहुत अधिक काम करेगा। (लेकिन सिमड लेफ्ट-पैकिंग तकनीक का उपयोग करके शायद अभी भी कुछ हद तक कुशलता से किया जा सकता है । हालांकि, छोटे तत्व के लिए कम कुशल हो जाता है; विशाल शफल-मास्क लुकअप टेबल व्यवहार्य नहीं हैं, और 32 से छोटे के साथ कोई AVX2 लेन-क्रॉसिंग फेरबदल नहीं है। बिट तत्व। 128b PSHUFB संस्करण अभी भी BMI2 PEXT / PDEP के साथ फ्लाई पर मास्क उत्पन्न करने में सक्षम हो सकता है , जैसे आप बड़े तत्वों के साथ AVX2 के लिए कर सकते हैं , लेकिन यह मुश्किल है क्योंकि 64-बिट पूर्णांक केवल 8 बाइट्स के साथ है। गॉडबोल्ट लिंक उस उत्तर पर कुछ कोड है जो उच्च तत्व गणना के लिए काम कर सकता है।)
यदि RNG की विलंबता एक अड़चन है, तो हम समानांतर में दो जनरेटर के वैक्टर चलाकर और भी तेजी से जा सकते हैं, जो हम उपयोग करते हैं। कंपाइलर अभी भी आसानी से एक अनियंत्रित लूप में रजिस्टरों में सब कुछ रख सकता है, और इससे दो निर्भरता श्रृंखला समानांतर में चलती हैं।
वर्तमान संस्करण में, PRNG के आउटपुट को काटते हुए, हम वास्तव में पोर्ट 0 थ्रूपुट पर अड़चन हैं, PRNG विलंबता नहीं, इसलिए इसके लिए कोई आवश्यकता नहीं है।
कोड: AVX2 संस्करण
Godbolt संकलक एक्सप्लोरर पर अधिक टिप्पणियों के साथ पूर्ण संस्करण ।
बहुत चुस्त नहीं, माफ करना मुझे सोना है और इस पोस्ट को प्राप्त करना है।
SSE2 संस्करण प्राप्त करने के लिए, s/_mm256/_mm
, s/256/128/
, s/v16u/v8u/
, और बदलने के vector_size(32)
अलावा 4 * 16 के लिए 4 * 8 से न्यू लाइन वेतन वृद्धि बदल 16. करने के लिए। (जैसा कि मैंने कहा, कोड गड़बड़ है, और दो संस्करणों को संकलित करने के लिए अच्छी तरह से सेट नहीं किया गया है। मूल रूप से एक AVX2 संस्करण बनाने की योजना नहीं है, लेकिन तब मैं वास्तव में एक हसवेल सीपीयू पर परीक्षण करना चाहता था जिसकी मुझे पहुंच थी।)
#include <immintrin.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
//#include <string.h>
// This would work equally fast 128b or 256b at a time (AVX2):
// https://stackoverflow.com/questions/24001930/avx-sse-version-of-xorshift128
struct rngstate256 {
__m256i state0;
__m256i state1;
};
static inline __m256i xorshift128plus_avx2(struct rngstate256 *sp)
{
__m256i s1 = sp->state0;
const __m256i s0 = sp->state1;
sp->state0 = s0;
s1 = _mm256_xor_si256(s1, _mm256_slli_epi64(s1, 23));
__m256i state1new = _mm256_xor_si256(_mm256_xor_si256(_mm256_xor_si256(s1, s0),
_mm256_srli_epi64(s1, 18)),
_mm256_srli_epi64(s0, 5));
sp->state1 = state1new;
return _mm256_add_epi64(state1new, s0);
}
// GNU C native vectors let us get the compiler to do stuff like %10 each element
typedef unsigned short v16u __attribute__((vector_size(32)));
__m256i* vec_store_digit_and_space(__m256i vec, __m256i *restrict p)
{
v16u v = (v16u)vec;
v16u ten = (v16u)_mm256_set1_epi16(10);
v16u divisor = (v16u)_mm256_set1_epi16(6554); // ceil((2^16-1) / 10.0)
v16u div6554 = v / divisor; // Basically the entropy from the upper two decimal digits: 0..65.
// Probably some correlation with the modulo-based values, especially dig3, but we do this instead of
// dig4 for more ILP and fewer instructions total.
v16u dig1 = v % ten;
v /= ten;
v16u dig2 = v % ten;
v /= ten;
v16u dig3 = v % ten;
// dig4 would overlap much of the randomness that div6554 gets
const v16u ascii_digitspace = (v16u)_mm256_set1_epi16( (' '<<8) | '0');
v16u *vecbuf = (v16u*)p;
vecbuf[0] = div6554 | ascii_digitspace;
vecbuf[1] = dig1 | ascii_digitspace;
vecbuf[2] = dig2 | ascii_digitspace;
vecbuf[3] = dig3 | ascii_digitspace;
return p + 4; // always a constant number of full vectors
}
void random_decimal_fill_buffer(char *restrict buf, size_t len, struct rngstate256 *restrict rngstate)
{
buf = __builtin_assume_aligned(buf, 32);
// copy to a local so clang can keep state in register, even in the non-inline version
// restrict works for gcc, but apparently clang still thinks that *buf might alias *rngstate
struct rngstate256 rng_local = *rngstate;
__m256i *restrict p = (__m256i*restrict)buf;
__m256i *restrict endbuf = (__m256i*)(buf+len);
static unsigned newline_pos = 0;
do {
__m256i rvec = xorshift128plus_avx2(&rng_local);
p = vec_store_digit_and_space(rvec, p); // stores multiple ASCII vectors from the entropy in rvec
#if 1
// this is buggy at the end or start of a power-of-2 buffer:
// usually there's a too-short line, sometimes a too-long line
const unsigned ncols = 100;
newline_pos += 4*16;
if (newline_pos >= ncols) {
newline_pos -= ncols;
char *cur_pos = (char*)p;
*(cur_pos - newline_pos*2 - 1) = '\n';
}
#endif
// Turning every 100th space into a newline.
// 1) With an overlapping 1B store to a location selected by a counter. A down-counter would be more efficient
// 2) Or by using a different constant for ascii_digitspace to put a newline in one element
// lcm(200, 16) is 400 bytes, so unrolling the loop enough to produce two full lines makes a pattern of full vectors repeat
// lcm(200, 32) is 800 bytes
// a power-of-2 buffer size doesn't hold a whole number of lines :/
// I'm pretty sure this can be solved with low overhead, like maybe 10% at worst.
} while(p <= endbuf-3);
*rngstate = rng_local;
}
#define BUFFER_SIZE (128 * 1024)
const static size_t bufsz = BUFFER_SIZE;
__attribute__((aligned(64))) static char static_buf[BUFFER_SIZE];
int main(int argc, char *argv[])
{
// TODO: choose a seed properly. (Doesn't affect the speed)
struct rngstate256 xorshift_state = {
_mm256_set_epi64x(123, 456, 0x123, 0x456),
_mm256_set_epi64x(789, 101112, 0x789, 0x101112)
};
for (int i=0; i < 1024ULL*1024*1024 / bufsz * 100; i++) {
random_decimal_fill_buffer(static_buf, bufsz, &xorshift_state);
size_t written = write(1, static_buf, bufsz);
(void)written;
//fprintf(stderr, "wrote %#lx of %#lx\n", written, bufsz);
}
}
Gcc, clang, या ICC के साथ संकलन (या उम्मीद है कि कोई अन्य संकलक जो GN99 C की C99 की समझ और इंटेल की आंतरिकता को समझता है)। GNU C वेक्टर एक्सटेंशन कंपाइलर / मोडुलो के लिए जादुई संख्याओं का उपयोग करते हुए कंपाइलर प्राप्त करने के लिए अत्यधिक सुविधाजनक है __attribute__
।
यह आंशिक रूप से लिखा जा सकता है, लेकिन यह अधिक कोड ले जाएगा।
प्रदर्शन नोट:
ओवरलैपिंग-स्टोर में न्यूलाइन्स डालने के लिए यह तय करने के लिए महत्वपूर्ण ओवरहेड है कि इसे कहां रखा जाए (ब्रांच मिसप्रिडिक्शन, और कोर 2 पर अड़चनें), लेकिन स्टोर पर ही प्रदर्शन पर कोई प्रभाव नहीं पड़ता है। कंपाइलर की एशम में बस उस स्टोर इंस्ट्रक्शन को कमेंट करते हुए (सभी ब्रांचिंग को समान करते हुए) कोर 2 पर प्रदर्शन को पूरी तरह से अपरिवर्तित छोड़ दिया, साथ ही बार-बार रन देने का समय +/- 1% से कम था। इसलिए मैं यह निष्कर्ष निकालता हूं कि स्टोर बफर / कैश इसे ठीक से संभालता है।
फिर भी, ascii_digitspace
एक तत्व के साथ एक नई तरह की घूर्णन खिड़की का उपयोग करना एक नई रेखा से भी तेज हो सकता है, अगर हम पर्याप्त रूप से अनियंत्रित हो जाते हैं कि कोई भी काउंटर / ब्रांचिंग चले जाते हैं।
/ Dev / null को लिखना मूल रूप से एक no-op है, इसलिए बफर संभवतः L2 कैश में गर्म रहता है (Haswell पर 256kiB प्रति कोर)। 128b वैक्टर से 256b वैक्टर तक का सही स्पीडअप अपेक्षित है: कोई अतिरिक्त निर्देश नहीं हैं, और सब कुछ (दुकानों सहित) दो बार चौड़ाई के साथ होता है। नईलाइन-प्रविष्टि शाखा को दो बार लिया जाता है, हालांकि। मैंने दुर्भाग्यवश उस भाग के #ifdef
एड के साथ अपने हैसवेल साइबरविन सेटअप पर समय नहीं दिया।
2.5GHz * 32B / 13.7GB / s = 5.84 साइकिल प्रति AVX2-Haswell पर स्टोर। यह बहुत अच्छा है, लेकिन तेज हो सकता है। शायद मैंने सोचा था कि साइबरविन सिस्टम कॉल में कुछ ओवरहेड है। मैंने कंपाइलर के एसएसएम आउटपुट में उन पर टिप्पणी करने की कोशिश नहीं की (जो यह सुनिश्चित करेगा कि कुछ भी अनुकूलित न हो।)
L1 कैश प्रति घड़ी एक 32B स्टोर को बनाए रख सकता है, और L2 बहुत कम बैंडविड्थ (उच्च विलंबता, हालांकि) नहीं है।
जब मैंने IACA को कुछ संस्करणों में देखा था (नई शाखाओं के लिए शाखा के बिना, लेकिन केवल एक ASCII वेक्टर प्रति RNG वेक्टर मिल रहा था), यह 4 या 5 घड़ियों के प्रति 32B वेक्टर स्टोर की तरह कुछ भविष्यवाणी कर रहा था।
मैं प्रत्येक RNG परिणाम से अधिक डेटा निकालने से स्पीडअप के अधिक होने की उम्मीद कर रहा था, जो कि खुद को देखने के आधार पर, एग्नर फॉग के गाइड और अन्य अनुकूलन संसाधनों पर विचार कर रहा है, जो मैंने एसओ x86 टैग विकी में लिंक जोड़े हैं ।
संभवतः यह स्काइलेक पर काफी तेज होगा , जहां वेक्टर पूर्णांक गुणा और बदलाव दो बार कई बंदरगाहों (p0 / p1) पर चल सकता है, हसवेल (p0 केवल) की तुलना में। xorshift और डिजिट एक्सट्रैक्शन दोनों ही बहुत सारी पारियों और गुणाओं का उपयोग करते हैं। ( अपडेट: स्काईलेक ने इसे 3.02 आईपीसी पर चलाया, हमें 3.77 साइकिल प्रति 32-बाइट एवीएक्स 2 स्टोर पर दी गई, जोकि 1 जीबी प्रति सेकंड के मग में समय पर मिली, /dev/null
लिनक्स 4.15 पर i7-6700k पर 3.9GHz पर लिखी गई।
अच्छी तरह से काम करने के लिए 64-बिट मोड की आवश्यकता नहीं है । SSE2 संस्करण केवल उतनी ही तेजी से है जब इसके साथ संकलित किया जाता है -m32
, क्योंकि इसमें बहुत अधिक वेक्टर रजिस्टरों की आवश्यकता नहीं होती है, और सभी 64-बिट गणित वैक्टर में किया जाता है, न कि सामान्य-उद्देश्य रजिस्टरों में।
यह वास्तव में Core2 पर 32-बिट मोड में थोड़ा तेज है, क्योंकि मैक्रो-फ्यूजन की तुलना केवल 32-बिट मोड में काम करती है, इसलिए आउट-ऑफ-ऑर्डर कोर (18.3s (1.85 निर्देश प्रति घड़ी) बनाम के लिए कम यूओपी हैं। । 16.9s (2.0 IPC))। कोई REX उपसर्ग होने से छोटे कोड-आकार भी Core2 के डिकोडर्स में मदद करता है।
इसके अलावा, कुछ रेग-रेग वेक्टर चालों को भार के साथ बदल दिया जाता है, क्योंकि सभी स्थिरांक वेक्टर वेक्टर में अब ठीक नहीं होते हैं। चूंकि L1 कैश से लोड थ्रूपुट एक अड़चन नहीं है, यह वास्तव में मदद करता है। (जैसे set1(10)
: एक स्थिर वेक्टर के गुणा से movdqa xmm0, xmm10
/ में pmullw xmm0, xmm1
बदल जाता है movdqa xmm0, [constant]
/ pmullw xmm0, xmm1
।) चूंकि reg-reg MOVDQA को ALU पोर्ट की आवश्यकता होती है, यह वास्तविक कार्य के साथ प्रतिस्पर्धा करता है, लेकिन एक MOVDQA लोड केवल फ्रंट-एंड डायोड बैंडविड्थ के लिए प्रतिस्पर्धा करता है। (REX उपसर्गों को बचाने से कई निर्देशों को रद्द करते हुए, कई निर्देशों के अंदर एक 4-बाइट पते के साथ।
मुझे आश्चर्य नहीं होगा अगर ALU MOVDQA को बचाने के लिए वास्तविक लाभ वहां से आ रहा है, क्योंकि दृश्यपटल को 2.0 IPC के औसत के साथ अच्छी तरह से रखना चाहिए।
ये सभी अंतर हैसवेल पर गायब हो जाते हैं, जहां पूरी तरह से डिकोड-यूओपी कैश से चलना चाहिए, अगर लूपबैक बफर नहीं। नेहलम के बाद से ALU + ब्रांच मैक्रो-फ्यूजन दोनों मोड में काम करता है।