9

Game Coding Complete, 3rd Edition, पुस्तक में लेखक डेटा संरचना आकार और दोनों को एक्सेस प्रदर्शन बढ़ाने के लिए एक तकनीक का उल्लेख करता है। संक्षेप में यह इस तथ्य पर निर्भर करता है कि जब सदस्य चर मेमोरी गठबंधन होते हैं तो आप प्रदर्शन प्राप्त करते हैं। यह एक स्पष्ट संभावित अनुकूलन है कि संकलक इसका लाभ उठाएंगे, लेकिन यह सुनिश्चित करके कि प्रत्येक चर को गठबंधन किया गया है, वे डेटा संरचना के आकार को सूखते हैं।अंतरिक्ष और प्रदर्शन बूस्ट के लिए बिट संरेखण

या कि कम से कम अपने दावे को किया गया था।

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

#pragma pack(push, 1) 

struct SlowStruct 
{ 
    char c; 
    __int64 a; 
    int b; 
    char d; 
}; 

struct FastStruct 
{ 
    __int64 a; 
    int b; 
    char c; 
    char d; 
    char unused[ 2 ]; // fill to 8-byte boundary for array use 
}; 

#pragma pack(pop) 

एक अनिर्दिष्ट परीक्षा में ऊपर struct वस्तुओं का उपयोग करते हुए वह (192ms की तुलना में 222ms) एक प्रदर्शन 15.6% की वृद्धि हुई है और FastStruct के लिए एक छोटे आकार की रिपोर्ट: वह निम्नलिखित कोड का टुकड़ा प्रदान करता है। यह सब मेरे लिए कागज पर समझ में आता है, लेकिन यह मेरी परीक्षण के तहत पकड़ में विफल रहता है:

enter image description here

एक ही समय और आकार (char unused[ 2 ] के लिए बढ़ रहा है) परिणाम!

अब अगर #pragma pack(push, 1) केवल FastStruct लिए अलग है (या पूरी तरह से हटा) हम एक अंतर देखते हैं:

enter image description here

तो, अंत में, यहाँ सवाल निहित है: (विशेष रूप से VS2010) आधुनिक compilers क्या पहले से ही बिट संरेखण के लिए अनुकूलित है, इसलिए प्रदर्शन वृद्धि की कमी (लेकिन एक पक्ष को प्रभावित के रूप में संरचना आकार में वृद्धि, जैसे माइक मकशैफ्री कहा)? या क्या मेरा परीक्षण किसी भी महत्वपूर्ण परिणाम को वापस करने के लिए पर्याप्त गहन/अनिवार्य नहीं है?

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

अंत में, भले ही उनकी कोई प्रदर्शन वृद्धि हुई थी, यह अभी भी कम से कम स्मृति के उपयोग रखने के लिए ध्यान में रखने के लिए एक उपयोगी tidbit है। लेकिन अगर मैं प्रदर्शन को बढ़ावा देता हूं (चाहे कितना नाबालिग हो) मैं इसे प्यार करूंगा कि मैं बस नहीं देख रहा हूं।

+4

तथ्य यह है कि आप सभी परीक्षणों के लिए एक ही समय में संकेत देते हैं कि आप लंबे समय तक नहीं चल रहे हैं। समय कोड का संकल्प शायद कोई अंतर दिखाने के लिए पर्याप्त नहीं है। –

+2

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

+1

@ बोपरसन: अधिक संभावना है कि कंपाइलर ने उन्हें एक ही कोड का उत्पादन करने के लिए अनुकूलित किया है। – Puppy

उत्तर

12

यह हार्डवेयर पर अत्यधिक निर्भर है।

मुझे समझें:

#pragma pack(push, 1) 

struct SlowStruct 
{ 
    char c; 
    __int64 a; 
    int b; 
    char d; 
}; 

struct FastStruct 
{ 
    __int64 a; 
    int b; 
    char c; 
    char d; 
    char unused[ 2 ]; // fill to 8-byte boundary for array use 
}; 

#pragma pack(pop) 

int main (void){ 

    int x = 1000; 
    int iterations = 10000000; 

    SlowStruct *slow = new SlowStruct[x]; 
    FastStruct *fast = new FastStruct[x]; 



    // Warm the cache. 
    memset(slow,0,x * sizeof(SlowStruct)); 
    clock_t time0 = clock(); 
    for (int c = 0; c < iterations; c++){ 
     for (int i = 0; i < x; i++){ 
      slow[i].a += c; 
     } 
    } 
    clock_t time1 = clock(); 
    cout << "slow = " << (double)(time1 - time0)/CLOCKS_PER_SEC << endl; 

    // Warm the cache. 
    memset(fast,0,x * sizeof(FastStruct)); 
    time1 = clock(); 
    for (int c = 0; c < iterations; c++){ 
     for (int i = 0; i < x; i++){ 
      fast[i].a += c; 
     } 
    } 
    clock_t time2 = clock(); 
    cout << "fast = " << (double)(time2 - time1)/CLOCKS_PER_SEC << endl; 



    // Print to avoid Dead Code Elimination 
    __int64 sum = 0; 
    for (int c = 0; c < x; c++){ 
     sum += slow[c].a; 
     sum += fast[c].a; 
    } 
    cout << "sum = " << sum << endl; 


    return 0; 
} 

कोर i7 920 3.5 गीगा @

slow = 4.578 
fast = 4.434 
sum = 99999990000000000 

ठीक है, बहुत ज्यादा नहीं फर्क। लेकिन यह अभी भी कई रनों पर लगातार है।
तो संरेखण नेहलेम कोर i7 पर एक छोटा सा अंतर बनाता है।


इंटेल जिऑन X5482 Harpertown @ 3.2 GHz (कोर 2 - पीढ़ी जिऑन)

slow = 22.803 
fast = 3.669 
sum = 99999990000000000 

अब एक बार देख ले ...

6.2x तेजी से !!!


निष्कर्ष:

आप परिणाम देखें। आप इन अनुकूलन करने के लिए अपने समय के लायक हैं या नहीं, यह तय करते हैं।


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

एक ही मानक लेकिन #pragma pack बिना:

कोर i7 920 @ 3.5 गीगा

slow = 4.49 
fast = 4.442 
sum = 99999990000000000 

इंटेल जिऑन X5482 Harpertown @ 3.2 GHz

slow = 3.684 
fast = 3.717 
sum = 99999990000000000 
  • कोर i7 संख्या नहीं बदला। स्पष्ट रूप से यह इस बेंचमार्क के लिए परेशानी के बिना misalignment संभाल सकता है।
  • कोर 2 ज़ीऑन अब दोनों संस्करणों के लिए एक ही समय दिखाता है।यह पुष्टि करता है कि कोर 2 आर्किटेक्चर पर गलत संरेखण एक समस्या है।

मेरी टिप्पणी से लिया:

आप #pragma pack को छोड़ दें, संकलक गठबंधन सब कुछ रखते हैं ताकि आप इस मुद्दे को नहीं देख पा रहे होंगे। तो यह वास्तव में एक उदाहरण है यदि आप दुरुपयोग#pragma pack का दुरुपयोग कर सकते हैं।

+0

आह, एक परीक्षण जो वास्तव में परिणाम दिखाता है! मेरे पुराने काम कंप्यूटर पर मुझे 100 से अधिक परीक्षणों में '71%' औसत प्रदर्शन वृद्धि मिली। कम आकार के साथ, और इन जैसे परिणाम, इन अनुकूलन को नहीं करना असंभव होगा, विशेष रूप से वे कितने सरल हैं। – ssell

+6

यदि आप '#pragma पैक' छोड़ देते हैं, तो संकलक सब कुछ गठबंधन रखेगा ताकि आप * इस समस्या को न देख सकें। तो यह वास्तव में एक उदाहरण है यदि आप '#pragma पैक' का दुरुपयोग करते हैं तो क्या हो सकता है। – Mysticial

+0

क्या आप '#pragma पैक' के बिना कह रहे हैं आप प्रदर्शन को बढ़ावा नहीं देंगे? पिछली टिप्पणी से मेरा परीक्षण पहले से ही बिना था। '#pragma पैक' का उपयोग करके 'फास्टस्ट्रक्चर' वास्तव में 50-200ms तक औसतन _slower_ प्रदर्शन कर रहा था। ** संपादित करें ** परीक्षण को फिर से चलाने के बाद, परिणाम '#pragma pack' के बिना समान हैं। यकीन नहीं है कि इसके बारे में क्या था। – ssell

6

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

शायद, आपके परीक्षण कार्यक्रम में, कंपाइलर ने कभी भी ढेर पर किसी भी संरचना को संग्रहित नहीं किया और सदस्यों को केवल रजिस्टरों में रखा, जिनके पास संरेखण नहीं है, जिसका अर्थ यह है कि संरचना आकार या संरेखण क्या है, यह काफी अप्रासंगिक है।

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

+0

तो, संक्षेप में, यह प्रयास के लायक नहीं है जब तक कि मुझे उन अतिरिक्त कुछ बाइटों की ज़रूरत नहीं है? इसके अलावा मैंने संकलक को केवल रजिस्टरों में रखने के बारे में नहीं सोचा था। – ssell

+3

@ssell: इस तरह के अनुकूलन केवल अधिक हो रहे हैं, और अधिक, आम। और हाँ, यह सामान्य रूप से इसके लायक नहीं है। – Puppy

3

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

कहा कि, आप विशेष gamedev stackexchange site पर इस सवाल repost करने के लिए चाहते हो सकता है, तो आप "क्षेत्र" से सीधे कुछ जवाब मिल सकता है।

अंत में, अपने परिणामों को वास्तव में कर रहे हैं एक ही माइक्रोसेकंड जो एक आधुनिक बहु सिस्टम पर मृत असंभव है अप करने के लिए - मैं यकीन है कि आप या तो एक बहुत कम संकल्प टाइमर, या आपके समय कोड का उपयोग कर रहा हूँ टूटा हुआ है।

+1

समय के लिए मैं 'बूस्ट :: क्रोनो' का उपयोग कर रहा हूं और बस सिस्टम के समय घटा रहा हूं। चूंकि लेखकों के परिणाम बहुत अलग हैं (30 एमएमएस!) मुझे उम्मीद नहीं थी कि कुछ और सटीक की आवश्यकता है। साथ ही, कंसोल प्रोग्रामिंग के बारे में तथ्य को इंगित करने के लिए धन्यवाद। कभी-कभी मैं भूल जाता हूं कि इन प्राचीन प्रणालियों से संभवतः वे जो कुछ भी कर सकते हैं उन्हें निचोड़ने के लिए उन्हें कितना करना है। – ssell

2

आधुनिक compilers सदस्य के आकार के आधार अलग बाइट सीमाओं पर सदस्यों संरेखित करें। this के नीचे देखें।

आम तौर पर आपको वास्तव में संरचना पैडिंग की परवाह नहीं करनी चाहिए, लेकिन यदि आपके पास ऐसी वस्तु है जिसमें 1000000 उदाहरण हैं या कुछ अंगूठे का नियम बस आपके सदस्यों को सबसे छोटे से छोटे से आदेश देने के लिए है। मैं #pragma निर्देशों के साथ पैडिंग के साथ गड़बड़ करने की अनुशंसा नहीं करता।

1

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

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

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

+1

यदि आप कंपाइलर को अपना काम करने से रोकने के लिए '#pragma pack' का उपयोग करते हैं तो आपको केवल संरेखित करने के लिए डमी चर की आवश्यकता होती है। यदि आप बस 'स्ट्रक्चर फास्टस्ट्रक्चर {__int64 ए लिखते हैं; int बी; चार सी; चार डी; }; 'बिना किसी' # प्रगमा के संकलक सब ठीक से संरेखित करेंगे। –

+0

मैं सामान्य रूप से बात कर रहा हूं। और विशेष रूप से प्राग से बचें, क्योंकि एक नियम उन पर भरोसा नहीं करता है। –

1

कुछ प्लेटफार्मों पर कंपाइलर के पास कोई विकल्प नहीं है: char से बड़े प्रकार की वस्तुओं की अक्सर उचित रूप से गठबंधन पते पर होने की सख्त आवश्यकताएं होती हैं। आमतौर पर संरेखण आवश्यकताओं को CPU के आधार पर समर्थित सबसे बड़े शब्द के आकार तक ऑब्जेक्ट के आकार के समान होते हैं। short आमतौर पर एक पते पर होने की आवश्यकता है, long आम तौर पर 8, double द्वारा विभाजित पते पर 8 से विभाजित पते पर होना चाहिए, और उदा। सिम वैक्टर 16 से विभाजित पते पर

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

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

#include <iostream> 

struct A 
{ 
    char a; 
    double b; 
    char c; 
    double d; 
}; 

struct B 
{ 
    double b; 
    double d; 
    char a; 
    char c; 
}; 

int main() 
{ 
    std::cout << "sizeof(A) = " << sizeof(A) << "\n"; 
    std::cout << "sizeof(B) = " << sizeof(B) << "\n"; 
} 

./alignment.tsk 
sizeof(A) = 32 
sizeof(B) = 24 
1

सी मानक निर्दिष्ट करता है कि संरचना के भीतर फ़ील्ड को बढ़ते पते पर आवंटित किया जाना चाहिए। एक स्ट्रक्चर जिसमें 'int8' प्रकार के आठ चर होते हैं और उस क्रम में संग्रहीत प्रकार 'int64' के सात चर, 64 बाइट्स लेते हैं (मशीन की संरेखण आवश्यकताओं के बावजूद बहुत अधिक)। यदि फ़ील्ड को 'int8', 'int64', 'int8', ... 'int64', 'int8' का आदेश दिया गया था, तो संरचना एक प्लेटफॉर्म पर 120 बाइट्स लेगी जहां 'int64' फ़ील्ड 8-बाइट सीमाओं पर गठबंधन किए गए हैं। खेतों को पुन: व्यवस्थित करने से उन्हें अधिक कसकर पैक किया जा सकेगा। कंपाइलर्स, हालांकि, ऐसा करने के लिए स्पष्ट अनुमति की संरचना के भीतर फ़ील्ड को पुन: व्यवस्थित नहीं करेंगे, क्योंकि ऐसा करने से कार्यक्रम अर्थशास्त्र बदल सकता है।

संबंधित मुद्दे