c++ 32-बिट सिस्टम पर int32_t के बजाय int64_t का उपयोग करने का प्रदर्शन प्रभाव क्या है?




performance 32bit-64bit (4)

हमारी C ++ लाइब्रेरी वर्तमान में समय मानों को संग्रहीत करने के लिए time_t का उपयोग करती है। मुझे कुछ स्थानों पर उप-सेकंड की सटीकता की आवश्यकता है, इसलिए वहां एक बड़ा डेटा प्रकार आवश्यक होगा। इसके अलावा, कुछ स्थानों पर वर्ष -२०३ useful की समस्या के समाधान के लिए उपयोगी हो सकता है। इसलिए मैं सभी स्थानों में time_t मान को बदलने के लिए एक अंतर्निहित int64_t मान के साथ एकल समय वर्ग पर पूरी तरह से स्विच करने के बारे में सोच रहा हूं।

अब मैं 32-बिट ऑपरेटिंग सिस्टम या 32-बिट CPU पर इस कोड को चलाते समय इस तरह के बदलाव के प्रदर्शन प्रभाव के बारे में सोच रहा हूं। IIUC कंपाइलर 32-बिट रजिस्टरों का उपयोग करके 64-बिट अंकगणितीय प्रदर्शन करने के लिए कोड उत्पन्न करेगा। लेकिन अगर यह बहुत धीमा है, तो मुझे समय के मूल्यों से निपटने के लिए अधिक विभेदित तरीके का उपयोग करना पड़ सकता है, जिससे सॉफ्टवेयर को बनाए रखना मुश्किल हो सकता है।

मुझे इसमें क्या दिलचस्पी है:

  • कौन से कारक इन कार्यों के प्रदर्शन को प्रभावित करते हैं? संभवतः संकलक और संकलक संस्करण; लेकिन क्या ऑपरेटिंग सिस्टम या CPU मेक / मॉडल को भी प्रभावित करता है? क्या एक सामान्य 32-बिट सिस्टम 64-बिट रजिस्टर आधुनिक CPU का उपयोग करेगा?
  • 32-बिट पर अनुकरण करने पर कौन से संचालन विशेष रूप से धीमा होगा? या जिसमें कोई मंदी नहीं होगी?
  • 32-बिट सिस्टम पर int64_t / uint64_t का उपयोग करने के लिए कोई मौजूदा बेंचमार्क परिणाम हैं?
  • क्या किसी के पास इस प्रदर्शन प्रभाव के बारे में कोई अनुभव है?

मैं ज्यादातर इंटेल 2.6 2 सिस्टम पर लिनक्स 2.6 (आरएचईएल 5, आरएचईएल 6) पर जी ++ 4.1 और 4.4 में रुचि रखता हूं; लेकिन अन्य प्रणालियों (जैसे स्पार्क सोलारिस + सोलारिस सीसी, विंडोज + एमएसवीसी) के लिए स्थिति के बारे में जानना भी अच्छा होगा।


Answer #1

32-बिट मोड में 64-बिट गणित करने के बारे में आप से अधिक जानना चाहते थे ...

जब आप 32-बिट मोड पर 64-बिट नंबर का उपयोग करते हैं (64-बिट सीपीयू पर भी यदि कोई कोड 32-बिट के लिए संकलित किया जाता है), तो उन्हें दो अलग-अलग 32-बिट संख्याओं में संग्रहीत किया जाता है, एक नंबर के उच्च बिट्स को संग्रहीत करता है, और एक और भंडारण निचले बिट्स। इसका प्रभाव एक निर्देश पर निर्भर करता है। (tl; dr; आम तौर पर, ३२-बिट सीपीयू पर ६४-बिट गणित करना सिद्धांत में २ गुना धीमा है, जब तक कि आप विभाजित / मोडुलो नहीं करते हैं, हालांकि व्यवहार में अंतर छोटा होने वाला है (१.३x मेरा होगा) लगता है), क्योंकि आमतौर पर प्रोग्राम केवल 64-बिट पूर्णांक पर गणित नहीं करते हैं, और पाइपलाइनिंग के कारण भी, आपके प्रोग्राम में अंतर बहुत कम हो सकता है)।

जोड़ / घटाव

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

  C 7 6 5 4 3 2 1 0      C 7 6 5 4 3 2 1 0
  0 1 1 1 1 1 1 1 1      1 0 0 0 0 0 0 0 0
+   0 0 0 0 0 0 0 1    -   0 0 0 0 0 0 0 1
= 1 0 0 0 0 0 0 0 0    = 0 1 1 1 1 1 1 1 1

झंडा प्रासंगिक क्यों है? ठीक है, यह सिर्फ इतना होता है कि सीपीयू में आमतौर पर दो अलग-अलग जोड़ और घटाव संचालन होते हैं। एक्स 86 में, अतिरिक्त संचालन को add और adc कहा जाता है। add अतिरिक्त के लिए खड़ा है, जबकि कैरी के साथ add के लिए। उन के बीच अंतर यह है कि adc एक कैरी बिट मानता है, और यदि यह सेट है, तो यह परिणाम में एक जोड़ता है।

इसी तरह, कैरी घटाव के साथ घटाव 1 परिणाम से ले जाता है अगर कैरी बिट सेट नहीं है।

यह व्यवहार मनमाने ढंग से आकार जोड़ने और पूर्णांक पर घटाव को आसानी से लागू करने की अनुमति देता है। एक्स और वाई के जोड़ का परिणाम (उन 8-बिट हैं) 0x1FE से बड़ा कभी नहीं 0x1FE । यदि आप 1 जोड़ते हैं, तो आपको 0x1FF मिलता है। 9 बिट्स इसलिए किसी भी 8-बिट जोड़ के परिणामों का प्रतिनिधित्व करने के लिए पर्याप्त है। यदि आप add साथ add शुरू करते add , और फिर adc साथ प्रारंभिक बिट्स से परे किसी भी बिट्स को add , तो आप अपने पसंद के किसी भी आकार पर जोड़ सकते हैं।

32-बिट सीपीयू पर दो 64-बिट मानों का जोड़ इस प्रकार है।

  1. A के पहले 32 बिट्स में b के पहले 32 बिट्स जोड़ें।
  2. बाद में 32 बिट्स के बी के बाद के 32 बिट्स के साथ जोड़ें।

सूक्ष्म रूप से घटाव के लिए।

यह 2 निर्देश देता है, हालांकि, निर्देश पाइपलाइनिंग के कारण , यह उससे धीमा हो सकता है, क्योंकि एक गणना दूसरे को समाप्त करने के लिए निर्भर करती है, इसलिए यदि सीपीयू के पास 64-बिट के अलावा कुछ और नहीं है, तो सीपीयू इंतजार कर सकता है पहले अतिरिक्त के लिए किया जाएगा।

गुणन

यह x86 पर ऐसा होता है कि imul और mul का उपयोग इस तरह से किया जा सकता है कि ओवरफ्लो को edx रजिस्टर में संग्रहीत किया जाता है। इसलिए, 64-बिट मान प्राप्त करने के लिए दो 32-बिट मानों को गुणा करना वास्तव में आसान है। ऐसा गुणन एक निर्देश है, लेकिन इसका उपयोग करने के लिए गुणन मूल्यों में से एक को ईएक्सएक्स में संग्रहित किया जाना चाहिए।

वैसे भी, दो 64-बिट मानों के गुणन के अधिक सामान्य मामले के लिए, उन्हें निम्नलिखित सूत्र का उपयोग करके गणना की जा सकती है (मान लें कि फ़ंक्शन आर 32 बिट्स से परे बिट्स हटाता है)।

सबसे पहले, यह नोटिस करना आसान है कि परिणाम के निचले 32 बिट्स गुणक चर के 32 बिट्स के निचले हिस्से का गुणन होगा। यह संबंध संबंध के कारण है।

1 a b 1 (mod n )
a 2b 2 (mod n )
1 2 1 बी 1 बी 2 (मॉड एन )

इसलिए, कार्य केवल उच्च 32 बिट्स का निर्धारण करने तक सीमित है। परिणाम के 32 बिट्स की गणना करने के लिए, निम्न मानों को एक साथ जोड़ा जाना चाहिए।

  • दोनों निचले 32 बिट्स के गुणन के उच्चतर 32 बिट्स (अतिप्रवाह जो CPU edx में स्टोर कर सकते हैं)
  • पहले चर के उच्च 32 बिट्स दूसरे चर के कम 32 बिट्स के साथ mulitplied
  • पहले चर के निचले 32 बिट्स को दूसरे चर के 32 बिट्स के साथ गुणा किया जाता है

यह लगभग 5 निर्देश देता है, हालाँकि x86 में अपेक्षाकृत सीमित संख्या में रजिस्टरों (वास्तुकला में विस्तार को अनदेखा करना) के कारण, वे पाइपलाइनिंग का बहुत अधिक लाभ नहीं उठा सकते हैं। यदि आप गुणा की गति में सुधार करना चाहते हैं तो SSE को सक्षम करें, क्योंकि इससे रजिस्टरों की संख्या बढ़ जाती है।

प्रभाग / मोडुलो (कार्यान्वयन में दोनों समान हैं)

मुझे नहीं पता कि यह कैसे काम करता है, लेकिन यह जोड़, घटाव या गुणा से भी अधिक जटिल है। यह 64-बिट सीपीयू पर विभाजन की तुलना में दस गुना धीमा होने की संभावना है। "कंप्यूटर प्रोग्रामिंग की कला, खंड 2: अर्धसूत्रीविभाजन एल्गोरिदम", अधिक विवरण के लिए पृष्ठ 257 की जांच करें यदि आप इसे समझ सकते हैं (मैं इस तरह से नहीं कर सकता कि मैं इसे समझा सकता हूं, दुर्भाग्य से)।

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

या / और / Xor

उन ऑपरेशनों को ध्यान में रखते हुए सिंगल बिट ऑपरेशन होते हैं, यहाँ कुछ खास नहीं होता है, बस बिटवाइज़ ऑपरेशन दो बार किया जाता है।

बाएँ / दाएँ शिफ्ट करना

दिलचस्प बात यह है कि x86 में वास्तव में shld नामक 64-बिट लेफ्ट शिफ्ट करने का निर्देश है, जो शून्य के साथ कम से कम महत्वपूर्ण बिट्स को बदलने के बजाय, उन्हें एक अलग रजिस्टर के सबसे महत्वपूर्ण बिट्स के साथ बदल देता है। इसी तरह, यह सही निर्देश के लिए मामला है। यह आसानी से दो निर्देशों के संचालन को 64-बिट शिफ्टिंग बना देगा।

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

__builtin_popcount [ll]

__builtin_popcount (निचला) + __builtin_popcount (अधिक)

अन्य भवन

मैं इस बिंदु पर जवाब खत्म करने के लिए बहुत आलसी हूं। क्या कोई भी उन का उपयोग करता है?

अहस्ताक्षरित बनाम हस्ताक्षरित

जोड़, घटाव, गुणा, या, और, xor, Shift बाएँ बिलकुल समान कोड उत्पन्न करते हैं। शिफ्ट राइट केवल थोड़े अलग कोड (अंकगणितीय पारी बनाम तार्किक पारी) का उपयोग करता है, लेकिन संरचनात्मक रूप से यह समान है। यह संभावना है कि डिवीजन एक अलग कोड उत्पन्न करता है, और हस्ताक्षरित डिवीजन अहस्ताक्षरित विभाजन की तुलना में धीमा होने की संभावना है।

मानक

मानक? वे ज्यादातर अर्थहीन हैं, क्योंकि जब आप लगातार एक ही ऑपरेशन को नहीं दोहराते हैं, तो निर्देश पाइपलाइनिंग आमतौर पर चीजों को तेज कर देगा। विभाजन को धीमा मानने के लिए स्वतंत्र महसूस करें, लेकिन वास्तव में और कुछ भी नहीं है, और जब आप बेंचमार्क से बाहर निकलते हैं, तो आप देख सकते हैं कि पाइपलाइनिंग के कारण, 32-बिट सीपीयू पर 64-बिट संचालन करना बिल्कुल भी धीमा नहीं है।

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


Answer #2

जोड़ / घटाव मूल रूप से दो चक्र बन जाते हैं, गुणा और भाग वास्तविक सीपीयू पर निर्भर करते हैं। सामान्य परिपूर्णता प्रभाव कम होगा।

ध्यान दें कि Intel Core 2 EM64T का समर्थन करता है।


Answer #3

आपका प्रश्न इसके वातावरण में बहुत अजीब लगता है। आप 32 बिट्स का उपयोग करने वाले time_t का उपयोग करते हैं। आपको अतिरिक्त जानकारी चाहिए, अधिक बिट्स का क्या अर्थ है। तो आप int32 से कुछ बड़ा उपयोग करने के लिए मजबूर हैं। इससे कोई फर्क नहीं पड़ता कि प्रदर्शन क्या है, सही है? विकल्प केवल 40 बिट्स का उपयोग करने के बीच जाएंगे या int64 से आगे जाएंगे। जब तक लाखों उदाहरणों को संग्रहीत नहीं किया जाना चाहिए, बाद वाला एक समझदार विकल्प है।

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

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


Answer #4

कौन से कारक इन कार्यों के प्रदर्शन को प्रभावित करते हैं? संभवतः संकलक और संकलक संस्करण; लेकिन क्या ऑपरेटिंग सिस्टम या CPU मेक / मॉडल को भी प्रभावित करता है?

ज्यादातर प्रोसेसर वास्तुकला (और मॉडल - कृपया मॉडल पढ़ें जहां मैं इस खंड में प्रोसेसर वास्तुकला का उल्लेख करता हूं)। कंपाइलर पर कुछ प्रभाव हो सकता है, लेकिन अधिकांश कंपाइलर इस पर बहुत अच्छा करते हैं, इसलिए प्रोसेसर आर्किटेक्चर का कंपाइलर से बड़ा प्रभाव होगा।

ऑपरेटिंग सिस्टम पर कोई प्रभाव नहीं पड़ेगा ("यदि आप ओएस बदलते हैं तो इसके अलावा", आपको एक अलग प्रकार के कंपाइलर का उपयोग करने की आवश्यकता है जो कुछ मामलों में कंपाइलर क्या बदलता है - लेकिन यह शायद एक छोटा प्रभाव है)।

क्या एक सामान्य 32-बिट सिस्टम 64-बिट रजिस्टर आधुनिक CPU का उपयोग करेगा?

यह नहीं हो सकता। यदि सिस्टम 32-बिट मोड में है, तो यह 32-बिट सिस्टम के रूप में कार्य करेगा, रजिस्टरों के अतिरिक्त 32-बिट पूरी तरह से अदृश्य हैं, जैसे कि यह होगा कि सिस्टम वास्तव में "सही 32-बिट सिस्टम" था ।

32-बिट पर अनुकरण करने पर कौन से संचालन विशेष रूप से धीमा होगा? या जिसमें कोई मंदी नहीं होगी?

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

यदि इनपुट पैरामीटर्स वास्तव में 64-बिट हैं, तो मिसिटिप्लिकेशन बहुत खराब हो जाएगा - इसलिए 2 ^ 35 * 83 उदाहरण के लिए 2 ^ 31 * 2 ^ 31 से भी बदतर है। यह इस तथ्य के कारण है कि प्रोसेसर 32 x 32 बिट को 64-बिट परिणाम में बहुत अच्छी तरह से गुणा कर सकता है - कुछ 5-10 घड़ी। लेकिन 64 x 64 बिट में कई गुणा अतिरिक्त कोड की आवश्यकता होती है, इसलिए इसमें अधिक समय लगेगा।

विभाजन गुणा करने के लिए एक समान समस्या है - लेकिन यहां एक तरफ 64-बिट इनपुट लेना ठीक है, इसे 32-बिट मान से विभाजित करें और 32-बिट मान प्राप्त करें। चूंकि यह अनुमान लगाना कठिन है कि यह कब काम करेगा, 64-बिट डिवाइड शायद लगभग हमेशा धीमा होता है।

डेटा भी दोगुना कैश-स्पेस लेगा, जो परिणामों को प्रभावित कर सकता है। और एक समान परिणाम के रूप में, सामान्य असाइनमेंट और चारों ओर से गुजरने वाले डेटा को न्यूनतम के रूप में दोगुना समय लगेगा, क्योंकि संचालित करने के लिए डेटा का दोगुना है।

कंपाइलर को अधिक रजिस्टरों का उपयोग करने की भी आवश्यकता होगी।

32-बिट सिस्टम पर int64_t / uint64_t का उपयोग करने के लिए कोई मौजूदा बेंचमार्क परिणाम हैं?

शायद, लेकिन मुझे किसी की जानकारी नहीं है। और यहां तक ​​कि अगर वहाँ हैं, तो यह केवल आपके लिए कुछ हद तक सार्थक होगा, क्योंकि संचालन का मिश्रण संचालन की गति के लिए अत्यधिक महत्वपूर्ण है।

यदि प्रदर्शन आपके आवेदन का एक महत्वपूर्ण हिस्सा है, तो अपने कोड (या इसके कुछ प्रतिनिधि भाग) को बेंचमार्क करें। अगर बेंचमार्क एक्स 5%, 25% या 103% धीमा परिणाम देता है, तो यह वास्तव में कोई बात नहीं है, यदि आपका कोड समान परिस्थितियों में कुछ पूरी तरह से अलग राशि धीमी या तेज है।

क्या किसी के पास इस प्रदर्शन प्रभाव के बारे में कोई अनुभव है?

मैंने 64-बिट आर्किटेक्चर के लिए 64-बिट पूर्णांक का उपयोग करने वाले कुछ कोड को फिर से जोड़ दिया है, और कुछ बिट द्वारा कोड के कुछ बिट्स पर - 25% के रूप में प्रदर्शन में सुधार पाया।

अपने OS को उसी OS के 64-बिट संस्करण में बदलना, शायद मदद करेगा?

संपादित करें:

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

यह कोड मैंने लिखा है, कुछ सामान्य फंक्शन्स को दोहराने की कोशिश कर रहा हूँ:

#include <iostream>
#include <cstdint>
#include <ctime>

using namespace std;

static __inline__ uint64_t rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (uint64_t)lo)|( ((uint64_t)hi)<<32 );
}

template<typename T>
static T add_numbers(const T *v, const int size)
{
    T sum = 0;
    for(int i = 0; i < size; i++)
    sum += v[i];
    return sum;
}


template<typename T, const int size>
static T add_matrix(const T v[size][size])
{
    T sum[size] = {};
    for(int i = 0; i < size; i++)
    {
    for(int j = 0; j < size; j++)
        sum[i] += v[i][j];
    }
    T tsum=0;
    for(int i = 0; i < size; i++)
    tsum += sum[i];
    return tsum;
}



template<typename T>
static T add_mul_numbers(const T *v, const T mul, const int size)
{
    T sum = 0;
    for(int i = 0; i < size; i++)
    sum += v[i] * mul;
    return sum;
}

template<typename T>
static T add_div_numbers(const T *v, const T mul, const int size)
{
    T sum = 0;
    for(int i = 0; i < size; i++)
    sum += v[i] / mul;
    return sum;
}


template<typename T> 
void fill_array(T *v, const int size)
{
    for(int i = 0; i < size; i++)
    v[i] = i;
}

template<typename T, const int size> 
void fill_array(T v[size][size])
{
    for(int i = 0; i < size; i++)
    for(int j = 0; j < size; j++)
        v[i][j] = i + size * j;
}




uint32_t bench_add_numbers(const uint32_t v[], const int size)
{
    uint32_t res = add_numbers(v, size);
    return res;
}

uint64_t bench_add_numbers(const uint64_t v[], const int size)
{
    uint64_t res = add_numbers(v, size);
    return res;
}

uint32_t bench_add_mul_numbers(const uint32_t v[], const int size)
{
    const uint32_t c = 7;
    uint32_t res = add_mul_numbers(v, c, size);
    return res;
}

uint64_t bench_add_mul_numbers(const uint64_t v[], const int size)
{
    const uint64_t c = 7;
    uint64_t res = add_mul_numbers(v, c, size);
    return res;
}

uint32_t bench_add_div_numbers(const uint32_t v[], const int size)
{
    const uint32_t c = 7;
    uint32_t res = add_div_numbers(v, c, size);
    return res;
}

uint64_t bench_add_div_numbers(const uint64_t v[], const int size)
{
    const uint64_t c = 7;
    uint64_t res = add_div_numbers(v, c, size);
    return res;
}


template<const int size>
uint32_t bench_matrix(const uint32_t v[size][size])
{
    uint32_t res = add_matrix(v);
    return res;
}
template<const int size>
uint64_t bench_matrix(const uint64_t v[size][size])
{
    uint64_t res = add_matrix(v);
    return res;
}


template<typename T>
void runbench(T (*func)(const T *v, const int size), const char *name, T *v, const int size)
{
    fill_array(v, size);

    uint64_t long t = rdtsc();
    T res = func(v, size);
    t = rdtsc() - t;
    cout << "result = " << res << endl;
    cout << name << " time in clocks " << dec << t  << endl;
}

template<typename T, const int size>
void runbench2(T (*func)(const T v[size][size]), const char *name, T v[size][size])
{
    fill_array(v);

    uint64_t long t = rdtsc();
    T res = func(v);
    t = rdtsc() - t;
    cout << "result = " << res << endl;
    cout << name << " time in clocks " << dec << t  << endl;
}


int main()
{
    // spin up CPU to full speed...
    time_t t = time(NULL);
    while(t == time(NULL)) ;

    const int vsize=10000;

    uint32_t v32[vsize];
    uint64_t v64[vsize];

    uint32_t m32[100][100];
    uint64_t m64[100][100];


    runbench(bench_add_numbers, "Add 32", v32, vsize);
    runbench(bench_add_numbers, "Add 64", v64, vsize);

    runbench(bench_add_mul_numbers, "Add Mul 32", v32, vsize);
    runbench(bench_add_mul_numbers, "Add Mul 64", v64, vsize);

    runbench(bench_add_div_numbers, "Add Div 32", v32, vsize);
    runbench(bench_add_div_numbers, "Add Div 64", v64, vsize);

    runbench2(bench_matrix, "Matrix 32", m32);
    runbench2(bench_matrix, "Matrix 64", m64);
}

इसके साथ संकलित:

g++ -Wall -m32 -O3 -o 32vs64 32vs64.cpp -std=c++0x

और परिणाम इस प्रकार हैं: नोट: नीचे दिए गए 2016 के परिणाम देखें - ये परिणाम 64-बिट मोड में SSE निर्देशों के उपयोग में अंतर के कारण थोड़े आशावादी हैं, लेकिन 32-बिट मोड में कोई SSE उपयोग नहीं है।

result = 49995000
Add 32 time in clocks 20784
result = 49995000
Add 64 time in clocks 30358
result = 349965000
Add Mul 32 time in clocks 30182
result = 349965000
Add Mul 64 time in clocks 79081
result = 7137858
Add Div 32 time in clocks 60167
result = 7137858
Add Div 64 time in clocks 457116
result = 49995000
Matrix 32 time in clocks 22831
result = 49995000
Matrix 64 time in clocks 23823

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

और यह 64-बिट पर तेजी से होता है मैंने सुना है कि आप में से कुछ पूछते हैं: एक ही संकलक विकल्पों का उपयोग करना, -m64 के बजाय -32 - युप्प, बहुत तेज:

result = 49995000
Add 32 time in clocks 8366
result = 49995000
Add 64 time in clocks 16188
result = 349965000
Add Mul 32 time in clocks 15943
result = 349965000
Add Mul 64 time in clocks 35828
result = 7137858
Add Div 32 time in clocks 50176
result = 7137858
Add Div 64 time in clocks 50472
result = 49995000
Matrix 32 time in clocks 12294
result = 49995000
Matrix 64 time in clocks 14733

संपादित करें, 2016 के लिए अपडेट करें : कम्पाइलर के 32- और 64-बिट मोड में, एसएसई के साथ और उसके बिना, चार वेरिएंट।

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

एक छोटी तालिका के रूप में:

Test name      | no-sse 32 | no-sse 64 | sse 32 | sse 64 |
----------------------------------------------------------
Add uint32_t   |   20837   |   10221   |   3701 |   3017 |
----------------------------------------------------------
Add uint64_t   |   18633   |   11270   |   9328 |   9180 |
----------------------------------------------------------
Add Mul 32     |   26785   |   18342   |  11510 |  11562 |
----------------------------------------------------------
Add Mul 64     |   44701   |   17693   |  29213 |  16159 |
----------------------------------------------------------
Add Div 32     |   44570   |   47695   |  17713 |  17523 |
----------------------------------------------------------
Add Div 64     |  405258   |   52875   | 405150 |  47043 |
----------------------------------------------------------
Matrix 32      |   41470   |   15811   |  21542 |   8622 |
----------------------------------------------------------
Matrix 64      |   22184   |   15168   |  13757 |  12448 |

संकलन विकल्पों के साथ पूर्ण परिणाम।

$ clang++ -m32 -mno-sse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 20837
result = 49995000
Add 64 time in clocks 18633
result = 349965000
Add Mul 32 time in clocks 26785
result = 349965000
Add Mul 64 time in clocks 44701
result = 7137858
Add Div 32 time in clocks 44570
result = 7137858
Add Div 64 time in clocks 405258
result = 49995000
Matrix 32 time in clocks 41470
result = 49995000
Matrix 64 time in clocks 22184

$ clang++ -m32 -msse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 3701
result = 49995000
Add 64 time in clocks 9328
result = 349965000
Add Mul 32 time in clocks 11510
result = 349965000
Add Mul 64 time in clocks 29213
result = 7137858
Add Div 32 time in clocks 17713
result = 7137858
Add Div 64 time in clocks 405150
result = 49995000
Matrix 32 time in clocks 21542
result = 49995000
Matrix 64 time in clocks 13757


$ clang++ -m64 -msse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 3017
result = 49995000
Add 64 time in clocks 9180
result = 349965000
Add Mul 32 time in clocks 11562
result = 349965000
Add Mul 64 time in clocks 16159
result = 7137858
Add Div 32 time in clocks 17523
result = 7137858
Add Div 64 time in clocks 47043
result = 49995000
Matrix 32 time in clocks 8622
result = 49995000
Matrix 64 time in clocks 12448


$ clang++ -m64 -mno-sse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 10221
result = 49995000
Add 64 time in clocks 11270
result = 349965000
Add Mul 32 time in clocks 18342
result = 349965000
Add Mul 64 time in clocks 17693
result = 7137858
Add Div 32 time in clocks 47695
result = 7137858
Add Div 64 time in clocks 52875
result = 49995000
Matrix 32 time in clocks 15811
result = 49995000
Matrix 64 time in clocks 15168




int64