हम C ++ में एक उच्च प्रदर्शन महत्वपूर्ण सॉफ़्टवेयर विकसित कर रहे हैं। वहां हमें एक समवर्ती हैश मानचित्र की आवश्यकता है और एक को लागू किया है। इसलिए हमने यह पता लगाने के लिए एक बेंचमार्क लिखा कि हमारे समवर्ती हैश मानचित्र की तुलना में कितना धीमा है std::unordered_map।
लेकिन, std::unordered_mapयह अविश्वसनीय रूप से धीमा प्रतीत होता है ... तो यह हमारा माइक्रो-बेंचमार्क है (समवर्ती नक्शे के लिए हमने एक नया धागा पैदा किया, यह सुनिश्चित करने के लिए कि लॉकिंग को अनुकूलित नहीं किया जाता है और ध्यान दें कि मैंने कभी 0 नहीं डाला क्योंकि मैं भी बेंचमार्क के साथ google::dense_hash_map, जो एक शून्य मान की जरूरत है):
boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> dist(std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
std::vector<uint64_t> vec(SIZE);
for (int i = 0; i < SIZE; ++i) {
uint64_t val = 0;
while (val == 0) {
val = dist(rng);
}
vec[i] = val;
}
std::unordered_map<int, long double> map;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < SIZE; ++i) {
map[vec[i]] = 0.0;
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "inserts: " << elapsed.count() << std::endl;
std::random_shuffle(vec.begin(), vec.end());
begin = std::chrono::high_resolution_clock::now();
long double val;
for (int i = 0; i < SIZE; ++i) {
val = map[vec[i]];
}
end = std::chrono::high_resolution_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "get: " << elapsed.count() << std::endl;
(EDIT: संपूर्ण स्रोत कोड यहां पाया जा सकता है: http://pastebin.com/vPqf7eya )
के लिए परिणाम std::unordered_mapहै:
inserts: 35126
get : 2959
के लिए google::dense_map:
inserts: 3653
get : 816
हमारे हाथ समर्थित समवर्ती नक्शे के लिए (जो लॉकिंग करता है, हालांकि बेंचमार्क सिंगल थ्रेडेड है - लेकिन एक अलग स्पॉन थ्रेड में):
inserts: 5213
get : 2594
यदि मैं बिना किसी समर्थन के बेंचमार्क प्रोग्राम को संकलित करता हूं और सबकुछ मुख्य धागे में चलाता हूं, तो हमें हमारे हाथ समर्थित समवर्ती नक्शे के लिए निम्नलिखित परिणाम मिलते हैं:
inserts: 4441
get : 1180
मैं निम्नलिखित कमांड के साथ संकलित करता हूं:
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
तो विशेष रूप से आवेषण std::unordered_mapअन्य मानचित्रों के लिए अत्यधिक महंगा - 35 सेकंड बनाम 3-5 सेकंड के लिए प्रतीत होता है। इसके अलावा देखने का समय काफी अधिक है।
मेरा सवाल: यह क्यों है? मैंने स्टैकओवरफ्लो पर एक और प्रश्न पढ़ा जहां कोई पूछता है, std::tr1::unordered_mapअपने स्वयं के कार्यान्वयन की तुलना में धीमा क्यों है। उच्चतम श्रेणी के उत्तर में कहा गया है, कि std::tr1::unordered_mapअधिक जटिल इंटरफ़ेस को लागू करने की आवश्यकता है। लेकिन मैं इस तर्क को नहीं देख सकता: हम अपने समवर्ती_पाठ में एक बाल्टी दृष्टिकोण का std::unordered_mapउपयोग करते हैं, एक बाल्टी-दृष्टिकोण का भी उपयोग करते हैं ( google::dense_hash_mapनहीं, लेकिन std::unordered_mapकम से कम हमारे हाथ से समर्थित संगामिति-सुरक्षित संस्करण की तुलना में तेज़ होना चाहिए?)। इसके अलावा मैं इंटरफ़ेस में कुछ भी नहीं देख सकता है जो एक विशेषता को मजबूर करता है जो हैश मानचित्र को बुरी तरह से निष्पादित करता है ...
तो मेरा सवाल: क्या यह सच है कि यह std::unordered_mapबहुत धीमा है? यदि नहीं: क्या गलत है? यदि हाँ: तो उसका कारण क्या है।
और मेरा मुख्य प्रश्न: एक मूल्य std::unordered_mapइतना भयानक महंगा में क्यों डाला जा रहा है (भले ही हम शुरुआत में पर्याप्त स्थान आरक्षित करते हैं, यह बहुत बेहतर प्रदर्शन नहीं करता है - इसलिए पुनर्वसन समस्या नहीं लगती है)?
संपादित करें:
सबसे पहले: हाँ प्रस्तुत बेंचमार्क निर्दोष नहीं है - यह इसलिए है क्योंकि हमने इसके साथ बहुत कुछ खेला है और यह सिर्फ एक हैक है (उदाहरण के लिए, uint64चींटियों को उत्पन्न करने के लिए वितरण व्यवहार में एक अच्छा विचार नहीं होगा, एक लूप में 0 को बाहर करें बेवकूफ की तरह है आदि ...)।
फिलहाल ज्यादातर टिप्पणियां यह बताती हैं, कि मैं इसके लिए पर्याप्त स्थान का प्रचार करके unordered_map को तेज बना सकता हूं। हमारे आवेदन में यह संभव नहीं है: हम एक डेटाबेस प्रबंधन प्रणाली विकसित कर रहे हैं और एक लेनदेन के दौरान कुछ डेटा संग्रहीत करने के लिए हैश मानचित्र की आवश्यकता है (उदाहरण के लिए जानकारी लॉक करना)। तो यह नक्शा 1 से सब कुछ हो सकता है (उपयोगकर्ता सिर्फ एक प्रविष्टि करता है और करता है) अरबों प्रविष्टियों के लिए (यदि पूर्ण तालिका स्कैन होता है)। यहां पर्याप्त स्थान का प्रचार करना असंभव है (और शुरुआत में बहुत कुछ आवंटित करना बहुत अधिक स्मृति का उपभोग करेगा)।
इसके अलावा, मैं माफी माँगता हूँ, कि मैंने अपने प्रश्न को पर्याप्त रूप से स्पष्ट नहीं किया है: मैं वास्तव में unordered_map तेज़ बनाने में दिलचस्पी नहीं लेता (googles dense hash map हमारे लिए ठीक काम करता है), मुझे अभी समझ नहीं आया कि इस विशाल प्रदर्शन अंतर कहाँ आते हैं । यह सिर्फ उपदेश नहीं हो सकता है (यहां तक कि पर्याप्त प्रचारित स्मृति के साथ, घने नक्शा unordered_map की तुलना में तेजी से परिमाण का एक क्रम है। हमारा हाथ समर्थित समवर्ती नक्शा आकार 64 की सरणी से शुरू होता है - इसलिए uneded_map की तुलना में एक छोटा होता है)।
तो इस खराब प्रदर्शन का कारण क्या है std::unordered_map? या अलग तरीके से पूछा गया: क्या कोई std::unordered_mapइंटरफ़ेस के कार्यान्वयन को लिख सकता है जो मानक अनुरूप है और (लगभग) गोगल्स घने हैश मानचित्र के समान तेज़ है? या मानक में ऐसा कुछ है जो कार्यान्वयनकर्ता को इसे लागू करने के लिए एक अक्षम तरीके से लागू करने के लिए लागू करता है?
संपादित करें 2:
प्रोफाइलिंग द्वारा मैं देखता हूं कि पूर्णांक विभाजनों के लिए बहुत समय का उपयोग किया जाता है। std::unordered_mapसरणी आकार के लिए मुख्य संख्याओं का उपयोग करता है, जबकि अन्य कार्यान्वयन दो की शक्तियों का उपयोग करते हैं। std::unordered_mapप्राइम-नंबरों का उपयोग क्यों करता है ? हैश खराब है तो बेहतर प्रदर्शन करने के लिए? अच्छे हैश के लिए यह कोई फर्क नहीं पड़ता imho करता है।
संपादित करें 3:
इसके लिए ये नंबर हैं std::map:
inserts: 16462
get : 16978
Sooooooo: आवेषण में आवेषण की std::mapतुलना में तेज़ क्यों होते हैं std::unordered_map... मेरा मतलब है वाट? std::mapएक बदतर इलाके (पेड़ बनाम सरणी) है, और अधिक आवंटन करने की आवश्यकता है (प्रत्येक टकराव के लिए प्रति + बनाम प्रति ~ 1 डालें) और, सबसे महत्वपूर्ण: एक और एल्गोरिथम जटिलता (हे (लॉगन) बनाम ओ (1)) है!
SIZE।