c++ - इंटेल सैंडीब्रिज-परिवार सीपीयू में पाइपलाइन के लिए एक कार्यक्रम का उद्घाटन करना




optimization x86 (3)

आप long double अभिकलन के लिए उपयोग कर सकते हैं । X86 पर यह 80-बिट प्रारूप होना चाहिए। इसके लिए केवल विरासत, x87 FPU का समर्थन है।

X87 FPU की कुछ कमियाँ:

  1. SIMD की कमी, अधिक निर्देशों की आवश्यकता हो सकती है।
  2. स्टैक आधारित, सुपर स्केलर और पाइपलाइड आर्किटेक्चर के लिए समस्याग्रस्त।
  3. रजिस्टरों के अलग और काफी छोटे सेट, अन्य रजिस्टरों से अधिक रूपांतरण और अधिक मेमोरी संचालन की आवश्यकता हो सकती है।
  4. कोर i7 पर SSE के लिए 3 पोर्ट हैं और x87 के लिए केवल 2, प्रोसेसर कम समानांतर निर्देशों को निष्पादित कर सकता है।

मैं इस कार्य को पूरा करने की कोशिश में एक हफ्ते से अपना दिमाग लगा रहा हूं और उम्मीद कर रहा हूं कि यहां कोई मुझे सही रास्ते पर ले जा सकता है। मुझे प्रशिक्षक के निर्देशों से शुरू करें:

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

कार्यक्रम को अपनाने के लिए, अपने ज्ञान का उपयोग करें कि इंटेल i7 पाइपलाइन कैसे संचालित होती है। WAR, RAW और अन्य खतरों को पेश करने के लिए अनुदेश पथों को फिर से क्रम में लाने के तरीकों की कल्पना करें। कैश की प्रभावशीलता को कम करने के तरीकों के बारे में सोचें। शैतानी अक्षम हो।

असाइनमेंट ने वेटस्टोन या मोंटे-कार्लो कार्यक्रमों का विकल्प दिया। कैश-इफ़ेक्ट-कमेंट्स ज्यादातर केवल वेटस्टोन पर लागू होते हैं, लेकिन मैंने मोंटे-कार्लो सिमुलेशन प्रोग्राम चुना:

// Un-modified baseline for pessimization, as given in the assignment
#include <algorithm>    // Needed for the "max" function
#include <cmath>
#include <iostream>

// A simple implementation of the Box-Muller algorithm, used to generate
// gaussian random numbers - necessary for the Monte Carlo method below
// Note that C++11 actually provides std::normal_distribution<> in 
// the <random> library, which can be used instead of this function
double gaussian_box_muller() {
  double x = 0.0;
  double y = 0.0;
  double euclid_sq = 0.0;

  // Continue generating two uniform random variables
  // until the square of their "euclidean distance" 
  // is less than unity
  do {
    x = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
    y = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
    euclid_sq = x*x + y*y;
  } while (euclid_sq >= 1.0);

  return x*sqrt(-2*log(euclid_sq)/euclid_sq);
}

// Pricing a European vanilla call option with a Monte Carlo method
double monte_carlo_call_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
  double S_adjust = S * exp(T*(r-0.5*v*v));
  double S_cur = 0.0;
  double payoff_sum = 0.0;

  for (int i=0; i<num_sims; i++) {
    double gauss_bm = gaussian_box_muller();
    S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm);
    payoff_sum += std::max(S_cur - K, 0.0);
  }

  return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
}

// Pricing a European vanilla put option with a Monte Carlo method
double monte_carlo_put_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
  double S_adjust = S * exp(T*(r-0.5*v*v));
  double S_cur = 0.0;
  double payoff_sum = 0.0;

  for (int i=0; i<num_sims; i++) {
    double gauss_bm = gaussian_box_muller();
    S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm);
    payoff_sum += std::max(K - S_cur, 0.0);
  }

  return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
}

int main(int argc, char **argv) {
  // First we create the parameter list                                                                               
  int num_sims = 10000000;   // Number of simulated asset paths                                                       
  double S = 100.0;  // Option price                                                                                  
  double K = 100.0;  // Strike price                                                                                  
  double r = 0.05;   // Risk-free rate (5%)                                                                           
  double v = 0.2;    // Volatility of the underlying (20%)                                                            
  double T = 1.0;    // One year until expiry                                                                         

  // Then we calculate the call/put values via Monte Carlo                                                                          
  double call = monte_carlo_call_price(num_sims, S, K, r, v, T);
  double put = monte_carlo_put_price(num_sims, S, K, r, v, T);

  // Finally we output the parameters and prices                                                                      
  std::cout << "Number of Paths: " << num_sims << std::endl;
  std::cout << "Underlying:      " << S << std::endl;
  std::cout << "Strike:          " << K << std::endl;
  std::cout << "Risk-Free Rate:  " << r << std::endl;
  std::cout << "Volatility:      " << v << std::endl;
  std::cout << "Maturity:        " << T << std::endl;

  std::cout << "Call Price:      " << call << std::endl;
  std::cout << "Put Price:       " << put << std::endl;

  return 0;
}

मेरे द्वारा किए गए परिवर्तनों से कोड के चलने के समय में एक सेकंड की वृद्धि हुई है, लेकिन मुझे पूरी तरह से यकीन नहीं है कि मैं कोड जोड़ने के बिना पाइपलाइन को स्टाल करने के लिए क्या बदल सकता हूं। सही दिशा के लिए एक बिंदु भयानक होगा, मैं किसी भी प्रतिक्रिया की सराहना करता हूं।

अपडेट: इस असाइनमेंट को देने वाले प्रोफेसर ने कुछ विवरण पोस्ट किए

मुख्य आकर्षण हैं:

  • यह एक सामुदायिक कॉलेज में (हेनेसी और पैटरसन पाठ्यपुस्तक का उपयोग करके) दूसरे सेमेस्टर आर्किटेक्चर वर्ग है।
  • लैब कंप्यूटर में हैसवेल सीपीयू होते हैं
  • छात्रों को CPUID अनुदेश और कैश आकार का निर्धारण करने के लिए, साथ ही आंतरिक और CLFLUSH अनुदेश से अवगत कराया गया है।
  • किसी भी संकलक विकल्प की अनुमति है, और इसलिए इनलाइन एएसएम है।
  • अपने स्वयं के वर्गमूलक एल्गोरिथ्म को लिखने की घोषणा पीला के बाहर होने के रूप में की गई थी

मेटा थ्रेड पर काउमोगुन्ग की टिप्पणियों से संकेत मिलता है कि यह स्पष्ट संकलक अनुकूलन नहीं था, इसका हिस्सा हो सकता है, और -O0 , और रन-टाइम में 17% की वृद्धि उचित थी।

ऐसा लगता है कि असाइनमेंट का लक्ष्य छात्रों को निर्देश-स्तर की समानता या इस तरह की चीजों को कम करने के लिए मौजूदा काम को फिर से आदेश देना था, लेकिन यह बुरी बात नहीं है कि लोगों ने गहराई से सीखा है और अधिक सीखा है।

ध्यान रखें कि यह एक कंप्यूटर-आर्किटेक्चर प्रश्न है, सामान्य तौर पर C ++ को धीमा करने के तरीके के बारे में सवाल नहीं है।


देर से जवाब लेकिन मुझे नहीं लगता कि हमने लिंक की गई सूचियों और टीएलबी का पर्याप्त दुरुपयोग किया है।

अपने नोड्स को आवंटित करने के लिए mmap का उपयोग करें, जैसे कि आपके ज्यादातर पते के MSB का उपयोग करते हैं। इसका परिणाम लंबी TLB लुकअप श्रृंखलाओं में होना चाहिए, एक पृष्ठ 12 बिट्स है, जो अनुवाद के लिए 52 बिट्स को छोड़ रहा है, या लगभग 5 स्तरों पर इसे हर बार ट्रैवर्स करना होगा। थोड़ी सी किस्मत के साथ, उन्हें हर बार 5 स्तरों के लुकअप के लिए स्मृति में जाना चाहिए। 1 मेमोरी एक्सेस आपके नोड तक पहुंचने के लिए, शीर्ष स्तर सबसे अधिक संभावना कहीं कैश में होगा, इसलिए हम 5 * मेमोरी एक्सेस की उम्मीद कर सकते हैं। नोड को रखें ताकि यह सबसे खराब सीमा तय कर सके ताकि अगले पॉइंटर को पढ़ने से 3-4 अन्य अनुवाद लुकअप हो जाएं। ट्रांसलेशन लुकअप की भारी मात्रा के कारण यह कैशे को पूरी तरह से नष्ट कर सकता है। इसके अलावा वर्चुअल टेबल का आकार उपयोगकर्ता के अधिकांश डेटा को अतिरिक्त समय के लिए डिस्क पर पृष्ठांकित किया जा सकता है।

एकल लिंक की गई सूची से पढ़ते समय, हर बार सूची की शुरुआत से पढ़ना सुनिश्चित करें ताकि एकल संख्या को पढ़ने में अधिकतम देरी हो।


महत्वपूर्ण पृष्ठभूमि का पठन: एग्नर फॉग का माइक्रोआर्च पीडीएफ , और संभवत: उलरिच ड्रेपर की मेमोरी के बारे में हर प्रोग्रामर को पता होना चाहिए । x86 टैग विकी में अन्य लिंक भी देखें, विशेष रूप से इंटेल के अनुकूलन मैनुअल और डायग्राम के साथ हैवेल माइक्रोआर्किटेक्चर के डेविड कैंटर के विश्लेषण

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

असाइनमेंट शब्दांकन और कोड के साथ समस्याएं :

इस कोड के लिए विशिष्ट-विशिष्ट विकल्प सीमित हैं। यह किसी भी सरणियों का उपयोग नहीं करता है, और पुस्तकालय कार्यों को समाप्त / log करने के लिए बहुत अधिक लागत कॉल है। अधिक या कम निर्देश-स्तरीय समानता का स्पष्ट तरीका नहीं है, और लूप-आधारित निर्भरता श्रृंखला बहुत कम है।

मैं एक जवाब देखना पसंद करूंगा, जिसने निर्भरता को बदलने के लिए अभिव्यक्ति को फिर से व्यवस्थित करने से मंदी लाने का प्रयास किया, केवल निर्भरता (खतरों) से ILP को कम करने के लिए। मैंने इसका प्रयास नहीं किया है।

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

रजिस्टर के लिए WAR और WAW खतरे बहुत ज्यादा एक मुद्दा नहीं है, नाम बदलने के लिए धन्यवाद । ( popcnt / lzcnt / tzcnt को छोड़कर, जिनके पास Intel CPUs पर एक गलत निर्भरता है , भले ही वह केवल लेखन हो। अर्थात WAW को RAW के खतरे + एक लेखन के रूप में संभाला जा रहा है)। मेमोरी ऑर्डर करने के लिए, आधुनिक सीपीयू सेवानिवृत्ति तक कैश में देरी करने के लिए स्टोर कतारों का उपयोग करते हैं, साथ ही WAR और WAW खतरों से भी बचते हैं

एगनर के निर्देश तालिकाओं से अलग, हवेलवेल पर केवल 3 चक्र क्यों लगते हैं? एफपी डॉट उत्पाद लूप में एफएमए विलंबता का नाम बदलने और छिपाने के बारे में अधिक है।

"आई 7" ब्रांड-नाम नेहेलम ( कोर 2 के उत्तराधिकारी) के साथ पेश किया गया था , और कुछ इंटेल मैनुअल भी "कोर आई 7" कहते हैं, जब वे नेहलम का मतलब समझते हैं, लेकिन उन्होंने सैंडब्रिज और बाद में माइक्रोआर्किटेक्चर्स के लिए "i7" ब्रांडिंग रखी। SnB है जब P6- परिवार एक नई प्रजाति, SnB- परिवार में विकसित हुआ । कई मायनों में, नेहेल के पास सैंडियम के साथ पेंटियम III की तुलना में अधिक आम है (उदाहरण के लिए रजिस्टर स्टॉल और आरओबी-रीड स्टॉल एसएनबी पर नहीं होते हैं, क्योंकि यह एक भौतिक रजिस्टर फ़ाइल का उपयोग करने के लिए बदल गया है। एक यूओपी कैश और एक अलग आंतरिक कोड भी है। यूओपी प्रारूप)। "I7 आर्किटेक्चर" शब्द उपयोगी नहीं है , क्योंकि यह SnB- परिवार को Nehalem के साथ समूहित करने के लिए बहुत कम मायने रखता है, लेकिन Core2 नहीं। (नेहेलम ने कई कोर को एक साथ जोड़ने के लिए साझा समावेशी L3 कैश आर्किटेक्चर को पेश किया, हालांकि। और एकीकृत GPU भी। तो चिप-स्तर, नामकरण अधिक समझ में आता है।)

अच्छे विचारों का सारांश जो शैतानी अक्षमता को सही ठहरा सकते हैं

यहां तक ​​कि शैतानी अक्षमता भी स्पष्ट रूप से बेकार काम या अनंत लूप को जोड़ने की संभावना नहीं है, और सी ++ / बूस्ट कक्षाओं के साथ गड़बड़ करना असाइनमेंट के दायरे से परे है।

  • एक एकल साझा std::atomic<uint64_t> लूप काउंटर के साथ बहु-धागा, इसलिए सही पुनरावृत्तियों की कुल संख्या होती है। परमाणु uint64_t विशेष रूप से खराब है -m32 -march=i586 । बोनस बिंदुओं के लिए, इसे गलत बताए जाने की व्यवस्था करें, और असमान विभाजन के साथ पृष्ठ सीमा पार करें (4: 4 नहीं)।
  • कुछ अन्य गैर-परमाणु चर के लिए गलत साझाकरण -> मेमोरी-ऑर्डर मिस-स्पेकुलेशन पाइपलाइन को साफ करता है, साथ ही अतिरिक्त कैश मिस भी करता है।
  • उपयोग करने के बजाय - एफपी चर पर, XOR उच्च बाइट 0x80 के साथ साइन बिट को फ्लिप करने के लिए, स्टोर-फ़ॉरवर्डिंग स्टालों के कारण।
  • स्वतंत्र रूप से प्रत्येक पुनरावृत्ति का समय, RDTSC से भी भारी कुछ के साथ। उदाहरण के लिए CPUID / RDTSC या एक समय फ़ंक्शन जो एक सिस्टम कॉल करता है। सीरियलाइज़िंग निर्देश स्वाभाविक रूप से पाइपलाइन-अनफ्रेंडली हैं।
  • स्थिरांक द्वारा गुणकों को उनके पारस्परिक ("पढ़ने में आसानी के लिए") से विभाजित करें। div धीमा है और पूरी तरह से पाइपलाइन नहीं है।
  • AVX (SIMD) के साथ गुणा / sqrt को vzeroupper करें, लेकिन स्केलर गणित-पुस्तकालय exp() और log() फ़ंक्शंस के लिए कॉल करने से पहले vzeroupper का उपयोग करने में विफल, जिससे AVX <-> SSE संक्रमण स्टाल
  • किसी लिंक की गई सूची में, या सरणियों में RNG आउटपुट को स्टोर करें जिसे आप ऑर्डर से बाहर निकालते हैं। प्रत्येक पुनरावृत्ति के परिणाम के लिए समान, और अंत में योग।

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

बहु-धागा बुरी तरह से

शायद बहुत ही पुनरावृत्तियों के साथ मल्टी-थ्रेड लूप्स के लिए ओपनएमपी का उपयोग करें, जिस तरह से गति से अधिक ओवरहेड होता है। आपके मोंटे-कार्लो कोड में वास्तव में स्पीडअप प्राप्त करने के लिए पर्याप्त समानता है, हालांकि, एस्प। यदि हम प्रत्येक पुनरावृत्ति को धीमा बनाने में सफल होते हैं। (प्रत्येक थ्रेड अंत में जोड़े गए एक आंशिक payoff_sum गणना करता है)। #omp parallel लूप उस लूप पर #omp parallel होगा जो शायद एक अनुकूलन होगा, निराशावादी नहीं।

बहु-धागा लेकिन दोनों थ्रेड्स को समान लूप काउंटर ( atomic वृद्धि के साथ ताकि पुनरावृत्तियों की कुल संख्या सही हो) को साझा करने के लिए मजबूर करें। यह शैतानी तार्किक लगता है। इसका अर्थ है लूप काउंटर के रूप में एक static चर का उपयोग करना। यह लूप काउंटर के लिए atomic उपयोग को सही ठहराता है, और वास्तविक कैश-लाइन पिंग-पॉन्गिंग बनाता है (जब तक कि थ्रेड हाइपरथ्रेडिंग के साथ एक ही भौतिक कोर पर नहीं चलते हैं; वह उतना धीमा नहीं हो सकता है)। वैसे भी, यह lock inc लिए अन-कंटेस्टेड केस की तुलना में बहुत धीमा है। और lock cmpxchg8b को 32 बिट सिस्टम पर एक lock cmpxchg8b uint64_t पर परमाणु वृद्धि के लिए lock cmpxchg8b , हार्डवेयर को परमाणु inc मध्यस्थ करने के बजाय लूप में फिर से प्रयास करना होगा।

झूठी साझाकरण भी बनाएं, जहां कई धागे एक ही कैश लाइन के अलग-अलग बाइट्स में अपने निजी डेटा (जैसे आरएनजी राज्य) को रखते हैं। (इसके बारे में इंटेल ट्यूटोरियल, देखने के लिए परफेक्ट काउंटर सहित) इसके लिए एक माइक्रोआर्किटेक्चर-विशिष्ट पहलू है : इंटेल सीपीयू मेमोरी मिस-ऑर्डरिंग नहीं होने का अनुमान लगाता है, और कम से कम पी 4 पर यह पता लगाने के लिए एक मेमोरी-ऑर्डर मशीन-स्पष्ट संपूर्ण घटना है । हसवेल पर जुर्माना उतना बड़ा नहीं हो सकता है। जैसा कि लिंक बताता है, एक lock एड निर्देश मानता है कि यह गलत अनुमानों से बचने के लिए होगा। एक सामान्य लोड अनुमान लगाता है कि जब लोड निष्पादित होता है और जब वह प्रोग्राम-ऑर्डर में रिटायर होता है ( जब तक आप pause उपयोग नहीं करते हैं ) के बीच अन्य कोर कैश लाइन को अमान्य नहीं करेंगे। lock एड निर्देशों के बिना सच साझा करना आमतौर पर एक बग है। परमाणु मामले के साथ गैर-परमाणु साझा लूप काउंटर की तुलना करना दिलचस्प होगा। वास्तव में pessimize करने के लिए, साझा परमाणु लूप काउंटर को रखें, और किसी अन्य चर के लिए समान या अलग कैश लाइन में गलत साझाकरण का कारण बनें।

रैंडम यार्क-विशिष्ट विचार:

यदि आप किसी भी अप्रत्याशित शाखाओं को पेश कर सकते हैं , तो यह कोड को काफी हद तक कम कर देगा। आधुनिक x86 सीपीयू में काफी लंबी पाइपलाइनें होती हैं, इसलिए एक गलत लागत की लागत ~ 15 चक्र (यूओपी कैश से चलने पर) होती है।

निर्भरता श्रृंखला:

मुझे लगता है कि यह असाइनमेंट के इच्छित भागों में से एक था।

सीपीयू की क्षमता को कई छोटी निर्भरता श्रृंखलाओं के बजाय एक लंबी निर्भरता श्रृंखला के संचालन के एक आदेश को चुनकर अनुदेश-स्तरीय समानता का दोहन करने की क्षमता को हराएं। -ffast-math को FP गणनाओं के संचालन के क्रम को बदलने की अनुमति नहीं दी जाती है जब तक कि आप -ffast-math उपयोग नहीं करते हैं, क्योंकि इससे परिणाम बदल सकते हैं (जैसा कि नीचे चर्चा की गई है)।

वास्तव में इसे प्रभावी बनाने के लिए, लूप-आधारित निर्भरता श्रृंखला की लंबाई बढ़ाएं। कुछ भी स्पष्ट नहीं है, यद्यपि: लिखे गए लूप में बहुत कम लूप-निर्भर निर्भरता श्रृंखला होती है: बस एक एफपी ऐड। (3 चक्र)। एकाधिक पुनरावृत्तियों में एक बार में उनकी गणना हो सकती है, क्योंकि वे पिछले पुनरावृत्ति के अंत में payoff_sum += से पहले अच्छी शुरुआत कर सकते हैं। ( log() और exp कई निर्देश लेते हैं, लेकिन समानता खोजने के लिए हसवेल के आउट-ऑफ-ऑर्डर विंडो से बहुत अधिक नहीं है : आरओबी आकार = 192 फ्यूज्ड-डोमेन यूओपी, और शेड्यूलर आकार = 60 अप्रयुक्त-डोमेन यूपीएस । जैसे ही निष्पादन। वर्तमान पुनरावृत्ति, अगले पुनरावृत्ति से निर्देश के लिए जगह बनाने के लिए पर्याप्त रूप से आगे बढ़ती है, इसके किसी भी हिस्से में जो उनके इनपुट तैयार हैं (यानी स्वतंत्र / अलग डिप चेन) जब पुराने निर्देश निष्पादन इकाइयों को छोड़ देते हैं तो निष्पादन शुरू कर सकते हैं (जैसे क्योंकि वे विलंबता पर अड़चन हैं, थ्रूपुट नहीं।)

RNG राज्य लगभग निश्चित रूप से addps तुलना में लंबे समय तक लूप-आधारित निर्भरता श्रृंखला होगा।

धीमी / अधिक एफपी संचालन (esp। अधिक डिवीजन) का उपयोग करें:

0.5 से गुणा करने के बजाय 2.0 से विभाजित करें, और इसी तरह। एफपी मल्टी इंटेल के डिजाइनों में बड़े पैमाने पर पाइपलाइज्ड है, और हसवेल और बाद में 0.5 सीसी थ्रूपुट में से एक है। FP divsd / divpd केवल आंशिक रूप से divpd है । (हालांकि स्काईलेक के पास एक शानदार 4c थ्रूपुट है, जो कि divpd xmm , 13-14c लेटेंसी के साथ, बनाम नेहेलम (7-22c) पर बिल्कुल भी पाइपलाइज्ड नहीं)।

do { ...; euclid_sq = x*x + y*y; } while (euclid_sq >= 1.0); do { ...; euclid_sq = x*x + y*y; } while (euclid_sq >= 1.0); दूरी के लिए स्पष्ट रूप से परीक्षण कर रहा है, इसलिए स्पष्ट रूप से यह sqrt() लिए उचित होगा। : P ( sqrt div से भी धीमा है)।

जैसा कि @Paul क्लेटन सुझाव देते हैं, साहचर्य / वितरण समकक्षों के साथ पुनर्लेखन अभिव्यक्तियाँ अधिक काम का परिचय दे सकती हैं (जब तक आप कंपाइलर को पुन: अनुकूलन करने की अनुमति देने के लिए -ffast-math का उपयोग नहीं करते हैं)। (exp(T*(r-0.5*v*v)) exp(T*r - T*v*v/2.0) हो सकता है exp(T*r - T*v*v/2.0) । ध्यान दें कि जबकि वास्तविक संख्याओं पर गणित सहयोगी है, फ्लोटिंग पॉइंट गणित नहीं है , यहां तक ​​कि बिना अतिप्रवाह / NaN (यही कारण है कि -ffast-math डिफ़ॉल्ट रूप से चालू नहीं है) पर विचार करें। एक बहुत बालों वाले नेस्टेड pow() सुझाव के लिए पॉल की टिप्पणी देखें।

यदि आप गणनाओं को बहुत कम संख्याओं तक सीमित कर सकते हैं, तो दो सामान्य संख्याओं पर एक ऑपरेशन असामान्य होने पर एफपी गणित ऑप्स को माइक्रोकोड में फंसाने के लिए ~ 120 अतिरिक्त चक्र लेती है। सटीक संख्याओं और विवरणों के लिए Agner Fog का माइक्रोएर पीडीएफ देखें। यह बहुत अधिक होने के कारण संभावना नहीं है, इसलिए स्केल फैक्टर को चुकता किया जाएगा और 0.0 के लिए सभी तरह से कम किया जाएगा। मैं अक्षमता (यहां तक ​​कि शैतानी) के साथ आवश्यक स्केलिंग को सही ठहराने का कोई तरीका नहीं देखता, केवल जानबूझकर दुर्भावना।

यदि आप आंतरिक ( <immintrin.h> ) का उपयोग कर सकते हैं

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

बायपास देरी का कारण एफपी गणित संचालन के बीच पूर्णांक फेरबदल का उपयोग करें।

vzeroupper उचित उपयोग के बिना SSE और AVX निर्देशों का मिश्रण पूर्व-स्काईलेक (और vzeroupper में एक अलग दंड) में बड़े स्टालों का कारण बनता है । इसके बिना भी, वेक्टरिंग बुरी तरह से स्केलर से भी बदतर हो सकती है (एक साथ 256 मोंटे-कार्लो पुनरावृत्तियों के लिए जोड़ / उप / mul / div / sqrt संचालन करने से बचाए गए डेटा की तुलना में वैक्टर में / बाहर चक्कर लगाते हुए अधिक चक्र, 256b वैक्टर के साथ) । add / sub / mul निष्पादन इकाइयाँ पूरी तरह से पाइपलाइज़ और पूर्ण-चौड़ाई वाली हैं, लेकिन 256b वैक्टर पर div और sqrt 128b वैक्टर (या स्केलर) पर उतने तेज़ नहीं हैं, इसलिए स्पीडअप double लिए नाटकीय नहीं है।

exp() और log() में हार्डवेयर समर्थन नहीं है, इसलिए उस हिस्से को वेक्टर तत्वों को स्केलर पर वापस लाने और लाइब्रेरी फ़ंक्शन को अलग से कॉल करने की आवश्यकता होगी, फिर परिणामों को एक वेक्टर में फेरबदल करना होगा। libm को आम तौर पर केवल SSE2 का उपयोग करने के लिए संकलित किया जाता है, इसलिए स्केलर गणित निर्देशों के विरासत-SSE एन्कोडिंग का उपयोग करेगा। यदि आपका कोड 256b वैक्टर का उपयोग करता है और कॉल बिना vzeroupper पहले करता है, तो आप स्टाल करते हैं। लौटने के बाद, एवीएक्स -128 निर्देश जैसे कि अगले वेक्टर तत्व को vmovsd लिए सेट करने के लिए vmovsd जैसे निर्देश भी स्टाल करेंगे। और फिर exp() एसएसई इंस्ट्रक्शन चलाने पर फिर से स्टाल करेगा। इस सवाल में ठीक ऐसा ही हुआ, जिससे 10 गुना मंदी आ गई। (साभार @ZBoson)।

इस कोड के लिए इंटेल के गणित के साथ नाथन कुर्ज़ के प्रयोगों को भी देखें। भविष्य की glibc exp() और इसी तरह के वेक्टर कार्यान्वयन के साथ आएगी

यदि पूर्व-IvB, या esp को लक्षित किया गया है। नेहलेम, 16bit या 8bit संचालन के साथ आंशिक रूप से पंजीकृत स्टालों का कारण बनने के लिए gcc प्राप्त करने का प्रयास करें और उसके बाद 32bit या 64bit संचालन करें। ज्यादातर मामलों में, gcc 8 या 16bit ऑपरेशन के बाद movzx का उपयोग करेगा, लेकिन यहाँ एक मामला है जहाँ gcc ah को संशोधित करता है और फिर ax पढ़ता है

(इनलाइन के साथ) asm:

इनलाइन (इनलाइन) एसम के साथ, आप यूओपी कैश को तोड़ सकते हैं: 32B कोड ऑफ कोड जो तीन 6uop कैश लाइनों में फिट नहीं होता है, यूओपी कैश से डिकोडर्स पर स्विच करने के लिए बाध्य करता है। आंतरिक लूप के अंदर शाखा लक्ष्य पर एक जोड़े लंबे nop बजाय कई सिंगल-बाइट nop एस का उपयोग कर एक अक्षम डिजाइन ट्रिक कर सकता है। या पहले के बजाय लेबल के बाद संरेखण पैडिंग डालें। : P यह केवल तभी मायने रखता है जब फ्रंटएंड एक अड़चन है, जो कि ऐसा नहीं होगा यदि हम बाकी कोड को रोकने में सफल रहे।

पाइपलाइन क्लीयर (उर्फ मशीन-नुक्स) को ट्रिगर करने के लिए स्व-संशोधित कोड का उपयोग करें।

8 बिट्स में फिट होने के लिए तुरंत बड़े के साथ 16bit निर्देश से LCP स्टालों उपयोगी होने की संभावना नहीं है। SnB पर uop cache और बाद में इसका मतलब है कि आप केवल एक बार decode पेनल्टी का भुगतान करते हैं। Nehalem (पहला i7) पर, यह एक लूप के लिए काम कर सकता है जो 28 uop लूप बफर में फिट नहीं होता है। gcc कभी-कभी ऐसे निर्देश उत्पन्न करता है, यहां तक ​​कि -mtune=intel साथ भी और जब वह 32bit निर्देश का उपयोग कर सकता था।

समय के लिए एक सामान्य मुहावरा CPUID (क्रमबद्ध करने के लिए) फिर RDTSC । यह सुनिश्चित करने के लिए कि RDTSC को पहले के निर्देशों के साथ फिर से RDTSC नहीं किया गया है, यह सुनिश्चित करने के लिए CPUID / RDTSC साथ अलग से हर पुनरावृत्ति का समय है, जो चीजों को बहुत धीमा कर देगा। (वास्तविक जीवन में, समय का स्मार्ट तरीका यह है कि सभी पुनरावृत्तियों को एक साथ जोड़ा जाए, बजाय एक-दूसरे को अलग-अलग समय देने और उन्हें जोड़ने के)।

बहुत सारी कैश मिस और अन्य मेमोरी स्लोडाउन का कारण

एक union { double d; char a[8]; } उपयोग करें union { double d; char a[8]; } union { double d; char a[8]; } union { double d; char a[8]; } आपके कुछ चरों के लिए। केवल एक बाइट्स के लिए एक संकीर्ण दुकान (या पढ़ें-संशोधित-लिखें) करके स्टोर-फ़ॉरवर्डिंग स्टाल का कारण बनें। (उस विकी लेख में लोड / स्टोर कतारों के लिए बहुत से अन्य सूक्ष्म-गुणात्मक सामान शामिल हैं)। उदाहरण के लिए - एक ऑपरेटर के बजाय सिर्फ उच्च बाइट पर XOR 0x80 का उपयोग करके एक double के संकेत को फ्लिप करें । शैतानी अक्षम डेवलपर ने सुना हो सकता है कि FP पूर्णांक की तुलना में धीमा है, और इस प्रकार पूर्णांक ऑप्स का उपयोग करके अधिक से अधिक करने की कोशिश करते हैं। (SSE रजिस्टरों में FP गणित को लक्षित करने वाला एक बहुत अच्छा संकलक संभवतः इसे दूसरे xmm रजिस्टर में स्थिरांक के साथ xorps संकलित कर सकता है, लेकिन x87 के लिए यह एकमात्र तरीका भयानक नहीं है यदि संकलक को पता चलता है कि यह मान को नकार रहा है और प्रतिस्थापित करता है अगले जोड़ के साथ एक घटाव।)

volatile उपयोग करें यदि आप -O3 संकलन कर रहे हैं और std::atomic का उपयोग नहीं कर रहे हैं, तो संकलक को वास्तव में स्टोर करने / पुनः लोड करने के लिए बाध्य करने के लिए बाध्य करें। ग्लोबल वैरिएबल (स्थानीय लोगों के बजाय) कुछ स्टोर्स / रीलोड्स को भी बाध्य करेंगे, लेकिन C ++ मेमोरी मॉडल के कमजोर ऑर्डर को कंपाइलर को हर समय मेमोरी में लोड / रीलोड करने की आवश्यकता नहीं होती है।

एक बड़ी संरचना के सदस्यों के साथ स्थानीय संस्करण बदलें, ताकि आप मेमोरी लेआउट को नियंत्रित कर सकें।

पैडिंग के लिए संरचना में सरणियों का उपयोग करें (और उनके अस्तित्व को सही ठहराने के लिए यादृच्छिक संख्या को संग्रहीत करना)।

अपना मेमोरी लेआउट चुनें ताकि L1 कैश में एक ही "सेट" में सब कुछ एक अलग लाइन में चला जाए। यह केवल 8-तरफा साहचर्य है, अर्थात प्रत्येक सेट में 8 "तरीके" हैं। कैश लाइनें 64B हैं।

इससे भी बेहतर, चीजों को बिल्कुल 4096B अलग रखें, क्योंकि भार विभिन्न स्टोरों पर झूठी निर्भरता रखता है, लेकिन एक पृष्ठ के भीतर समान ऑफसेट के साथ । आक्रामक आउट-ऑफ-ऑर्डर सीपीयू मेमोरी डिसैम्बिगेशन का उपयोग यह पता लगाने के लिए करते हैं कि लोड और स्टोर को परिणामों को बदलने के बिना फिर से व्यवस्थित किया जा सकता है , और इंटेल के कार्यान्वयन में गलत-सकारात्मक हैं जो लोड को जल्दी शुरू होने से रोकते हैं। संभवतः वे केवल पृष्ठ ऑफसेट के नीचे बिट्स की जांच करते हैं, इसलिए टीएलबी द्वारा आभासी पृष्ठ से भौतिक पृष्ठ पर उच्च बिट्स का अनुवाद करने से पहले चेक शुरू हो सकता है। एग्नेर गाइड के साथ-साथ स्टीफन कैनन का उत्तर भी देखें, और इसी सवाल पर @Krazy Glew के उत्तर के अंत के पास एक अनुभाग भी देखें। (एंडी ग्लीव इंटेल के मूल पी 6 माइक्रोआर्किटेक्चर के आर्किटेक्ट में से एक थे।)

आपको __attribute__((packed)) का उपयोग करें ताकि वे आपको गलत-संरेखित चर दे सकें ताकि वे कैश-लाइन या यहां तक ​​कि पृष्ठ सीमाओं को भी __attribute__((packed)) सकें। (इसलिए दो कैश-लाइन्स से डेटा के एक double जरूरत है)। कैश लाइनों और पृष्ठ रेखाओं को पार करते समय, किसी भी Intel i7 uarch में गलत लोड का कोई दंड नहीं है। कैश-लाइन स्प्लिट अभी भी अतिरिक्त चक्र लेते हैं । स्काइलेक नाटकीय रूप से पृष्ठ विभाजन भार के लिए दंड को 100 से 5 चक्र तक कम कर देता है। (धारा 2.1.3) । शायद समानांतर में दो पृष्ठ चलने में सक्षम होने से संबंधित है।

atomic<uint64_t> पर एक पृष्ठ-विभाजन केवल सबसे खराब स्थिति , esp के बारे में होना चाहिए । यदि यह एक पृष्ठ में 5 बाइट्स और दूसरे पृष्ठ में 3 बाइट्स, या 4: 4 के अलावा कुछ भी है। यहां तक ​​कि बीच में विभाजन भी कैश-लाइन स्प्लिट्स के लिए कुछ यूरेश, IIRC पर 16B वैक्टर के साथ अधिक कुशल हैं। alignas(4096) struct __attribute((packed)) परिणामों के लिए भंडारण के लिए एक सरणी सहित, सब कुछ एक alignas(4096) struct __attribute((packed)) (अंतरिक्ष को बचाने के लिए) में रखें। काउंटर से पहले कुछ के लिए uint8_t या uint16_t का उपयोग करके uint16_t प्राप्त करें।

यदि आप संकलित पते मोड का उपयोग करने के लिए कंपाइलर प्राप्त कर सकते हैं, तो यह यूओपी माइक्रो-फ्यूजन को हरा देगा । हो सकता है कि my_data[constant] साथ साधारण स्केलर चर को बदलने के लिए #define का उपयोग कर रहा हो।

यदि आप एक अतिरिक्त स्तर का अप्रत्यक्ष परिचय दे सकते हैं, तो लोड / स्टोर पते जल्दी ज्ञात नहीं हैं, जो आगे चलकर कम कर सकते हैं।

गैर-सन्निहित आदेश में अनुप्रस्थ सरणियाँ

मुझे लगता है कि हम पहली जगह में एक सरणी शुरू करने के लिए अक्षम औचित्य के साथ आ सकते हैं: यह हमें यादृच्छिक संख्या के उपयोग को यादृच्छिक संख्या के उपयोग से अलग करता है। प्रत्येक पुनरावृत्ति के परिणाम को एक सरणी में संग्रहीत किया जा सकता है, बाद में अभिव्यक्त किया जा सकता है (अधिक शैतानी अक्षमता के साथ)।

"अधिकतम यादृच्छिकता" के लिए, हम यादृच्छिक सरणी को उस पर नए यादृच्छिक संख्या लिखने पर थ्रेड लूपिंग कर सकते हैं। यादृच्छिक संख्या का उपभोग करने वाला धागा एक यादृच्छिक संख्या को लोड करने के लिए एक यादृच्छिक सूचकांक उत्पन्न कर सकता है। (यहां कुछ मेक-वर्क है, लेकिन माइक्रोऑरेक्टेक्टुरली यह लोड-एड्रेस को जल्दी ज्ञात करने में मदद करता है ताकि लोड किए गए डेटा की आवश्यकता होने से पहले किसी भी संभावित लोड विलंबता को हल किया जा सके।) अलग-अलग कोर पर एक रीडर और लेखक होने से मेमोरी-ऑर्डर गलत हो जाएगा। -पीसुलेशन पाइपलाइन को साफ करता है (जैसा कि पहले झूठे बंटवारे के मामले के लिए चर्चा की गई थी)।

अधिकतम निराशावाद के लिए, 4096 बाइट्स (यानी 512 डबल्स) के स्ट्राइड के साथ अपने एरे पर लूप करें। जैसे

for (int i=0 ; i<512; i++)
    for (int j=i ; j<UPPER_BOUND ; j+=512)
        monte_carlo_step(rng_array[j]);

इसलिए एक्सेस पैटर्न 0, 4096, 8192, ...,
8, 4104, 8200, ...
16, 4112, 8208, ...

यह वह है जो आपको एक double rng_array[MAX_ROWS][512] सरणी के लिए मिलेगा जैसे कि double rng_array[MAX_ROWS][512] गलत क्रम में (पंक्तियों पर लूप करना, आंतरिक लूप में एक पंक्ति के भीतर कॉलम के बजाय, @JesperJahhl द्वारा सुझाए गए अनुसार)। अगर शैतानी अक्षमता उस तरह के आयामों के साथ 2 डी सरणी को सही ठहरा सकती है, तो बगीचे की विविधता वास्तविक दुनिया की अक्षमता आसानी से गलत पहुंच पैटर्न के साथ लूपिंग को सही ठहराती है। यह वास्तविक जीवन में वास्तविक कोड में होता है।

यदि लूप बड़ा नहीं है, तो समान पृष्ठों को पुन: उपयोग करने के बजाय कई अलग-अलग पृष्ठों का उपयोग करने के लिए आवश्यक होने पर लूप सीमा को समायोजित करें। हार्डवेयर प्रीफ़ेचिंग पृष्ठों के दौरान (साथ ही साथ / बिल्कुल भी) काम नहीं करता है। प्रीफ़ेचर प्रत्येक पृष्ठ के भीतर एक आगे और एक पीछे की धारा को ट्रैक कर सकता है (जो कि यहां होता है), लेकिन यह केवल उस पर कार्य करेगा यदि मेमोरी बैंडविड्थ पहले से ही गैर-प्रीफैच के साथ संतृप्त नहीं है।

यह तब तक बहुत सारी TLB मिस भी उत्पन्न करेगा, जब तक कि पेज एक विशाल पृष्ठ में नहीं मिल जाते ( लिनक्स यह अवसरवादी रूप से गुमनाम (फ़ाइल-समर्थित नहीं) आवंटन के लिए होता है, जैसे mmap(MAP_ANONYMOUS) / new जो mmap(MAP_ANONYMOUS) उपयोग करते हैं

परिणामों की सूची संग्रहीत करने के लिए एक सरणी के बजाय, आप एक लिंक की गई सूची का उपयोग कर सकते हैं। तब प्रत्येक पुनरावृत्ति को एक पॉइंटर-चेज़िंग लोड (अगले लोड के लोड-पता के लिए एक रॉ निर्भरता खतरा) की आवश्यकता होगी। खराब एलोकेटर के साथ, आप कैश को हराकर, सूची नोड्स को मेमोरी में चारों ओर बिखेरने का प्रबंधन कर सकते हैं। एक शैतानी रूप से अक्षम आवंटनकर्ता के साथ, यह प्रत्येक नोड को अपने पेज की शुरुआत में रख सकता है। (उदाहरण के लिए सीधे पेज को तोड़ने या ऑब्जेक्ट आकार को सही तरीके से समर्थन free लिए, बिना mmap(MAP_ANONYMOUS) साथ आवंटित free

ये वास्तव में माइक्रोआर्किटेक्चर-विशिष्ट नहीं हैं, और इनका पाइपलाइन से कोई लेना-देना नहीं है (इनमें से अधिकांश गैर-पाइपलाइड सीपीयू पर एक मंदी भी होगी)।

कुछ हद तक विषय: संकलक को बदतर कोड उत्पन्न / अधिक काम करते हैं:

C + 11 std::atomic<int> और std::atomic<double> का उपयोग सबसे अधिक कोड के लिए करें। एमएफईएनसीई और lock एड निर्देश दूसरे थ्रेड से विवाद के बिना भी काफी धीमा हैं।

-m32 धीमी कोड बनाएगा, क्योंकि x87 कोड SSE2 कोड से भी बदतर होगा। स्टैक-आधारित 32 बिट कॉलिंग कन्वेंशन अधिक निर्देश लेता है, और यहां तक ​​कि एफपी को स्टैक पर एक्सपेक्ट्स exp() जैसे कार्यों के लिए भी गुजरता है। atomic<uint64_t>::operator++ on -m32 को lock cmpxchg8B लूप (i586) की आवश्यकता होती है । (ताकि लूप काउंटर के लिए उपयोग करें! [बुराई हंसी])।

-march=i386 भी pessimize (धन्यवाद @ जैस्पर) करेगा। fcom साथ FP तुलना 686 fcomi से धीमी है। पूर्व -586 परमाणु 64 बिट स्टोर प्रदान नहीं करता है, (अकेले एक cmpxchg चलो), इसलिए सभी 64 बिट atomic ऑप्स libgcc फ़ंक्शन कॉल के लिए संकलित करते हैं (जो कि वास्तव में i686 के लिए संकलित किया जाता है, बजाय वास्तव में लॉक का उपयोग करने के)। इसे अंतिम पैराग्राफ में गॉडबॉल्ट कंपाइलर एक्सप्लोरर लिंक पर आज़माएं।

sqrtl अतिरिक्त सटीक और अतिरिक्त सुस्ती के लिए long double / sqrtl / sqrtl उपयोग करें जहां आकार ( long double ) 10 या 16 (संरेखण के लिए पैडिंग के साथ) है। (IIRC, 64 बिट विंडोज, long double बराबर 8byte long double का उपयोग करता है। (वैसे भी, 10byte का लोड / स्टोर (80bit) FP ऑपरेंड्स 4/7 uops है, बनाम float या double केवल 1 Uop लेने के लिए fld m64/m32 लिए प्रत्येक) । X87 को long double हार के साथ ऑटो- -m64 -march=haswell -O3 भी gcc -m64 -march=haswell -O3 लिए -m64 -march=haswell -O3

यदि atomic<uint64_t> लूप काउंटर का उपयोग नहीं कर रहे हैं, तो लूप काउंटर सहित हर चीज के लिए long double उपयोग करें।

atomic<double> संकलित करता है, लेकिन += जैसे पढ़ना-संशोधित-लिखना इसके लिए समर्थित नहीं है (यहां तक ​​कि 64 बिट पर भी)। atomic<long double> को केवल परमाणु भार / भंडार के लिए एक लाइब्रेरी फ़ंक्शन को कॉल करना होगा। यह शायद वास्तव में अक्षम है, क्योंकि x86 ISA स्वाभाविक रूप से परमाणु 10byte भार / भंडार का समर्थन नहीं करता है , और जिस तरह से मैं लॉक किए बिना सोच सकता हूं ( cmpxchg16b ) को 64 बिट मोड की आवश्यकता होती है।

-O0 , अस्थायी vars को भागों को असाइन करके एक बड़ी अभिव्यक्ति को तोड़ने से अधिक स्टोर / रीलोड हो जाएगा। volatile या कुछ के बिना, यह अनुकूलन सेटिंग्स के साथ कोई फर्क नहीं पड़ेगा कि वास्तविक कोड का एक वास्तविक निर्माण उपयोग करेगा।

सी अलियासिंग नियम किसी भी चीज को उर्फ ​​करने की अनुमति देता है, इसलिए एक char* स्टोर के माध्यम से संकलक को बाइट-स्टोर से पहले / बाद में सब कुछ स्टोर करने / पुनः लोड करने के लिए मजबूर करता है, यहां तक ​​कि -O3 पर भी। (यह उदाहरण के लिए uint8_t की एक सरणी पर संचालित होने वाले ऑटो-वेक्टरिंग कोड के लिए एक समस्या है।)

कोशिश करें uint16_t लूप काउंटर्स की, 16bit पर ट्रंकेशन को बाध्य करने के लिए, संभवतया 16bit ऑपरेंड-साइज़ (संभावित स्टॉल) और / या अतिरिक्त movzx निर्देशों (सुरक्षित) का उपयोग करके। हस्ताक्षरित अतिप्रवाह अपरिभाषित व्यवहार है , इसलिए जब तक आप -fwrapv या कम -fno-strict-overflow कम -fno-strict-overflow उपयोग नहीं करते हैं, तब तक हस्ताक्षरित लूप काउंटरों को प्रत्येक पुनरावृत्ति को फिर से साइन-एक्सटेंड करने की आवश्यकता नहीं है , भले ही वह 64 बिट पॉइंट्स के लिए ऑफ़सेट के रूप में उपयोग किया जाए।

पूर्णांक से float और फिर से वापस करने के लिए बल रूपांतरण। और / या double <=> float रूपांतरण। निर्देशों में एक से अधिक विलंबता है, और स्केलर int-> फ्लोट ( cvtsi2ss ) बाकी एक्सएमएम रजिस्टर को शून्य नहीं करने के लिए बुरी तरह से डिज़ाइन किया गया है। (इस कारण से निर्भरताएं तोड़ने के लिए gcc एक अतिरिक्त pxor सम्मिलित करता है।)

बार-बार अपने CPU के संबंध को एक अलग CPU (@Egwor द्वारा सुझाया गया) पर सेट करें । शैतानी तर्क: आप नहीं चाहते कि एक कोर आपके धागे को लंबे समय तक चलाने से गर्म हो जाए, क्या आप? हो सकता है कि किसी अन्य कोर को स्वैप करने से वह कोर टर्बो एक उच्चतर घड़ी की गति में आ जाए। (वास्तव में: वे एक-दूसरे के इतने करीब हैं कि बहु-सॉकेट प्रणाली को छोड़कर यह अत्यधिक संभावना नहीं है)। अब बस ट्यूनिंग को गलत करें और इसे अक्सर करें। ओएस की बचत / थ्रेडिंग स्थिति को पुनर्स्थापित करने में बिताए गए समय के अलावा, नए कोर में ठंडे एल 2 / एल 1 कैश, यूओपी कैश और शाखा भविष्यवक्ता हैं।

बार-बार अनावश्यक सिस्टम कॉल का परिचय आपको धीमा कर सकता है, चाहे वे कुछ भी हों। हालाँकि कुछ महत्वपूर्ण लेकिन सरल समय जैसे gettimeofday को कर्नेल मोड में कोई संक्रमण नहीं होने के साथ उपयोगकर्ता-अंतरिक्ष में लागू किया जा सकता है। (लिनक्स पर glibc कर्नेल की सहायता से ऐसा करता है, क्योंकि कर्नेल vdso में कोड निर्यात करता है)।

सिस्टम कॉल ओवरहेड (कैश / टीएलबी सहित उपयोगकर्ता-अंतरिक्ष में लौटने के बाद छूट जाती है, केवल संदर्भ स्विच ही नहीं) पर अधिक जानकारी के लिए, फ्लेक्सएससी पेपर में वर्तमान स्थिति के कुछ महान पूर्ण-काउंटर विश्लेषण हैं, साथ ही बैचिंग सिस्टम के लिए एक प्रस्ताव है। बड़े पैमाने पर बहु-थ्रेडेड सर्वर प्रक्रियाओं से कॉल।








cpu-architecture