मेममेरी मेमरी से तेज क्यों है?


89

मैं एक आवेदन में प्रदर्शन हॉटस्पॉट की जांच कर रहा हूं, जो अपने समय का 50% मेमोव (3) में खर्च करता है। एप्लिकेशन लाखों 4-बाइट पूर्णांकों को सॉर्ट किए गए सरणियों में सम्मिलित करता है, और सम्मिलित मूल्य के लिए स्थान बनाने के लिए डेटा को "दाईं ओर" स्थानांतरित करने के लिए मेमोव का उपयोग करता है।

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

मैंने अपने बेंचमार्क को दो मशीनों (कोर i5, कोर i7) पर चलाया और देखा कि मेमोव वास्तव में मेमरी से ज्यादा तेज है, पुराने कोर i7 पर भी लगभग दोगुना तेज है! अब मैं स्पष्टीकरण की तलाश कर रहा हूं।

यहाँ मेरा बेंचमार्क है। यह मेम्पी के साथ 100 एमबी की प्रतिलिपि बनाता है, और फिर मेम्बोव के साथ लगभग 100 एमबी चलता है; स्रोत और गंतव्य अतिव्यापी हैं। स्रोत और गंतव्य के लिए विभिन्न "दूरी" की कोशिश की जाती है। प्रत्येक परीक्षण 10 बार चलाया जाता है, औसत समय मुद्रित किया जाता है।

https://gist.github.com/cruppstahl/78a57cdf937bca3d062c

यहां कोर i5 (लिनक्स 3.5.0-54-जेनेरिक # 81 ~ सटीक1-Ubuntu SMP x86_64 GNU / Linux, gcc 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) पर परिणाम हैं। कोष्ठक में संख्या है) स्रोत और गंतव्य के बीच की दूरी (अंतर का आकार):

memcpy        0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633

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

( मेम्स्की-ssse3-back.S , लाइनें 1650 ff)

L(gobble_ll_loop):
    prefetchnta -0x1c0(%rsi)
    prefetchnta -0x280(%rsi)
    prefetchnta -0x1c0(%rdi)
    prefetchnta -0x280(%rdi)
    sub $0x80, %rdx
    movdqu  -0x10(%rsi), %xmm1
    movdqu  -0x20(%rsi), %xmm2
    movdqu  -0x30(%rsi), %xmm3
    movdqu  -0x40(%rsi), %xmm4
    movdqu  -0x50(%rsi), %xmm5
    movdqu  -0x60(%rsi), %xmm6
    movdqu  -0x70(%rsi), %xmm7
    movdqu  -0x80(%rsi), %xmm8
    movdqa  %xmm1, -0x10(%rdi)
    movdqa  %xmm2, -0x20(%rdi)
    movdqa  %xmm3, -0x30(%rdi)
    movdqa  %xmm4, -0x40(%rdi)
    movdqa  %xmm5, -0x50(%rdi)
    movdqa  %xmm6, -0x60(%rdi)
    movdqa  %xmm7, -0x70(%rdi)
    movdqa  %xmm8, -0x80(%rdi)
    lea -0x80(%rsi), %rsi
    lea -0x80(%rdi), %rdi
    jae L(gobble_ll_loop)

मेमू क्यों तेज है तो मेम्कॉपी? मैं मेमरी को मेमोरी पेज कॉपी करने की उम्मीद करूंगा, जो लूपिंग की तुलना में बहुत तेज होना चाहिए। सबसे खराब स्थिति में मैं उम्मीद करूंगा कि मेमोविले जितना ही तेज होगा।

पुनश्च: मुझे पता है कि मैं अपने कोड में मेम्मो को मेमकी से नहीं बदल सकता। मुझे पता है कि कोड नमूना C और C ++ को मिलाता है। यह सवाल वास्तव में सिर्फ अकादमिक उद्देश्यों के लिए है।

अद्यतन १

मैंने विभिन्न उत्तरों के आधार पर परीक्षणों के कुछ रूपों को चलाया।

  1. जब दो बार मेमेची चलाते हैं, तो दूसरा रन पहले की तुलना में तेज होता है।
  2. जब "टच" डेस्टिनेशन बफर ऑफ मेम्सी ( memset(b2, 0, BUFFERSIZE...)) होता है तो मेम्पी का पहला रन भी तेज होता है।
  3. मेमकी अब भी मेमोव की तुलना में थोड़ी धीमी है।

यहाँ परिणाम हैं:

memcpy        0.0118526
memcpy        0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648

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

सभी महान जवाब और टिप्पणियों के लिए धन्यवाद!


1
आपने मेम्मोव कोड को देखा, क्या आपने मेम्ची कोड को भी देखा?
ओलिवर चार्ल्सवर्थ

8
मेरी उम्मीद यह थी कि स्मृति की नकल करना बहुत तेज है - केवल जब स्मृति एल 1 कैश में है। जब डेटा कैश में फिट नहीं होता है तो आपका प्रतिलिपि प्रदर्शन घट जाता है।
मैक्सिम इगोरुशिन

1
BTW, आपने केवल एक शाखा की प्रतिलिपि बनाई है memmove। जब स्रोत गंतव्य को ओवरलैप करता है तो यह शाखा स्थानांतरित नहीं हो सकती है और गंतव्य कम पते पर है।
मैक्सिम एगोरुस्किन

2
मेरे पास लिनक्स मशीन तक पहुंचने का समय नहीं है, इसलिए मैं इस सिद्धांत का परीक्षण नहीं कर सकता। लेकिन एक और संभावित स्पष्टीकरण अतिव्याप्ति है ; आपका memcpyलूप पहली बार है कि सामग्री b2को एक्सेस किया गया है, इस प्रकार ओएस को इसके लिए भौतिक मेमोरी करना पड़ता है क्योंकि यह जाता है।
ओलिवर चार्ल्सवर्थ

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

जवाबों:


56

आपकी memmoveकॉल 2 से 128 बाइट्स के साथ मेमोरी को फेरबदल कर रही हैं, जबकि आपका memcpyस्रोत और गंतव्य पूरी तरह से अलग हैं। किसी तरह कि प्रदर्शन अंतर के लिए लेखांकन है: यदि आप एक ही जगह पर कॉपी करते हैं, तो आप देखेंगे कि memcpyसंभवत : तेजी से एक फ्रिज खत्म हो जाएगा , जैसे ideen.com पर :

memmove (002) 0.0610362
memmove (004) 0.0554264
memmove (008) 0.0575859
memmove (016) 0.057326
memmove (032) 0.0583542
memmove (064) 0.0561934
memmove (128) 0.0549391
memcpy 0.0537919

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


मुझे उम्मीद होगी कि सीपीयू कैश में अंतर पैदा नहीं कर रहा है क्योंकि मेरे बफ़र्स कैश से बहुत बड़े हैं।
क्रुप्स्तहल

2
लेकिन प्रत्येक को मुख्य मेमोरी एक्सेस की कुल संख्या की आवश्यकता होती है, है ना? (यानी 100 एमबी रीड, और 100 एमबी लेखन)। कैश पैटर्न उस दौर में नहीं मिलता है। तो एक ही तरीका है कि एक दूसरे की तुलना में धीमी हो सकती है अगर कुछ सामान को एक से अधिक बार मेमोरी से / से पढ़ना / लिखना पड़ता है।
ओलिवर चार्ल्सवर्थ

2
@ टोनी डी - मेरा निष्कर्ष उन लोगों से पूछना था जो मुझसे ज्यादा होशियार हैं;)
क्रुप्स्तहल

1
इसके अलावा, यदि आप उसी स्थान पर कॉपी करते हैं, तो क्या होता है, लेकिन memcpyपहले दोबारा करें?
ओलिवर चार्ल्सवर्थ

1
@ ओलिवरचर्ल्सवर्थ: पहला टेस्ट रन हमेशा एक महत्वपूर्ण हिट लेता है, लेकिन दो यादगार टेस्ट कर रहा है: मेमसीपी 0.0688002 0.0583162 | memmove 0.0577443 0.05862 0.0601029 ... देखें ideone.com/8EEAcA
टोनी

24

जब आप उपयोग कर रहे हैं memcpy, तो राइट्स को कैश में जाने की आवश्यकता है। जब आप उपयोग करते हैं memmoveजहां आप एक छोटे से कदम को कॉपी कर रहे हैं, तो आप जिस मेमोरी को कॉपी कर रहे हैं वह पहले से ही कैश में होगा (क्योंकि यह 2, 4, 16 या 128 बाइट्स "बैक" पढ़ा गया था)। एक करके देखें memmoveजहां गंतव्य कई मेगाबाइट (> 4 * कैश आकार) है, और मुझे लगता है (लेकिन परीक्षण करने के लिए परेशान नहीं किया जा सकता है) आप समान परिणाम प्राप्त करेंगे।

मैं गारंटी देता हूं कि जब आप बड़े मेमोरी ऑपरेशन करते हैं तो सभी कैश रखरखाव के बारे में है।


+1 मैं आपके द्वारा बताए गए कारणों के बारे में सोचता हूं, पीछे की तरफ लूपिंग मेमोव, मेम्ची से कैश फ्रेंडली है। हालांकि, मुझे पता चला कि जब दो बार मेम्ची टेस्ट चल रहा है, तो दूसरा रन मेमोव की तरह तेज है। क्यों? बफ़र्स इतने बड़े होते हैं कि मेम्के का एक दूसरा रन पहले रन के रूप में अक्षम (कैश-वार) होना चाहिए। तो ऐसा लगता है कि यहां अतिरिक्त कारक हैं जो प्रदर्शन दंड का कारण बनते हैं।
क्रुप्स्तहल

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

15

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

अंतिम परिणाम यह है कि मेमोव को एक विशेष तरीके से अतिव्यापी क्षेत्रों को संभालने के लिए परिभाषित किया गया था, भले ही यह प्रदर्शन को प्रभावित करता हो। मेमकोपी को गैर-अतिव्यापी क्षेत्रों के लिए उपलब्ध सर्वोत्तम एल्गोरिदम का उपयोग करना चाहिए। कार्यान्वयन आम तौर पर लगभग समान हैं।

आपके द्वारा चलाई गई समस्या यह है कि x86 हार्डवेयर की इतनी विविधताएँ हैं कि यह बता पाना असंभव है कि स्मृति को चारों ओर स्थानांतरित करने का कौन सा तरीका सबसे तेज़ होगा। और यहां तक ​​कि अगर आपको लगता है कि आपके पास एक परिस्थिति में एक परिणाम कुछ है जैसा कि मेमोरी लेआउट में एक अलग 'स्ट्राइड' होने के कारण सरल रूप से अलग-अलग कैश प्रदर्शन हो सकता है।

आप या तो बेंचमार्क कर सकते हैं कि आप वास्तव में क्या कर रहे हैं या समस्या को अनदेखा कर सकते हैं और सी लाइब्रेरी के लिए किए गए बेंचमार्क पर भरोसा कर सकते हैं।

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

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


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

मेरा आवेदन है एक बी पेड़! जब भी पूर्णांक को पत्ती के नोड में डाला जाता है तो मेमोव को स्थान बनाने के लिए कहा जाता है। मैं एक डेटाबेस इंजन पर काम कर रहा हूँ।
क्रुप्स्तहल

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

हालांकि यह उत्तर सही है, यह वास्तव में यह नहीं समझाता है कि इस मामले में क्यों धीमा है, यह अनिवार्य रूप से कह रहा है "यह धीमा है क्योंकि कुछ मामलों में यह धीमा हो सकता है"।
ओलिवर चार्ल्सवर्थ

मैं कह रहा हूं कि समान परिस्थितियों के लिए, बेंचमार्क को कॉपी / ले जाने के लिए मेमोरी का एक ही लेआउट शामिल होगा, क्योंकि कार्यान्वयन समान हैं। समस्या microbenchmark में है।
user3710044

2

"मेमकी मेम की तुलना में अधिक कुशल है।" आपके मामले में, आप सबसे अधिक सटीक एक ही काम नहीं कर रहे हैं जब आप दो कार्यों को चलाते हैं।

सामान्य तौर पर, USE मेमॉव तभी करें जब आपको करना हो। इसका उपयोग तब करें जब बहुत ही उचित मौका हो कि स्रोत और गंतव्य क्षेत्र अति-लेपिंग हों।

संदर्भ: https://www.youtube.com/watch?v=Yr1YnOVG-4g डॉ। जेरी कैन, (स्टैनफोर्ड इंट्रो सिस्टम लेक्चर - 7) समय: 36:00

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