जब, अगर कभी, लूप अभी भी उपयोगी है?


93

मैं कुछ अत्यंत प्रदर्शन-क्रिटिकल कोड (एक त्वरित सॉर्ट एल्गोरिथ्म जो लाखों और लाखों बार एक मोंटे कार्लो सिमुलेशन के अंदर कहा जा रहा है) को लूप अन्रॉलिंग द्वारा अनुकूलित करने की कोशिश कर रहा हूं। यहाँ मैं आंतरिक लूप की कोशिश कर रहा हूँ:

// Search for elements to swap.
while(myArray[++index1] < pivot) {}
while(pivot < myArray[--index2]) {}

मैंने कुछ करने की कोशिश की जैसे:

while(true) {
    if(myArray[++index1] < pivot) break;
    if(myArray[++index1] < pivot) break;
    // More unrolling
}


while(true) {
    if(pivot < myArray[--index2]) break;
    if(pivot < myArray[--index2]) break;
    // More unrolling
}

इससे बिल्कुल कोई फर्क नहीं पड़ा इसलिए मैंने इसे और अधिक पठनीय रूप में बदल दिया। मुझे अन्य बार भी इसी तरह के अनुभव हुए हैं मैंने लूप को अनियंत्रित करने की कोशिश की है। आधुनिक हार्डवेयर पर शाखा भविष्यवाणियों की गुणवत्ता को देखते हुए, यदि कभी, लूप अभी भी एक उपयोगी अनुकूलन को नियंत्रित कर रहा है?


1
क्या मैं पूछ सकता हूं कि आप मानक पुस्तकालय क्विकसेट रूटीन का उपयोग क्यों नहीं कर रहे हैं?
पीटर अलेक्जेंडर

14
@Poita: क्योंकि मेरे पास कुछ अतिरिक्त विशेषताएं हैं जिनकी मुझे उन सांख्यिकीय गणनाओं की आवश्यकता है जो मैं कर रहा हूं और अपने उपयोग के मामलों के लिए बहुत उच्च स्तर पर हूं और इसलिए सामान्य रूप से मानक कार्य की तुलना में सामान्य रूप से कम लेकिन तेजी से। मैं डी प्रोग्रामिंग भाषा का उपयोग कर रहा हूं, जिसमें एक पुराना भद्दा आशावादी है, और यादृच्छिक फ़्लोट के बड़े सरणियों के लिए, मैं अभी भी जीसीसी के सी ++ एसटीएल सॉर्ट को 10-20% से हराता हूं।
dsimcha

जवाबों:


122

यदि आप निर्भरता श्रृंखलाओं को तोड़ सकते हैं, तो लूप का अनियंत्रित होना समझ में आता है। यह ऑर्डर ऑफ या सुपर-स्केलर सीपीयू से चीजों को बेहतर तरीके से शेड्यूल करने की संभावना देता है और इस तरह तेजी से चलता है।

एक सरल उदाहरण:

for (int i=0; i<n; i++)
{
  sum += data[i];
}

यहाँ तर्कों की निर्भरता श्रृंखला बहुत कम है। यदि आपको स्टाल मिलता है क्योंकि आपके पास डेटा-सरणी पर कैश-मिस है तो सीपीयू कुछ भी नहीं कर सकता है लेकिन प्रतीक्षा करने के लिए।

दूसरी ओर यह कोड:

for (int i=0; i<n; i+=4)
{
  sum1 += data[i+0];
  sum2 += data[i+1];
  sum3 += data[i+2];
  sum4 += data[i+3];
}
sum = sum1 + sum2 + sum3 + sum4;

तेजी से भाग सकता था। यदि आपको एक गणना में कैश मिस या अन्य स्टाल मिलता है, तो अभी भी तीन अन्य निर्भरता श्रृंखलाएं हैं जो स्टाल पर निर्भर नहीं हैं। एक आउट ऑफ ऑर्डर सीपीयू इन पर अमल कर सकता है।


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

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

2
ध्यान रखें कि इस तरह से राशि की गणना करते समय परिणाम मूल लूप के समान नहीं होगा।
बरबस

पाश-चालित निर्भरता एक चक्र है , जोड़। एक OoO कोर ठीक करेगा। यहां अनियंत्रित होकर फ्लोटिंग पॉइंट SIMD की मदद मिल सकती है, लेकिन यह OoO के बारे में नहीं है।
विड्रैक

2
@ निल्स: बहुत ज्यादा नहीं; मुख्यधारा x86 OoO CPU अभी भी Core2 / Nehalem / K10 के समान है। कैची मिस होने के बाद भी पकड़ना बहुत मामूली था, फिर भी एफपी लेटेंसी को छिपाना अभी भी प्रमुख लाभ था। 2010 में, सीपीयू जो प्रति घड़ी 2 लोड कर सकते थे, यहां तक ​​कि दुर्लभ थे (सिर्फ एएमडी क्योंकि एसएनबी अभी तक जारी नहीं किया गया था), इसलिए कई संचयक निश्चित रूप से पूर्णांक कोड के लिए अब की तुलना में कम मूल्यवान थे (बेशक यह अदिश कोड है जो ऑटो-वेक्टर करना चाहिए , तो कौन जानता है कि संकलक कई संचयकों को वेक्टर तत्वों में बदल देगा या कई वेक्टर संचयकों में ...)
पीटर कॉर्ड

25

इससे कोई फर्क नहीं पड़ेगा क्योंकि आप समान संख्या में तुलना कर रहे हैं। यहाँ एक बेहतर उदाहरण है। के बजाय:

for (int i=0; i<200; i++) {
  doStuff();
}

लिखो:

for (int i=0; i<50; i++) {
  doStuff();
  doStuff();
  doStuff();
  doStuff();
}

फिर भी यह लगभग निश्चित रूप से मायने नहीं रखेगा लेकिन आप अब 200 की बजाय 50 तुलना कर रहे हैं (कल्पना करें कि तुलना अधिक जटिल है)।

मैनुअल लूप सामान्य तौर पर हालांकि इतिहास की एक कलाकृति है। यह उन चीजों की बढ़ती सूची में से एक है जो एक अच्छा संकलक आपके लिए काम करेगा जब यह मायने रखता है। उदाहरण के लिए, ज्यादातर लोग लिखने x <<= 1या x += xइसके बजाय परेशान नहीं करते हैं x *= 2। आप बस लिखते हैं x *= 2और जो कुछ भी सबसे अच्छा है, कंपाइलर आपके लिए उसका अनुकूलन करेगा।

मूल रूप से आपके कंपाइलर का अनुमान लगाने की आवश्यकता कम होती जा रही है।


1
@ माइक निश्चित रूप से अनुकूलन को बंद कर देता है यदि एक अच्छा विचार है जब हैरान हो, लेकिन यह उस लिंक को पढ़ने के लायक है जिसे Poita_ ने पोस्ट किया है। कंपाइलरों को उस व्यवसाय में अच्छा दर्द हो रहा है ।
dmckee --- पूर्व-मध्यस्थ ने बिल्ली

16
@ माइक "मैं यह तय करने में पूरी तरह से सक्षम हूं कि कब या उन चीजों को नहीं करना है" ... मुझे संदेह है, जब तक कि आप अलौकिक न हों।
मिस्टर बॉय

5
@ जॉन: मुझे नहीं पता कि आप ऐसा क्यों कहते हैं; लोगों को लगता है कि अनुकूलन किसी प्रकार की काली कला है केवल संकलक और अच्छे अनुमानक जानते हैं कि कैसे करना है। यह सभी निर्देशों और चक्रों और उन कारणों के लिए नीचे आता है जिनके कारण उन्हें खर्च किया जाता है। जैसा कि मैंने SO पर कई बार समझाया है, यह बताना आसान है कि कैसे और क्यों खर्च किए जा रहे हैं। यदि मुझे एक लूप मिला है जिसमें एक महत्वपूर्ण प्रतिशत का उपयोग करना है, और यह लूप ओवरहेड में बहुत अधिक चक्र खर्च करता है, तो सामग्री की तुलना में, मैं इसे देख सकता हूं और इसे अनियंत्रित कर सकता हूं। कोड फहराने के लिए भी। यह एक जीनियस नहीं है।
माइक डनलवे

3
मुझे यकीन है कि यह इतना कठिन नहीं है, लेकिन मुझे अभी भी संदेह है कि आप इसे तेजी से कर सकते हैं जैसा कि संकलक करता है। वैसे भी आपके लिए कंपाइलर के साथ क्या समस्या है? यदि आपको यह पसंद नहीं है तो बस अनुकूलन को बंद कर दें और अपना समय दूर की तरह जलाएं जैसे कि यह 1990 है!
श्री बॉय

2
पाश अनियंत्रित होने के कारण प्रदर्शन लाभ का उन तुलनाओं से कोई लेना-देना नहीं है जिन्हें आप बचा रहे हैं। कुछ भी नहीं।
बोब्बोगो

14

आधुनिक हार्डवेयर पर शाखा की भविष्यवाणी के बावजूद, अधिकांश संकलक आपके लिए वैसे भी अनियंत्रित लूप करते हैं।

यह पता लगाना सार्थक होगा कि आपका कंपाइलर आपके लिए कितना अनुकूलन करता है।

मुझे फेलिक्स वॉन लीटनर की प्रस्तुति इस विषय पर बहुत ज्ञानवर्धक लगी । मैं आपको इसे पढ़ने की सलाह देता हूं। सारांश: आधुनिक संकलक बहुत चालाक होते हैं, इसलिए हाथ के अनुकूलन लगभग कभी भी प्रभावी नहीं होते हैं।


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

3
"हाथ का अनुकूलन लगभग कभी भी प्रभावी नहीं होता है" → यदि आप कार्य के लिए पूरी तरह से नए हैं तो शायद सच है। बस सच नहीं तो।
विड्रैक

2019 में मैंने अभी भी संकलक के ऑटो प्रयासों पर पर्याप्त लाभ के साथ मैनुअल अनियंत्रित किया है .. इसलिए इसकी संकलक को ऐसा करने के लिए विश्वसनीय नहीं है। ऐसा लगता है कि यह सब अक्सर अनियंत्रित नहीं होता है। कम से कम c # के लिए मैं सभी भाषाओं की ओर से नहीं बोल सकता।
WDUK

2

जहां तक ​​मैं इसे समझता हूं, आधुनिक संकलक पहले से ही छोरों को अनियंत्रित करते हैं जहां उपयुक्त है - एक उदाहरण जीसीसी, यदि अनुकूलन झंडे पारित कर देता है तो यह मैनुअल कहता है:

अनियंत्रित लूप जिनके पुनरावृत्तियों की संख्या संकलन समय पर या लूप में प्रवेश पर निर्धारित की जा सकती है।

तो, व्यवहार में यह संभावना है कि आपका संकलक आपके लिए तुच्छ मामलों को करेगा। यह आप पर निर्भर करता है कि यह सुनिश्चित करने के लिए कि आपके लूप के कितने संभव संकलक के लिए आसान हैं, यह निर्धारित करने के लिए कि कितने पुनरावृत्तियों की आवश्यकता होगी।


बस समय में संकलक आमतौर पर लूप को अनियंत्रित नहीं करते हैं, उत्तराधिकार बहुत महंगे हैं। स्थैतिक संकलक इस पर अधिक समय बिता सकते हैं, लेकिन दो प्रमुख तरीकों के बीच का अंतर महत्वपूर्ण है।
हाबिल

2

लूप अनरोलिंग, चाहे वह हैंड अनरोलिंग या कंपाइलर अनरोलिंग हो, अक्सर काउंटर-प्रोडक्टिव हो सकता है, खासकर अधिक हालिया x86 CPUs (कोर 2, कोर i7) के साथ। नीचे पंक्ति: जिस भी सीपीयू पर आप इस कोड को तैनात करने की योजना बनाते हैं, उस पर बिना लूप के और बिना लूप के अपने कोड को बेंचमार्क करें।


विशेष रूप से x86 सीपीयू पर क्यों?
जॉनटोर्टुगो

7
@JohnTortugo: आधुनिक x86 सीपीयू में छोटे छोरों के लिए कुछ अनुकूलन हैं - उदाहरण के लिए लूप स्ट्रीम डिटेक्टर ऑन कोर और नेह्मल अचीवर्सिटर्स - एक लूप को अनियंत्रित करना ताकि एलएसडी कैश के भीतर फिट होने के लिए यह अब छोटा न हो, इस अनुकूलन को हरा देता है। उदाहरण के लिए देखें tomshardware.com/reviews/Intel-i7-nehalem-cpu,2041-3.html
Paul R

1

बिना जाने की कोशिश करना ऐसा करने का तरीका नहीं है।
क्या यह सॉर्ट कुल समय का उच्च प्रतिशत लेता है?

सभी लूप को अनियंत्रित करता है लूप ओवरहेडिंग घटाना / घटाना, स्टॉप स्थिति की तुलना करना, और कूदना कम करता है। यदि आप लूप में कर रहे हैं तो लूप ओवरहेड की तुलना में अधिक निर्देश चक्र लेता है, तो आपको बहुत सुधार प्रतिशत-वार नहीं दिखाई देगा।

यहां बताया गया है कि अधिकतम प्रदर्शन कैसे प्राप्त करें।


1

लूप अनरोलिंग विशिष्ट मामलों में सहायक हो सकता है। केवल लाभ कुछ परीक्षण लंघन नहीं है!

उदाहरण के लिए, स्केलर प्रतिस्थापन, सॉफ्टवेयर प्रीफ़ेक्टिंग के कुशल सम्मिलन की अनुमति दे सकता है ... आपको वास्तव में आश्चर्य होगा कि यह कैसे उपयोगी हो सकता है (-O3 के साथ-साथ अधिकांश छोरों पर आप आसानी से 10% स्पीडअप प्राप्त कर सकते हैं)।

जैसा कि पहले कहा गया था, यह लूप पर बहुत निर्भर करता है और संकलक और प्रयोग आवश्यक है। यह एक नियम बनाना मुश्किल है (या अनियंत्रित होने के लिए संकलक अनुमान सही होगा)


0

पूरी तरह से अनियंत्रित होना लूप आपकी समस्या के आकार पर निर्भर करता है। यह पूरी तरह से आपके एल्गोरिथ्म पर निर्भर है जो काम के छोटे समूहों में आकार को कम करने में सक्षम है। आपने ऊपर जो किया वह ऐसा नहीं दिखता है। मुझे यकीन नहीं है कि एक मोंटे कार्लो सिमुलेशन भी अनियंत्रित हो सकता है।

लूप के अनियंत्रित होने के लिए मैं अच्छा परिदृश्य एक छवि को घुमाएगा। चूंकि आप काम के अलग-अलग समूहों को घुमा सकते हैं। इसे काम पर लाने के लिए आपको पुनरावृत्तियों की संख्या कम करनी होगी।


मैं एक त्वरित प्रकार को नियंत्रित कर रहा था जो कि मेरे सिमुलेशन के मुख्य लूप से कहा जाता है, न कि सिमुलेशन के मुख्य लूप से।
dsimcha

0

लूप अनियंत्रित करना अभी भी उपयोगी है अगर लूप में और उसके साथ दोनों में बहुत सारे स्थानीय चर हैं। लूप इंडेक्स के लिए एक को बचाने के बजाय उन रजिस्टरों का अधिक उपयोग करना।

आपके उदाहरण में, आप रजिस्टरों का उपयोग न करते हुए, कम मात्रा में स्थानीय चर का उपयोग करते हैं।

यदि तुलना भारी (यानी गैर- testनिर्देश) है, तो तुलना (लूप एंड) भी एक बड़ी कमी है , खासकर अगर यह किसी बाहरी फ़ंक्शन पर निर्भर करता है।

पाश अनियंत्रित होने से शाखा भविष्यवाणी के लिए सीपीयू की जागरूकता बढ़ाने में मदद मिलती है, लेकिन वे वैसे भी होते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.