2012-01-17 13 views
26

मैं एक ऐसे कन्स्ट्रक्टर को अनुकूलित कर रहा हूं जिसे हमारे ऐप के सबसे निचले लूप में से एक में बुलाया जाता है। प्रश्न में कक्षा लगभग 100 बाइट चौड़ी है, जिसमें int एस, float एस, bool एस, और तुच्छ संरचनाएं शामिल हैं, और इसे तुलनीय रूप से कॉपी करने योग्य होना चाहिए (इसमें एक नॉनट्रिविअल डिफॉल्ट कन्स्ट्रक्टर है, लेकिन कोई विनाशक या वर्चुअल फ़ंक्शन नहीं है)। यह अक्सर इतना बनाया जाता है कि प्रत्येक नैनोसेकंद इस सीटीआर में बिताए गए समय के अतिरिक्त सर्वर हार्डवेयर के लगभग $ 6,000 तक काम करता है जिसे हमें खरीदने की ज़रूरत है।क्या एमसीसी को स्मृति-संरेखित वस्तुओं के लिए कुशल रचनाकार उत्पन्न करने के लिए मजबूर किया जा सकता है?

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

क्या मैं __attribute__ सिम सीमाओं पर स्मृति-गठबंधन होने के लिए ऐसी वस्तुओं के लिए एक कुशल निर्माता को उत्सर्जित करने के लिए जीसीसी प्राप्त कर सकता हूं? या क्या मुझे पुराने स्कूल की तकनीकों का सहारा लेना चाहिए जैसे असेंबली में अपना खुद का मेमोरी प्रारंभकर्ता लिखना?

यह ऑब्जेक्ट केवल स्टैक पर स्थानीय के रूप में बनाया गया है, इसलिए कोई भी नया/मॉलोक ओवरहेड लागू नहीं होता है।

प्रसंग:

इस वर्ग के एक स्थानीय चर के रूप में ढेर पर यह निर्माण, चुनिंदा गैर मूलभूत मूल्यों के साथ कुछ क्षेत्रों लेखन, और फिर इसे एक समारोह के लिए गुजर (संदर्भ द्वारा) द्वारा किया जाता है, जो इसके संदर्भ को दूसरे के पास भेजता है।

struct Trivial { 
    float x,y,z; 
    Trivial() : x(0), y(0), z(0) {}; 
}; 

struct Frobozz 
{ 
    int na,nb,nc,nd; 
    bool ba,bb,bc; 
    char ca,cb,cc; 
    float fa,fb; 
    Trivial va, vb; // in the real class there's several different kinds of these 
    // and so on 
    Frobozz() : na(0), nb(1), nc(-1), nd(0), 
       ba(false), bb(true), bc(false), 
       ca('a'), cb('b'), cc('c'), 
       fa(-1), fb(1.0) // etc 
    {} 
} __attribute__((aligned(16))); 

// a pointer to a func that takes the struct by reference 
typedef int (*FrobozzSink_t)(Frobozz&); 

// example of how a function might construct one of the param objects and send it 
// to a sink. Imagine this is one of thousands of event sources: 
int OversimplifiedExample(int a, float b) 
{ 
    Frobozz params; 
    params.na = a; params.fb = b; // other fields use their default values 
    FrobozzSink_t funcptr = AssumeAConstantTimeOperationHere(); 
    return (*funcptr)(params); 
} 

यहाँ इष्टतम निर्माता हाल में निर्माण उदाहरण में एक स्थिर "टेम्पलेट" उदाहरण से कॉपी करके, आदर्श रूप में एक समय में 16 बाइट्स काम करने के लिए SIMD ऑपरेटरों का उपयोग करके काम करेगा। इसके बजाय GCC OversimplifiedExample() — स्ट्रक्चर बाइट-बाय-बाइट को भरने के लिए तत्काल mov ops की एक श्रृंखला के लिए बिल्कुल गलत काम करता है।

// from objdump -dS 
int OversimplifiedExample(int a, float b) 
{ 
    a42:55     push %ebp 
    a43:89 e5    mov %esp,%ebp 
    a45:53     push %ebx 
    a46:e8 00 00 00 00  call a4b <_Z21OversimplifiedExampleif+0xb> 
    a4b:5b     pop %ebx 
    a4c:81 c3 03 00 00 00 add $0x3,%ebx 
    a52:83 ec 54    sub $0x54,%esp 
    // calling the 'Trivial()' constructors which move zero, word by word... 
    a55:89 45 e0    mov %eax,-0x20(%ebp) 
    a58:89 45 e4    mov %eax,-0x1c(%ebp) 
    a5b:89 45 e8    mov %eax,-0x18(%ebp) 
    a5e:89 45 ec    mov %eax,-0x14(%ebp) 
    a61:89 45 f0    mov %eax,-0x10(%ebp) 
    a64:89 45 f4    mov %eax,-0xc(%ebp) 
    // filling out na/nb/nc/nd.. 
    a67:c7 45 c4 01 00 00 00 movl $0x1,-0x3c(%ebp) 
    a71:c7 45 c8 ff ff ff ff movl $0xffffffff,-0x38(%ebp) 
    a78:89 45 c0    mov %eax,-0x40(%ebp) 
    a7b:c7 45 cc 00 00 00 00 movl $0x0,-0x34(%ebp) 
    a82:8b 45 0c    mov 0xc(%ebp),%eax 
    // doing the bools and chars by moving one immediate byte at a time! 
    a85:c6 45 d0 00   movb $0x0,-0x30(%ebp) 
    a89:c6 45 d1 01   movb $0x1,-0x2f(%ebp) 
    a8d:c6 45 d2 00   movb $0x0,-0x2e(%ebp) 
    a91:c6 45 d3 61   movb $0x61,-0x2d(%ebp) 
    a95:c6 45 d4 62   movb $0x62,-0x2c(%ebp) 
    a99:c6 45 d5 63   movb $0x63,-0x2b(%ebp) 
    // now the floats... 
    a9d:c7 45 d8 00 00 80 bf movl $0xbf800000,-0x28(%ebp) 
    aa4:89 45 dc    mov %eax,-0x24(%ebp) 
    // FrobozzSink_t funcptr = GetFrobozz(); 
    aa7:e8 fc ff ff ff  call aa8 <_Z21OversimplifiedExampleif+0x68> 
    // return (*funcptr)(params); 
    aac:8d 55 c0    lea -0x40(%ebp),%edx 
    aaf:89 14 24    mov %edx,(%esp) 
    ab2:ff d0    call *%eax 
    ab4:83 c4 54    add $0x54,%esp 
    ab7:5b     pop %ebx 
    ab8:c9     leave 
    ab9:c3     ret 
} 

मैं इस वस्तु की एक 'डिफ़ॉल्ट टेम्पलेट' के निर्माण के लिए, और फिर इसे डिफ़ॉल्ट निर्माता में थोक-कॉपी, एक छिपे हुए 'डमी' निर्माता कि साथ प्रवंचना का एक सा करके जीसीसी के लिए प्रोत्साहित करने की कोशिश की आधार आदर्श और फिर डिफ़ॉल्ट होने बस इसे कॉपी:

struct Frobozz 
{ 
    int na,nb,nc,nd; 
    bool ba,bb,bc; 
    char ca,cb,cc; 
    float fa,fb; 
    Trivial va, vb; 
    inline Frobozz(); 
private: 
    // and so on 
    inline Frobozz(int dummy) : na(0), /* etc etc */  {} 
} __attribute__((aligned(16))); 

Frobozz::Frobozz() 
{ 
    const static Frobozz DefaultExemplar(69105); 
    // analogous to copy-on-write idiom 
    *this = DefaultExemplar; 
    // or: 
    // memcpy(this, &DefaultExemplar, sizeof(Frobozz)); 
} 

लेकिन यह कुछ बेमानी ढेर नकल की वजह से उत्पन्न प्रारंभकर्ता सूची के साथ बुनियादी डिफ़ॉल्ट से भी धीमी कोड।

अंत में मैं, *this = DefaultExemplar कदम करने के लिए एक inlined मुक्त समारोह लिख pipelinedMOVDQA SSE2 opcodes कि कुशलतापूर्वक struct नकल जारी करने के लिए स्मृति संरेखण के बारे में संकलक intrinsics और मान्यताओं का उपयोग कर का सहारा लिया। यह मुझे प्रदर्शन की जरूरत है, लेकिन यह icky है। मैंने सोचा कि असेंबली में शुरुआती लिखने के मेरे दिन मेरे पीछे थे, और मैं वास्तव में सिर्फ जीसीसी के अनुकूलक को सही जगह पर सही कोड छोड़ देता हूं।

क्या कोई तरीका है कि मैं अपने कन्स्ट्रक्टर के लिए इष्टतम कोड उत्पन्न करने के लिए जीसीसी प्राप्त कर सकता हूं, कुछ कंपाइलर सेटिंग या अतिरिक्त __attribute__ मुझे याद आया है?

यह उबंटू पर चल रहा जीसीसी 4.4 है।कंपाइलर झंडे में -m32 -march=core2 -O3 -fno-strict-aliasing -fPIC (दूसरों के बीच) शामिल हैं। पोर्टेबिलिटी पर विचार नहीं है, और मैं यहां प्रदर्शन के मानकों-अनुपालन को बलिदान देने के लिए पूरी तरह से तैयार हूं।

समय सीधे rdtsc, जैसे की एन OversimplifiedExample एक पाश() टाइमर संकल्प की वजह से ध्यान और कैश और सांख्यिकीय महत्व और इतने पर के साथ नमूने के बीच कॉल को मापने के साथ समय स्टाम्प काउंटर को पढ़ कर प्रदर्शन किया गया।

मैंने कॉल साइटों की संख्या को जितना संभव हो सके कम करके इसे अनुकूलित किया है, लेकिन मैं अभी भी जानना चाहता हूं कि जीसीसी से बेहतर सीटीआर कैसे प्राप्त करें।

+0

क्या आपने हाल ही में जीसीसी की कोशिश की है, जैसे कि 4.6.2 (या जल्द ही रिलीज होने वाला नवीनतम स्नैपशॉट)? –

+1

क्या आप निर्माता की परिभाषा को छोड़ सकते हैं और इसे पूरी तरह से हाथ में लिख सकते हैं? जोखिम भरा और बनाए रखना मुश्किल है, लेकिन 34 * $ 6000 के लिए यह खुद के लिए भुगतान करेगा मुझे संदेह है कि – Flexo

+1

क्या आपने विभिन्न '-msse' झंडे को जोड़ने का भी प्रयास किया है? मुझे लगता है कि कुछ मामलों में उन्हें एसएसई के लिए जरूरी है। इसके अलावा मेरा सुझाव है कि आप हाल ही में एक जीसीसी प्राप्त करें और इसके मैनपेज को ब्राउज़ करें, इस बारे में सोचें कि क्या हर विकल्प आपकी स्थिति में सुधार कर सकता है और फिर इसे आज़माएं। – PlasmaHH

उत्तर

8

यहां मैं यह कैसे करूंगा। किसी भी निर्माता को घोषित न करें;

const Frobozz DefaultFrobozz = 
    { 
    0, 1, -1, 0,  // int na,nb,nc,nd; 
    false, true, false, // bool ba,bb,bc; 
    'a', 'b', 'c',  // char ca,cb,cc; 
    -1, 1.0    // float fa,fb; 
    } ; 

फिर OversimplifiedExample में: बजाय, एक निश्चित frobozz कि डिफ़ॉल्ट मान घोषित

leal -72(%ebp), %edi 
movl $_DefaultFrobozz, %esi 
movl $16, %ecx 
rep movsl 
:

Frobozz params (DefaultFrobozz) ; 

साथ gcc -O3 (संस्करण 4.5.2), params की initialisation को कम कर देता है

जो 32-बिट वातावरण में उतना ही अच्छा है जितना अच्छा है।

चेतावनी: मैंने 64-बिट जी ++ संस्करण 4.7.0 20110827 (प्रयोगात्मक) के साथ यह कोशिश की, और यह ब्लॉक चाल के बजाय 64-बिट प्रतियों का स्पष्ट अनुक्रम उत्पन्न हुआ। प्रोसेसर rep movsq की अनुमति नहीं देता है, लेकिन मैं 64-बिट लोड और स्टोर्स के अनुक्रम से तेज़ होने के लिए rep movsl की अपेक्षा करता हूं। शायद नहीं। (लेकिन -Os स्विच - स्पेस के लिए अनुकूलित करें - rep movsl निर्देश का उपयोग करता है।) वैसे भी, इसे आज़माएं और हमें बताएं कि क्या होता है।

जोड़ने के लिए संपादित: मैं प्रोसेसर के बारे में गलत था rep movsq की अनुमति नहीं दे रहा था। इंटेल के दस्तावेज में कहा गया है "एमओवीएस, एमओवीएसबी, एमओवीएसडब्ल्यू, और एमओवीएसडी निर्देश आरईपी उपसर्ग से पहले हो सकते हैं", लेकिन ऐसा लगता है कि यह सिर्फ एक दस्तावेज गड़बड़ है। किसी भी मामले में, यदि मैं Frobozz काफी बड़ा करता हूं, तो 64-बिट कंपाइलर rep movsq निर्देश उत्पन्न करता है; तो शायद यह जानता है कि यह क्या कर रहा है।

+0

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

+3

"लेकिन मैं उम्मीद करता हूं कि 64-बिट लोड और स्टोर्स के अनुक्रम की तुलना में प्रतिनिधि movsd तेज हो "वहां सीमा है जहां 'आरईपी MOVS' निर्देश आम तौर पर धीमे हो जाएंगे। भी, 'आरईपी एमओवीएस' के लिए 3 स्पष्ट रजिस्टरों 'ईसीएक्स',' ईएसआई' और 'ईडीआई' की आवश्यकता होती है, जो प्रतियां ब्लॉक करने के लिए अपील के रूप में अत्यधिक रजिस्ट्रार शफल/स्पिलिंग का कारण बन सकती हैं। – Necrolis

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

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