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


322

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

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

कार्यक्रम को अपनाने के लिए, अपने ज्ञान का उपयोग करें कि इंटेल 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 ++ को धीमा करने के तरीके के बारे में सवाल नहीं है।


97
मैंने सुना है कि i7 बहुत खराब तरीके से करता हैwhile(true){}
क्लिफ एबी

3
HN atm पर नंबर 2: news.ycombinator.com/item?id=11749756
mlvljr

5
OpenMP के साथ अगर आप इसे बुरी तरह से करते हैं आप एन धागे 1. से अधिक समय लग बनाने के लिए सक्षम होना चाहिए
Flexo

9
इस सवाल पर अब मेटा
मदरा का भूत

3
@bluefeet: मैंने इसे इसलिए जोड़ा क्योंकि यह पहले से ही एक बंद वोट को दोबारा खोलने के एक घंटे के भीतर आकर्षित कर चुका था। यह केवल मेटा पर चर्चा के तहत देखने के लिए पढ़ने की टिप्पणियों को साकार किए बिना 5 लोगों को साथ आने के लिए और वीटीसी लेता है। अब एक और करीबी वोट है। मुझे लगता है कि कम से कम एक वाक्य चक्रों को बंद / फिर से खोलने में मदद करेगा।
पीटर कॉर्डेस

जवाबों:


405

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

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


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

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

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

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

रजिस्टर के लिए WAR और WAW खतरे बहुत ज्यादा एक मुद्दा नहीं है, नाम बदलने के लिए धन्यवाद । (popcnt/lzcnt/को छोड़करtzcnt, जिनके पास Intel CPUs पर एक झूठी निर्भरता है , भले ही यह केवल लिखने योग्य हो। अर्थात WAW को रॉ के खतरे + एक लेखन के रूप में संभाला जा रहा है)। मेमोरी ऑर्डर करने के लिए, आधुनिक सीपीयू सेवानिवृत्ति तक कैश में देरी करने के लिए स्टोर कतारों का उपयोग करते हैं, साथ ही 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()फ़ंक्शंस पर कॉल करने से पहले उपयोग करने में विफल रहें , जिससे AVX <-> SSE ट्रांस्फ़ॉर्म स्टॉल
  • किसी लिंक की गई सूची में, या सरणियों में RNG आउटपुट को स्टोर करें जिसे आप ऑर्डर से बाहर निकालते हैं। प्रत्येक पुनरावृत्ति के परिणाम के लिए समान, और अंत में योग।

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


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

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

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

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


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

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


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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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


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


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

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

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

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


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


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

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


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

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

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

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

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

का प्रयोग करें __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))आरएनजी परिणामों के लिए भंडारण के लिए एक सरणी सहित, सब कुछ (अंतरिक्ष को बचाने के लिए) डालें। काउंटर से पहले uint8_tया uint16_tकिसी चीज का उपयोग करके मिसलिग्न्मेंट प्राप्त करें ।

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

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


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

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

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

अधिकतम निराशावाद के लिए, 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, 820%, ...

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

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

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

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


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

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

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

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

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

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

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

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


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

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

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


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


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

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

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


10
@JesperJuhl: हाँ, मैं उस औचित्य को खरीदूंगा। "शैतानी अक्षमता" इस तरह का एक अद्भुत वाक्यांश है :)
पीटर कॉर्डेस

2
निरंतर के व्युत्क्रम से विभाजन से गुणाओं को बदलना प्रदर्शन को मामूली रूप से कम कर सकता है (कम से कम यदि कोई व्यक्ति out -it -O3-Optmath की कोशिश नहीं कर रहा है)। इसी तरह काम को बढ़ाने के लिए समरूपता का उपयोग करना ( exp(T*(r-0.5*v*v))बनने exp(T*r - T*v*v/2.0); exp(sqrt(v*v*T)*gauss_bm)बनने exp(sqrt(v)*sqrt(v)*sqrt(T)*gauss_bm))। संबद्धता (और सामान्यीकरण) भी exp(T*r - T*v*v/2.0)`पाव ((पाव (ई_वेल्यू, टी), आर) / पॉव (पॉव (पॉव (ई_वेल्यू, टी), वी), वी), वी)), - 2.0) [या कुछ और में बदल सकता है उस तरह]]। इस तरह के गणित के गुर वास्तव में सूक्ष्मजैविकांतीय विकृति के रूप में नहीं गिने जाते हैं।
पॉल ए। क्लेटन

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

19
उन सुझावों में से कुछ इतनी शैतानी अक्षम हैं कि मुझे यह देखने के लिए प्रोफेसर से बात करनी होगी कि आउटपुट को सत्यापित करने के लिए बैठने के लिए अब 7 मिनट का समय बहुत ज्यादा है। अभी भी इस के साथ काम करना, यह शायद सबसे मजेदार था जो मैंने एक परियोजना के साथ किया है।
गायमोघुन

4
क्या? कोई म्यूटेक्स नहीं? दो मिलियन धागे एक साथ चलने वाले म्यूटेक्स की रक्षा करते हुए प्रत्येक और प्रत्येक व्यक्ति की संगणना (बस मामले में!) ग्रह पर सबसे तेज सुपर कंप्यूटर को अपने घुटनों पर लाएगा। उस ने कहा, मैं इस तिरस्कारपूर्ण उत्तर से प्यार करता हूं।
डेविड हैमेन

35

कुछ चीजें जो आप चीजों को यथासंभव खराब करने के लिए कर सकते हैं:

  • i386 आर्किटेक्चर के लिए कोड संकलित करें। यह SSE और नए निर्देशों के उपयोग को रोक देगा और x87 FPU के उपयोग को मजबूर करेगा।

  • std::atomicहर जगह चर का उपयोग करें । यह कंपाइलर के कारण सभी जगह मेमोरी बैरियर लगाने के लिए मजबूर होने के कारण यह बहुत महंगा हो जाएगा। और यह एक ऐसा अक्षम व्यक्ति है जो संभवतः "सुरक्षा को सुनिश्चित करने के लिए" कर सकता है।

  • भविष्यवाणी करने के लिए प्रीफ़ैचर के लिए सबसे खराब तरीके से मेमोरी तक पहुंच सुनिश्चित करें (कॉलम प्रमुख बनाम पंक्ति प्रमुख)।

  • अपने चरों को अतिरिक्त रूप से महंगा बनाने के लिए आप यह सुनिश्चित कर सकते हैं कि उनके पास 'डायनेमिक स्टोरेज अवधि' (हीप आबंटित) है, जो newउन्हें 'ऑटोमैटिक स्टोरेज पीरियड' (स्टैक आबंटित) की अनुमति देने के बजाय उन्हें आवंटित करके है ।

  • सुनिश्चित करें कि आपके द्वारा आवंटित की गई सभी मेमोरी बहुत ही अजीब तरह से संरेखित है और हर तरह से विशाल पृष्ठों को आवंटित करने से बचें, क्योंकि ऐसा करने से टीएलटी बहुत अधिक कुशल होगी।

  • आप जो भी करते हैं, अपने कोड को कंपाइलर ऑप्टिमाइज़र सक्षम के साथ नहीं बनाते हैं। और सबसे स्पष्ट डिबग प्रतीकों को सक्षम करने के लिए सुनिश्चित करें (कोड को धीमा नहीं कर सकते , लेकिन यह कुछ अतिरिक्त डिस्क स्थान को बर्बाद कर देगा)।

नोट: यह उत्तर मूल रूप से मेरी टिप्पणियों को संक्षेप में बताता है कि @Peter Cordes पहले से ही उनके बहुत अच्छे उत्तर में शामिल है। सुझाव है कि अगर आप केवल एक को छोड़ दें तो वह आपका उत्थान प्राप्त करेगा :)


9
इनमें से कुछ के लिए मेरी मुख्य आपत्ति इस सवाल का विवरण है: कार्यक्रम को अपनाने के लिए, अपने ज्ञान का उपयोग करें कि इंटेल i7 पाइपलाइन कैसे संचालित होती है मुझे नहीं लगता है कि x87 के बारे में कुछ भी विशिष्ट है, या std::atomic, गतिशील आवंटन से अप्रत्यक्ष स्तर का अतिरिक्त। वे एटम या K8 पर भी धीमी गति से चलने वाले हैं। फिर भी उत्थान हो रहा है, लेकिन इसीलिए मैं आपके कुछ सुझावों का विरोध कर रहा था।
पीटर कॉर्ड्स

वे उचित बिंदु हैं। बावजूद, वे चीजें अभी भी पूछने वाले के लक्ष्य की ओर कुछ हद तक काम करती हैं। अपवित्र की सराहना करें :)
जेस्पर जुहल

SSE इकाई पोर्ट 0, 1 और 5 का उपयोग करती है। x87 इकाई केवल पोर्ट 0 और 1. का उपयोग करती है
माइकस

@ मिचास: आप इसके बारे में गलत हैं। Haswell पोर्ट 5 पर कोई SSE FP गणित निर्देश नहीं चलाता है। ज्यादातर SSE FP शफ़ल और बूलियन (xorps / andps / orps)। x87 धीमा है, लेकिन आपका स्पष्टीकरण थोड़ा गलत क्यों है। (और यह बिंदु पूरी तरह से गलत है।)
पीटर कॉर्डेस

1
@ मिचास: movapd xmm, xmmआमतौर पर एक निष्पादन पोर्ट की आवश्यकता नहीं होती है (यह आईवीबी और बाद में रजिस्टर नाम बदलने के चरण में संभाला जाता है)। यह भी AVX कोड में लगभग कभी आवश्यक नहीं है, क्योंकि सब कुछ लेकिन FMA गैर-विनाशकारी है। लेकिन काफी हद तक, यदि इसे समाप्त नहीं किया गया, तो Haswell इसे port5 पर चलाता है। मैंने x87 रजिस्टर-कॉपी ( fld st(i)) पर नहीं देखा था , लेकिन आप हसवेल / ब्रॉडवेल के लिए सही हैं: यह p01 पर चलता है। Skylake इसे p05 पर चलाता है, SnB इसे p0 पर चलाता है, IvB इसे p5 पर चलाता है। तो IVB / SKL p5 पर कुछ x87 सामान (तुलना सहित) करते हैं, लेकिन SNB / HSW / BDW x87 के लिए p5 का उपयोग बिल्कुल नहीं करते हैं।
पीटर कॉर्ड्स

11

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

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

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

3
स्केलर गणित के लिए, x87 गणित निर्देश स्वयं ही थोड़े धीमे होते हैं। 10byte ऑपरेंड को स्टोर / लोड करना काफी धीमा है, हालांकि, और x87 के स्टैक-आधारित डिज़ाइन को अतिरिक्त निर्देशों (जैसे fxch) की आवश्यकता होती है । साथ -ffast-mathएक अच्छा संकलक Monte-Carlo छोरों, हालांकि vectorize सकता है, और x87 कि रोका जा सके।
पीटर कॉर्ड्स

मैंने अपना उत्तर थोड़ा बढ़ाया है।
मीकस

1
पुन :: 4: आप किस i7 uarch के बारे में बात कर रहे हैं, और कौन से निर्देश? Haswell mulssp01 पर चल सकता है , लेकिन fmulकेवल पर p0addssकेवल p1उसी तरह चलता है fadd। केवल दो निष्पादन पोर्ट हैं जो एफपी गणित ऑप्स को संभालते हैं। (इसका एकमात्र अपवाद यह है कि स्काईलेक ने समर्पित ऐड यूनिट को गिरा दिया और addssF01 इकाइयों में p01 पर, लेकिन पी 5 पर चलता है fadd। इसलिए कुछ faddनिर्देशों के साथ मिश्रण करके fma...ps, आप सिद्धांत रूप में थोड़ा और कुल FLOP / s कर सकते हैं।)
पीटर कॉर्ड्स

2
यह भी ध्यान दें कि Windows x86-64 ABI में 64 बिट है long double, अर्थात यह अभी भी है double। SysV ABI 80bit का उपयोग करता है long double, हालांकि। इसके अलावा, फिर से: 2: रजिस्टर नामकरण स्टैक रजिस्टरों में समानता को उजागर करता है। स्टैक-आधारित आर्किटेक्चर को कुछ अतिरिक्त निर्देशों की आवश्यकता होती है, जैसे fxchg, एस्प। जब समानांतर गणना interleaving। इसलिए यह अधिक पसंद है कि मेमोरी राउंड-ट्रिप के बिना समानता को व्यक्त करना कठिन है, बजाय इसके कि यह उर्क के लिए कठिन है कि वहां क्या है। हालांकि आपको अन्य रजिस्टरों से अधिक रूपांतरण की आवश्यकता नहीं है। मुझे नहीं मालूम वैसा कहने का आपका क्या आशय है।
पीटर कॉर्ड्स

6

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

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

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


x86-64 पेज टेबल 48-बिट वर्चुअल एड्रेस के लिए 4 लेवल गहरे हैं। (एक पीटीई के पास 52 भौतिक पते हैं)। भविष्य के सीपीयू एक 5-स्तरीय पेज टेबल सुविधा का समर्थन करेंगे, जो वर्चुअल एड्रेस स्पेस (57) के 9 बिट्स के लिए होगा। क्यों 64 बिट में वर्चुअल एड्रेस भौतिक पते (52 बिट लंबा) की तुलना में 4 बिट छोटा (48 बिट लंबा) होता है? । ओएस इसे डिफ़ॉल्ट रूप से सक्षम नहीं करेंगे क्योंकि यह धीमा होगा और तब तक कोई लाभ नहीं होगा जब तक आपको उस पुण्य पता स्थान की आवश्यकता न हो।
पीटर कॉर्ड्स

लेकिन हां, मजेदार विचार। आप mmapएक ही भौतिक पृष्ठ (एक ही सामग्री के साथ) के लिए कई वर्चुअल पते प्राप्त करने के लिए एक फ़ाइल या साझा-मेमोरी क्षेत्र पर उपयोग कर सकते हैं, जिससे अधिक टीएलबी को एक ही भौतिक रैम से अधिक याद आती है। यदि आपकी लिंक-लिस्ट nextसिर्फ एक रिश्तेदार ऑफसेट थी , तो आप एक ही पेज के मैपिंग की एक श्रृंखला रख सकते थे, +4096 * 1024जब तक कि आप अंततः एक अलग भौतिक पृष्ठ पर नहीं पहुंचते। या निश्चित रूप से L1d कैश हिट से बचने के लिए कई पृष्ठों पर फैले हुए हैं। पेज-वॉक हार्डवेयर के भीतर उच्च-स्तरीय PDEs का कैशिंग है, इसलिए हां इसे पुण्य addr अंतरिक्ष में फैलाएं!
पीटर कॉर्ड्स

पुराने पते में एक ऑफसेट जोड़ने से लोड-उपयोग विलंबता को [एक [reg+small_offset]एड्रेसिंग मोड के लिए विशेष मामला ] को हराकर बदतर बना देता है ( क्या आधार + ऑफसेट आधार की तुलना में किसी भिन्न पृष्ठ में है? ) आपको या तो add64-बिट ऑफ़सेट का मेमोरी-सोर्स मिलेगा , या आपको एक लोड और इंडेक्सिंग एड्रेसिंग मोड मिलेगा [reg+reg]। यह भी देखें कि L2 TLB मिस करने के बाद क्या होता है? - SnB- परिवार पर L1d कैश के माध्यम से पेज वॉक भ्रूण।
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.