गोलंग: विशिष्ट प्रदर्शन के मुद्दे

गो भाषा लोकप्रियता प्राप्त कर रही है। इतना विश्वास है कि अधिक से अधिक सम्मेलन हैं, उदाहरण के लिए, गोलंगकॉन्फ़ , और भाषा दस सबसे अधिक भुगतान की जाने वाली प्रौद्योगिकियों में से एक है। इसलिए, यह पहले से ही अपनी विशिष्ट समस्याओं के बारे में बात करने के लिए समझ में आता है, उदाहरण के लिए, प्रदर्शन। सभी संकलित भाषाओं के लिए सामान्य समस्याओं के अलावा, गो का अपना है। वे ऑप्टिमाइज़र, स्टैक, टाइप सिस्टम और मल्टीटास्किंग मॉडल से जुड़े हैं। उन्हें हल करने के तरीके और वर्कअराउंड कभी-कभी बहुत विशिष्ट होते हैं।

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



बोलने वालों के बारे में। डेनियल पोडॉल्स्की : 26 साल का अनुभव, ऑपरेशन में 20, समूह के नेता सहित, गो पर 5 साल की प्रोग्रामिंग। किरिल दंशिन : ग्रिस्टन, मेंटेनर, फास्ट HTTP, ब्लैक गो-माज के निर्माता।

रिपोर्ट को संयुक्त रूप से डैनियल पोडॉल्स्की और किरिल डानशिन द्वारा तैयार किया गया था, लेकिन डैनियल ने एक रिपोर्ट बनाई और किरिल ने मानसिक रूप से मदद की।

भाषा निर्माण


हमारे पास एक प्रदर्शन मानक है - direct । यह एक ऐसा फंक्शन है जो एक चर को बढ़ाता है और अब कुछ भी नहीं करता है।

 //   var testInt64 int64 func BenchmarkDirect(b *testing.B) { for i := 0; i < bN; i++ { incDirect() } } func incDirect() { testInt64++ } 

फ़ंक्शन का परिणाम प्रति ऑपरेशन 1.46 एनएस है । यह न्यूनतम विकल्प है। प्रति ऑपरेशन 1.5 एनएस से अधिक, शायद काम नहीं करेगा।

हम उसे कैसे प्यार करते हैं, यह देखें


कई जानते हैं और defer भाषा निर्माण का उपयोग करने के लिए प्यार करता हूँ। अक्सर हम इसे इस तरह से उपयोग करते हैं।

 func BenchmarkDefer(b *testing.B) { for i := 0; i < bN; i++ { incDefer() } } func incDefer() { defer incDirect() } 

लेकिन आप इसका उपयोग नहीं कर सकते! प्रत्येक डिफरेंश 40 एनपीएस प्रति ऑपरेशन खाता है।

 //   BenchmarkDirect-4 2000000000 1.46 / // defer BenchmarkDefer-4 30000000 40.70 / 

मैंने सोचा कि शायद यह इनलाइन की वजह से है? शायद इनलाइन इतनी तेज है?

डायरेक्ट इनलाइन है, और डेफर फ़ंक्शन इनलाइन नहीं कर सकता है। इसलिए, इनलाइन के बिना एक अलग परीक्षण फ़ंक्शन संकलित किया।

 func BenchmarkDirectNoInline(b *testing.B) { for i := 0; i < bN; i++ { incDirectNoInline() } } //go:noinline func incDirectNoInline() { testInt64++ } 

कुछ भी नहीं बदला है, defer वही 40 ns लिया। प्रिय प्रिय, पर प्रलय नहीं।

जहाँ एक फंक्शन 100 ns से कम होता है, आप बिना डिफर के कर सकते हैं।

लेकिन जहां फ़ंक्शन एक माइक्रोसेकंड से अधिक लेता है, यह सभी समान है - आप डेफर का उपयोग कर सकते हैं।

संदर्भ द्वारा एक पैरामीटर पास करना


एक लोकप्रिय मिथक पर विचार करें।

 func BenchmarkDirectByPointer(b *testing.B) { for i := 0; i < bN; i++ { incDirectByPointer(&testInt64) } } func incDirectByPointer(n *int64) { *n++ } 

कुछ भी नहीं बदला है - कुछ भी इसके लायक नहीं है।

 //     BenchmarkDirectByPointer-4 2000000000 1.47 / BenchmarkDeferByPointer-4 30000000 43.90 / 

3 ns प्रति डेफर को छोड़कर, लेकिन यह उतार-चढ़ाव के लिए बंद है।

अनाम कार्य


कभी-कभी newbies पूछते हैं, "क्या एक अनाम फ़ंक्शन महंगा है?"

 func BenchmarkDirectAnonymous(b *testing.B) { for i := 0; i < bN; i++ { func() { testInt64++ }() } } 

एक अनाम फ़ंक्शन महंगा नहीं है, इसमें 40.4 ns लगता है।

इंटरफेस


एक इंटरफ़ेस और संरचना है जो इसे लागू करता है।

 type testTypeInterface interface { Inc() } type testTypeStruct struct { n int64 } func (s *testTypeStruct) Inc() { s.n++ } 

वेतन वृद्धि विधि का उपयोग करने के लिए तीन विकल्प हैं। सीधे var testStruct = testTypeStruct{} से: var testStruct = testTypeStruct{}

इसी ठोस इंटरफ़ेस से: var testInterface testTypeInterface = &testStruct

रनटाइम इंटरफ़ेस रूपांतरण के साथ: var testInterfaceEmpty interface{} = &testStruct

नीचे रनटाइम इंटरफ़ेस रूपांतरण और सीधे उपयोग है।

 func BenchmarkInterface(b *testing.B) { for i := 0; i < bN; i++ { testInterface.Inc() } } func BenchmarkInterfaceRuntime(b *testing.B) { for i := 0; i < bN; i++ { testInterfaceEmpty.(testTypeInterface).Inc() } } 

इंटरफ़ेस, जैसे, लागत कुछ भी नहीं है।

 //  BenchmarkStruct-4 2000000000 1.44 / BenchmarkInterface-4 2000000000 1.88 / BenchmarkInterfaceRuntime-4 200000000 9.23 / 


रनटाइम इंटरफ़ेस रूपांतरण लागत, लेकिन महंगी नहीं - आपको विशेष रूप से मना करने की आवश्यकता नहीं है। लेकिन जहां संभव हो इसके बिना करने की कोशिश करें।

मिथकों:

  • Dereference - dereferencing पॉइंटर्स - निःशुल्क।
  • बेनामी सुविधाएँ मुफ्त हैं।
  • इंटरफेस स्वतंत्र हैं।
  • रनटाइम इंटरफ़ेस रूपांतरण - मुफ़्त नहीं।

स्विच, मैप और स्लाइस


जाने के लिए हर नवागंतुक पूछता है कि क्या होता है यदि आप नक्शे के साथ स्विच को बदलते हैं। क्या यह तेज होगा?

स्विच विभिन्न आकारों में आते हैं। मैंने तीन आकारों पर परीक्षण किया: 10 मामलों के लिए छोटा, 100 के लिए मध्यम और 1000 मामलों के लिए बड़ा। 1000 मामलों के लिए स्विच वास्तविक उत्पादन कोड में पाए जाते हैं। बेशक, कोई भी उन्हें अपने हाथों से नहीं लिखता है। यह स्वतः-जनरेट कोड है, आमतौर पर एक प्रकार का स्विच है। दो प्रकारों पर परीक्षण किया गया: इंट और स्ट्रिंग। ऐसा लग रहा था कि यह अधिक स्पष्ट रूप से निकल जाएगा।

थोड़ा स्विच। सबसे तेज़ विकल्प वास्तविक स्विच है। इसके तुरंत बाद यह टुकड़ा हो जाता है, जहां संबंधित पूर्णांक सूचकांक में फ़ंक्शन का संदर्भ होता है। नक्शा इंट या स्ट्रिंग पर एक नेता नहीं है।
BenchmarkSwitchIntSmall -45000000003.26 ns / op
BenchmarkMapIntSmall -410000000011.70 ns / op
BenchmarkSliceIntSmall -45000000003.85 ns / op है
BenchmarkSwitchStringSmall -410000000012.70 एनएस / से
BenchmarkMapStringSmall -410000000015.60 ns / op है

स्ट्रिंग्स पर स्विच इंट की तुलना में काफी धीमा है। यदि आप स्ट्रिंग को नहीं बल्कि इंट को स्विच कर सकते हैं, तो ऐसा करें।

मध्य स्विच। स्विच खुद भी अभी भी नियम बना रहा है, लेकिन स्लाइस इसे थोड़ा आगे निकल गया है। नक्शा अभी भी खराब है। लेकिन एक स्ट्रिंग कुंजी पर, नक्शा स्विच से तेज है - जैसा कि अपेक्षित था।
BenchmarkSwitchIntMedium -43000000004.55 ns / op है
BenchmarkMapIntMedium -410000000017.10 एनएस / से
BenchmarkSliceIntMedium -43000000003.76 ns / op
BenchmarkSwitchStringMedium -45000000028.50 एनएस / से
BenchmarkMapStringMedium -410000000020.30 एनएस / से

बड़ा स्विच है। एक हजार मामले "स्ट्रिंग द्वारा स्विच" नामांकन में नक्शे की बिना शर्त जीत दिखाते हैं। सैद्धांतिक रूप से, स्लाइस जीता, लेकिन व्यवहार में मैं आपको यहां एक ही स्विच का उपयोग करने की सलाह देता हूं। मानचित्र अभी भी धीमा है, यहां तक ​​कि यह विचार करते हुए कि नक्शे में एक विशेष हैश फ़ंक्शन के साथ पूर्णांक कुंजियाँ हैं। सामान्य तौर पर, यह फ़ंक्शन कुछ भी नहीं करता है। इंट के पास खुद के पास हैश है।
BenchmarkSwitchIntLarge -410000000013.6 ns / op है
BenchmarkMapIntLarge -45000000034.3 एनएस / से
BenchmarkSliceIntLarge -410000000012.8 एनएस / से
BenchmarkSwitchStringLarge -420000000100.0 एनएस / से
BenchmarkMapStringLarge -43000000037.4 एनएस / से

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

अंतर-दिनचर्या बातचीत


विषय जटिल है, मैंने कई परीक्षण किए हैं और सबसे अधिक खुलासा करेंगे। हम अंतर-संपर्क के निम्नलिखित साधनों को जानते हैं।

  • परमाणु। ये सीमित प्रयोज्यता के साधन हैं - आप सूचक को प्रतिस्थापित कर सकते हैं या int का उपयोग कर सकते हैं।
  • जावा के बाद से म्यूटेक्स का व्यापक रूप से उपयोग किया गया है।
  • चैनल जीओ के लिए अद्वितीय है।
  • बफ़र्ड चैनल - बफ़र्ड चैनल।

बेशक, मैंने गोरोइटिन की एक बड़ी संख्या पर परीक्षण किया जो एक संसाधन के लिए प्रतिस्पर्धा करते हैं। लेकिन उन्होंने अपने लिए तीन को संकेत के रूप में चुना: थोड़ा - 100, एक मध्यम - 1000 और बहुत - 10000।

लोड प्रोफ़ाइल अलग है । कभी-कभी सभी गोरोटिन एक चर में लिखना चाहते हैं, लेकिन यह दुर्लभ है। आमतौर पर, सब के बाद, कुछ लिखते हैं, कुछ पढ़ते हैं। ज्यादातर पाठकों में से - 90% पढ़ते हैं, जो लिखते हैं - 90% लिखते हैं।

यह वह कोड होता है जिसका उपयोग किया जाता है ताकि चैनल को परोसने वाला गोरूटिन पढ़ने और लिखने दोनों को एक चर में प्रदान कर सके।

 go func() { for { select { case n, ok := <-cw: if !ok { wgc.Done() return } testInt64 += n case cr <- testInt64: } } }() 

यदि चैनल के माध्यम से कोई संदेश हमारे पास आता है, जिसके माध्यम से हम लिखते हैं, हम इसे निष्पादित करते हैं। यदि चैनल बंद है, तो हम गोरोइन खत्म करते हैं। किसी भी समय, हम उस चैनल को लिखने के लिए तैयार हैं जो पढ़ने के लिए अन्य गोरआउट्स द्वारा उपयोग किया जाता है।
BenchmarkMutex -410000000016.30 एनएस / से
BenchmarkAtomic -42000000006.72 ns / op
BenchmarkChan -45000000239.00 एनएस / से

यह एक गोरोइन के लिए डेटा है। चैनल परीक्षण दो गोरोइंटिन पर किया जाता है: एक चैनल को संसाधित करता है, दूसरा इस चैनल को लिखता है। और इन विकल्पों को एक पर परीक्षण किया गया है।

  • डायरेक्ट एक वैरिएबल को लिखता है।
  • म्यूटेक्स एक लॉग लेता है, एक चर को लिखता है और एक लॉग जारी करता है।
  • परमाणु परमाणु के माध्यम से एक चर को लिखता है। यह मुक्त नहीं है, लेकिन अभी भी एक गूटिन पर म्यूटेक्स की तुलना में काफी सस्ता है।

थोड़ी मात्रा में गोरोइन के साथ, परमाणु सिंक्रनाइज़ करने के लिए एक प्रभावी और तेज़ तरीका है, जो आश्चर्य की बात नहीं है। प्रत्यक्ष यहां नहीं है, क्योंकि हमें सिंक्रनाइज़ेशन की आवश्यकता है, जो यह प्रदान नहीं करता है। लेकिन परमाणु की खामियां हैं, ज़ाहिर है।
BenchmarkMutexFew -43000055894 ns / op
BenchmarkAtomicFew -410000014585 ns / op है
BenchmarkChanFew -45000323859 एनएस / से
BenchmarkChanBufferedFew -45000341321 एनएस / ओपी
BenchmarkChanBufferedFullFew -42000070052 एनएस / से
BenchmarkMutexMostlyReadFew -43000056402 एनएस / से
BenchmarkAtomicMostlyReadFew -410000002094 एनएस / से
BenchmarkChanMostlyReadFew -43000442689 एनएस / से
BenchmarkChanBufferedMostlyReadFew -43000449,666 ns / op
BenchmarkChanBufferedFullMostlyReadFew -45000442,708 ns / op
BenchmarkMutexMostlyWriteFew -42000079708 एनएस / से
BenchmarkAtomicMostlyWriteFew -410000013358 एनएस / से
BenchmarkChanMostlyWriteFew -43000449,556 ns / op
BenchmarkChanBufferedMostlyWriteFew -43000445423 ns / op
BenchmarkChanBufferedFullMostlyWriteFew -43000414626 ns / op

इसके बाद म्यूटेक्स है। मुझे उम्मीद थी कि चैनल म्यूटेक्स के रूप में तेज़ होगा, लेकिन नहीं।

चैनल म्यूटेक्स की तुलना में अधिक महंगा परिमाण का एक आदेश है।

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

किसी भी लोड प्रोफ़ाइल पर कितनी लागत आती है, के वितरण के साथ यह तस्वीर - MostlyRead और MostlyWrite दोनों पर। इसके अलावा, पूरा MostlyRead चैनल अधूरा के समान है। और MostlyWrite का बफर चैनल, जिसमें बफर पूरा नहीं है, बाकी की तरह ही खर्च होता है। मैं यह नहीं कह सकता कि ऐसा क्यों है - मैंने अभी तक इस मुद्दे का अध्ययन नहीं किया है।

पासिंग पैरामीटर


कैसे तेजी से मापदंडों को पारित करने के लिए - संदर्भ या मूल्य से? आइए इसे देखें।

मैंने निम्नानुसार जाँच की - नेस्टेड प्रकार 1 से 10 तक।

 type TP001 struct { I001 int64 } type TV002 struct { I001 int64 S001 TV001 I002 int64 S002 TV001 } 

दसवें नेस्टेड प्रकार में 10 इंट64 क्षेत्र होंगे, और पिछले नेस्टिंग के नेस्टेड प्रकार भी 10 होंगे।

फिर उन्होंने ऐसे कार्य लिखे जो एक प्रकार के घोंसले का निर्माण करते हैं।

 func NewTP001() *TP001 { return &TP001{ I001: rand.Int63(), } } func NewTV002() TV002 { return TV002{ I001: rand.Int63(), S001: NewTV001(), I002: rand.Int63(), S002: NewTV001(), } } 

परीक्षण के लिए, मैंने टाइप के तीन विकल्प का उपयोग किया: नेस्टिंग 2 के साथ छोटा, नेस्टिंग 3 के साथ मध्यम, नेस्टिंग 5 के साथ बड़ा। मुझे रात में 10 घोंसले के शिकार के साथ एक बहुत बड़ा परीक्षण करना था, लेकिन तस्वीर बिल्कुल 5 के समान है।

फ़ंक्शन में, मान से गुजरना संदर्भ से गुजरने के रूप में कम से कम दो बार तेज है । यह इस तथ्य के कारण है कि मूल्य से गुजरना एस्केप विश्लेषण को लोड नहीं करता है। तदनुसार, जो चर हम आवंटित करते हैं, वे स्टैक पर हैं। यह कचरा संग्रहकर्ता के लिए रनटाइम के लिए काफी सस्ता है। हालांकि उसके पास जुड़ने का समय नहीं हो सकता है। ये परीक्षण कई सेकंड के लिए चले गए - कचरा कलेक्टर शायद अभी भी सो रहा था।
BenchmarkCreateSmallByValue -42000008942 एनएस / से
BenchmarkCreateSmallByPointer -410000015985 एनएस / से
BenchmarkCreateMediuMByValue -42000862317 एनएस / से
BenchmarkCreateMediuMByPointer -420001228130 एनएस / से
BenchmarkCreateLargeByValue -43047398456 ns / op
BenchmarkCreateLargeByPointer -42061928751 ns / op

काला जादू


क्या आप जानते हैं कि यह प्रोग्राम क्या आउटपुट देगा?

 package main type A struct { a, b int32 } func main() { a := new(A) aa = 0 ab = 1 z := (*(*int64)(unsafe.Pointer(a))) fmt.Println(z) } 

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

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

मैंने इस कोड को यह समझाने के लिए उद्धृत किया कि मैं सभी असुरक्षित काले जादू को क्यों मानता हूं। आपको इसका उपयोग करने की आवश्यकता नहीं है। लेकिन सिरिल का मानना ​​है कि यह आवश्यक है। और यहाँ क्यों है।

एक फ़ंक्शन है जो GOB - गो बाइनरी मार्शल के समान काम करता है। यह एनकोडर है, लेकिन असुरक्षित पर।

 func encodeMut(data []uint64) (res []byte) { sz := len(data) * 8 dh := (*header)(unsafe.Pointer(&data)) rh := &header{ data: dh.data, len: sz, cap: sz, } res = *(*[]byte)(unsafe.Pointer(&rh)) return } 

वास्तव में, यह मेमोरी का एक टुकड़ा लेता है और इसमें से बाइट्स की एक सरणी खींचता है।

यह भी एक आदेश नहीं है - ये दो आदेश हैं। इसलिए, साइरिल डानशिन, जब वह एक उच्च-प्रदर्शन कोड लिखता है, तो अपने कार्यक्रम की हिम्मत पाने और इसे असुरक्षित बनाने में संकोच नहीं करता है।

बेंचमार्क gob-42000008466 ns / op है120.94 एमबी / एस
BenchmarkUnsafeMut -45000000037 ns / op27691.06 एमबी / एस
हम गोलांगकॉन्फ़ में 7 अक्टूबर को गो की अधिक विशिष्ट विशेषताओं पर चर्चा करेंगे - जो पेशेवर विकास में गो का उपयोग करने वालों के लिए एक सम्मेलन है, और जो इस भाषा को विकल्प के रूप में मानते हैं। यदि आप इस लेख के साथ बहस करना चाहते हैं या संबंधित मुद्दों को प्रकट करना चाहते हैं - तो एक रिपोर्ट के लिए एक आवेदन प्रस्तुत करें

उच्च प्रदर्शन के संबंध में, बाकी सब कुछ के लिए, निश्चित रूप से, HighLoad ++ । हम वहां भी आवेदन स्वीकार करते हैं। समाचार पत्र के लिए साइन अप करें और वेब डेवलपर्स के लिए हमारे सभी सम्मेलनों की खबर के साथ तारीख तक रहें।

Source: https://habr.com/ru/post/hi461291/


All Articles