2011-12-04 10 views
22

की तुलना में बहुत धीमी गति से काम करता है मेरे पास एक perf परीक्षण फ़ंक्शन का .NET और C++ कार्यान्वयन है जो 6838 कुंजी के पूल से स्ट्रिंग कुंजियों का उपयोग करके एक शब्दकोश में 854,750 लुकअप करता है। मैंने इन कार्यों को एक असली ऐप में एक पेर्फ बाधा की जांच करने के लिए लिखा था।स्ट्रिंग कुंजी के साथ unordered_map में सी ++ ~ 1 एम लुक-अप .NET कोड

नेट कार्यान्वयन, एफ # में लिखा है शब्दकोश का उपयोग करता है और .NET 4.0

सी ++ कार्यान्वयन के लिए संकलित किया गया है std :: unordered_map उपयोग करता है और रिलीज मोड में VS2010 के साथ बनाया गया है।

मेरी मशीन पर .NET कोड औसतन 240 एमएस में चलता है और सी ++ कोड 630 एमएस में चलता है। क्या आप कृपया समझने में मेरी मदद कर सकते हैं कि गति में इस विशाल अंतर का क्या कारण हो सकता है?

यदि मैं सी ++ कार्यान्वयन छोटा में महत्वपूर्ण लंबाई बनाता हूं और "key_prefix_" के बजाय "key_" उपसर्ग का उपयोग करता हूं तो यह 140 एमएस में चलाएगा।

एक और चाल मैंने कोशिश की है कि एक कस्टम अपरिवर्तनीय स्ट्रिंग कार्यान्वयन के साथ std :: स्ट्रिंग को प्रतिस्थापित करना है जिसमें स्रोत के लिए एक कॉन्स char * सूचक और एक बार गणना की हैश है। इस स्ट्रिंग का उपयोग सी ++ कार्यान्वयन के प्रदर्शन को 190 एमएस तक कम करने की अनुमति है।

सी ++ कोड:

struct SomeData 
{ 
public: 
    float Value; 
}; 

typedef std::string KeyString; 
typedef std::unordered_map<KeyString, SomeData> DictionaryT; 

const int MaxNumberOfRuns = 125; 
const int MaxNumberOfKeys = 6838; 

DictionaryT dictionary; 
dictionary.rehash(MaxNumberOfKeys); 

auto timer = Stopwatch::StartNew(); 

int lookupCount = 0; 

char keyBuffer[100] = "key_prefix_"; 
size_t keyPrefixLen = std::strlen(keyBuffer); 

/// run MaxNumberOfRuns * MaxNumberOfKeys iterations 
for(int runId = 0; runId < MaxNumberOfRuns; runId++) 
{ 
    for(int keyId = 0; keyId < MaxNumberOfKeys; keyId++) 
    { 
     /// get a new key from the pool of MaxNumberOfKeys keys   
     int randomKeySuffix = (std::rand() % MaxNumberOfKeys); 
     ::itoa(randomKeySuffix, keyBuffer + keyPrefixLen, 10); 

     KeyString key = keyBuffer; 

     /// lookup key in the dictionary   
     auto dataIter = dictionary.find(key); 
     SomeData* data; 

     if(dataIter != dictionary.end()) 
     { 
      /// get existing value   
      data = &dataIter->second; 
     } 
     else 
     { 
      /// add a new value 
      data = &dictionary.insert(dataIter, DictionaryT::value_type(key, SomeData()))->second; 
     } 

     /// update corresponding value in the dictionary 
     data->Value += keyId * runId; 
     lookupCount++; 
    } 
} 

timer.Stop(); 
std::cout << "Time: " << timer.GetElapsedMilleseconds() << " ms" << std::endl; 
std::cout << "Lookup count: " << lookupCount << std::endl; 

प्रिंटों:

समय: 636 एमएस
लुक गिनती: 854750

एफ # कोड

open System 
open System.Diagnostics 
open System.Collections.Generic 

type SomeData = 
    struct 
     val mutable Value : float 
    end 

let dictionary = new Dictionary<string, SomeData>() 
let randomGen = new Random() 

let MaxNumberOfRuns = 125 
let MaxNumberOfKeys = 6838 

let timer = Stopwatch.StartNew() 

let mutable lookupCount = 0 

/// run MaxNumberOfRuns * MaxNumberOfKeys iterations 
for runId in 1 .. MaxNumberOfRuns do 
    for keyId in 1 .. MaxNumberOfKeys do 

     /// get a new key from the pool of MaxNumberOfKeys keys 
     let randomKeySuffix = randomGen.Next(0, MaxNumberOfKeys).ToString()   
     let key = "key_prefix_" + randomKeySuffix 

     /// lookup key in the dictionary 
     let mutable found, someData = dictionary.TryGetValue (key) 
     if not(found) then 
      /// add a new value 
      someData <- new SomeData() 
      dictionary.[key] <- someData 

     /// update corresponding value in the dictionary 
     someData.Value <- someData.Value + float(keyId) * float(runId) 

     lookupCount <- lookupCount + 1 

timer.Stop() 

printfn "Time: %d ms" timer.ElapsedMilliseconds 
printfn "Lookup count: %d" lookupCount 

प्रिंटों:

समय: 245 एमएस
लुक गिनती: 854750

+2

शायद unordered_map end iterator का उपयोग करते हुए अगले तत्व संकेत प्रदर्शन में बाधा डाल रहा है? – GWW

+1

दोनों ठंडे कैश या दोनों गर्म कैश से चलते हैं? – sarnold

+0

कोई भी तत्व अगले संकेत संकेत को हटाने में मदद नहीं करता है :(दोनों कार्य ठंडे कैश के साथ चलते हैं। – evgenyp

उत्तर

43

दृश्य स्टूडियो 2010, std::string के लिए एक performant हैश समारोह का उपयोग करता है बल्कि एक सटीक एक से। असल में, यदि कुंजी स्ट्रिंग 10 वर्णों से बड़ी है, तो हैश फ़ंक्शन हैश के लिए प्रत्येक वर्ण का उपयोग बंद कर देता है, और 1 से अधिक की दूरी पर है।

size_t operator()(const _Kty& _Keyval) const 
    { // hash _Keyval to size_t value by pseudorandomizing transform 
    size_t _Val = 2166136261U; 
    size_t _First = 0; 
    size_t _Last = _Keyval.size(); 
    size_t _Stride = 1 + _Last/10; 

    for(; _First < _Last; _First += _Stride) 
     _Val = 16777619U * _Val^(size_t)_Keyval[_First]; 
    return (_Val); 
    } 
  • size() >= 10 - पहली
  • size() >= 20 के बाद हर दूसरे चरित्र का उपयोग करें - के बाद पहली
  • ...

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

+3

वाह, यह एक महान अवलोकन है। धन्यवाद! – karunski

+5

धन्यवाद !!! मुझे शर्मिंदा है कि मैंने इस सवाल को पोस्ट करने से पहले इसे आजमाया नहीं है। हाँ, यह काम करता है। कस्टम हैश के साथ फ़ंक्शन C++ perf लगभग 180ms है। जो ~ 50 एमएस की तुलना में ~ 50 एमएस है। – evgenyp

+0

दिलचस्प अगर मैं थोड़ी देर तक कुंजी बनाता हूं तो सी ++ और .NET कार्यान्वयन दोनों का प्रदर्शन लगभग समान होगा (~ मेरी मशीन पर 260ms)। यदि मैं सी ++ की तुलना में पूर्णांक कुंजी का उपयोग ~ 35 एमएस और .NET ~ 40 एमएस में चलाऊंगा। शायद मैं असली ऐप से पूरे फ़ंक्शन का .NET कार्यान्वयन तैयार करूंगा जहां मैं इस लूप को चलाता हूं (यह कुछ डेटा मालिश करता है nd कई वैक्टरों के डॉट उत्पाद की गणना करता है) यह देखने के लिए कि इसका प्रदर्शन मूल कार्यान्वयन के साथ तुलनीय है। – evgenyp

5

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

  • हो सकता है कि std :: स्ट्रिंग के लिए हैश समारोह वास्तव में छोटे तार के लिए अनुकूलित है:

    ग के बारे में ++ संस्करण तेजी से एक छोटी कुंजी लंबाई रोशन है, क्योंकि यह बातें की एक जोड़ी को इंगित कर सकता है के साथ किया जा रहा है आपका नोट लंबे तारों की बजाय।

  • शायद लंबी स्ट्रिंग को unordered_set में कॉपी करने में अधिक समय लगता है क्योंकि यह VS 2010 C++ लाइब्रेरी में एक छोटा स्ट्रिंग अनुकूलन अक्षम करता है। इस प्रकार मानचित्र में प्रतिलिपि आवंटन की आवश्यकता है।

कफ के बाहर, यहां अनॉर्डर्ड_मैप के साथ अपने अनुभव के आधार पर कुछ जंगली अवलोकन हैं (हालांकि मैं माइक्रोसॉफ्ट के बूस्ट कार्यान्वयन से अधिक परिचित हूं)।

  • इस उदाहरण में std :: स्ट्रिंग को कुंजी प्रकार के रूप में उपयोग करने का कोई कारण नहीं है, केवल पूर्णांक मान का उपयोग करें। यह संभावित रूप से दोनों सी ++ और एफ # संस्करणों को तेजी से बना देगा।

  • जब आप मानचित्र में मूल्य डालते हैं, तो शायद किसी प्रविष्टि के बाद एक खोज करने के लिए तेज़ नहीं होता है क्योंकि दोनों को कुंजी स्ट्रिंग को फिर से चालू करने की आवश्यकता होगी। बस [] ऑपरेटर का इस्तेमाल किया, जो अपने आप पर एक खोज-या-सम्मिलित ऑपरेशन करता है। मुझे लगता है कि यह इस बात पर निर्भर करेगा कि आप मानचित्र में कितनी बार हिट पाते हैं बनाम एक नया मान जोड़ें।

  • यदि आवंटन बाधा है और आपको स्ट्रिंग कुंजी प्रकार का उपयोग करना होगा तो आपको स्ट्रिंग में साझा पीआरटी को संग्रहीत करने के बजाय स्ट्रिंग में साझा करने के बजाय बेहतर प्रदर्शन हो सकता है, जब आप इसे मानचित्र में डालते हैं।

  • कुंजी प्रकार है कि स्ट्रिंग

  • प्रयास को बढ़ावा देने के कार्यान्वयन के "key_prefix_" भाग पर ध्यान नहीं देता के लिए अपने स्वयं के हैश फंक्शन प्रदान करने का प्रयास करें; शायद यह तेज़ है।

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

+0

धन्यवाद। मैंने पहले ही आपके कुछ सुझावों का प्रयास किया है। मैं इसे प्रोफाइलर के तहत चलाने की कोशिश करूंगा।मैं निराश था क्योंकि मैंने सी ++ में उस विधि को स्पष्ट रूप से उम्मीद की थी कि सी ++ कार्यान्वयन बॉक्स से बाहर .NET को हरा देगा और फिर मैं इसे ट्यून कर दूंगा और इसे तेज़ी से चलाऊंगा। – evgenyp

+1

छोटे-स्ट्रिंग अनुकूलन पर: अपग्रेड करने के बजाय, इसे प्रदर्शन में सुधार करना चाहिए, क्योंकि छोटे-स्ट्रिंग ऑप्टिमाइज़ेशन को सभी वर्णों की प्रतिलिपि बनाने की आवश्यकता है। यदि यह कोई छोटी स्ट्रिंग नहीं है, तो यह केवल पॉइंटर्स को स्वैप कर सकता है, या स्ट्रिंग को ले जा सकता है (VS2010 पहले से ही जागरूक है)। – Xeo

+0

हाँ पूर्णांक कुंजी बहुत तेज़ी से काम करती है। मैंने स्ट्रिंग का इस्तेमाल किया क्योंकि मेरे असली ऐप में यही है। काश मैं वहां पूर्णांक का उपयोग कर सकता हूं, लेकिन अभी यह संभव नहीं है। – evgenyp

0

जब आप शुद्ध डेटा-संरचना कोड से निपट रहे हैं, 2.6 का गति अनुपात अजीब नहीं है। मेरा मतलब देखने के लिए slides on this project पर एक नज़र डालें।

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