क्या gcc std :: unordered_map कार्यान्वयन धीमा है? यदि ऐसा है - क्यों?


100

हम 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)) है!


1
एसटीडी में अधिकांश कंटेनर अपने अनुमानों के साथ बहुत रूढ़िवादी हैं, मैं आपके द्वारा उपयोग किए जा रहे बाल्टी की गिनती (निर्माता में निर्दिष्ट) पर एक नज़र डालूंगा, और इसे आपके लिए बेहतर अनुमान तक बढ़ाऊंगा SIZE
यलीसर

क्या आपने इंटेल टीबीबी से समवर्ती_शाम_मैप की कोशिश की है? threadingbuildingblocks.org/docs/help/reference/…
मैडिसनविस्ट

1
@MadScientist हमने टीबीबी पर विचार किया। समस्या लाइसेंसिंग है: यह एक शोध परियोजना है और हमें अभी तक यकीन नहीं है कि हम इसे कैसे प्रकाशित करेंगे (सबसे निश्चित रूप से खुला स्रोत - लेकिन अगर हम किसी व्यावसायिक उत्पाद में उपयोग की अनुमति देना चाहते हैं, तो GPLv2 बहुत प्रतिबंधक है)। इसके अलावा यह एक और निर्भरता है। लेकिन हो सकता है कि हम बाद के समय में इसका इस्तेमाल करेंगे, अब तक हम इसके बिना भी अच्छे से रह सकते हैं।
मार्कस पिलमैन

1
एक प्रोफाइलर के तहत इसे चलाना, जैसे कि वेलग्रिंड, आनंददायक हो सकता है।
मैक्सिम एगोरुस्किन

1
एक हैश तालिका में स्थानीयता एक पेड़ में स्थानीयता से थोड़ा बेहतर है, कम से कम अगर हैश फ़ंक्शन "यादृच्छिक" है। यह हैश फ़ंक्शन सुनिश्चित करता है कि आप आस-पास के समय पर आस-पास की वस्तुओं का उपयोग कर सकते हैं। आपके पास एकमात्र लाभ यह है कि हैशटेबल सरणी एक सन्निहित ब्लॉक है। यह वैसे भी एक पेड़ के लिए सच हो सकता है, अगर ढेर खंडित नहीं है और आप एक ही बार में पेड़ का निर्माण करते हैं। एक बार आकार कैश से बड़ा होने के बाद, प्रदर्शन में कोई अंतर होने पर स्थानीयता में अंतर कम होगा।
स्टीव 314

जवाबों:


87

मुझे कारण मिला: यह gcc-4.7 की समस्या है !!

साथ जीसीसी-4.7

inserts: 37728
get    : 2985

साथ जीसीसी-4.6

inserts: 2531
get    : 1565

तो std::unordered_mapgcc-4.7 में टूटा हुआ है (या मेरी स्थापना, जो Ubuntu पर gcc-4.7.0 की स्थापना है - और एक अन्य स्थापना जो डेबियन परीक्षण पर 4.7.1 gcc है)।

मैं एक बग रिपोर्ट प्रस्तुत करूंगा .. तब तक: std::unordered_mapgcc 4.7 के साथ प्रयोग न करें !


वहाँ डेल्टा में 4.6 से कुछ है कि कारण होता है?
मार्क कैनलस

30
मेलिंग सूची में पहले से ही एक रिपोर्ट है। चर्चा "फिक्स" को max_load_factorसंभालने की ओर इशारा करती प्रतीत होती है , जिसके कारण प्रदर्शन में अंतर आया।
jxh

इस कीड़े के लिए बुरा समय! मुझे unordered_map के साथ बहुत खराब प्रदर्शन मिल रहा था, लेकिन मुझे खुशी है कि यह रिपोर्ट की गई है और "निश्चित" है।
बो लू

+1 - क्या एक चूसना BBBBBUG .. मुझे आश्चर्य है कि gcc-4.8.2 के साथ क्या होता है
ikh

2
इस बग पर कोई अपडेट? क्या यह अभी भी जीसीसी (5+) के बाद के संस्करणों के लिए मौजूद है?
rph

21

मैं अनुमान लगा रहा हूं कि आपने ठीक से अपना आकार नहीं unordered_mapदिया है, जैसा कि यलीसर ने सुझाव दिया था। जब जंजीरें बहुत लंबी हो जाती हैं unordered_map, तो जी ++ कार्यान्वयन स्वचालित रूप से एक बड़ी हैश तालिका में पुनः आ जाएगा, और यह प्रदर्शन पर एक बड़ा खींच होगा। मैं सही ढंग से याद है, unordered_mapडिफ़ॉल्ट रूप में (छोटी प्रधानमंत्री अधिक बड़ी) 100

मेरे पास chronoअपने सिस्टम पर नहीं था , इसलिए मैंने समय के साथ काम किया times()

template <typename TEST>
void time_test (TEST t, const char *m) {
    struct tms start;
    struct tms finish;
    long ticks_per_second;

    times(&start);
    t();
    times(&finish);
    ticks_per_second = sysconf(_SC_CLK_TCK);
    std::cout << "elapsed: "
              << ((finish.tms_utime - start.tms_utime
                   + finish.tms_stime - start.tms_stime)
                  / (1.0 * ticks_per_second))
              << " " << m << std::endl;
}

मैंने एक SIZEका उपयोग किया 10000000, और मेरे संस्करण के लिए चीजों को थोड़ा बदलना पड़ा boost। यह भी ध्यान दें, मैं मिलान करने के लिए हैश टेबल को पूर्व-आकार देता हूं SIZE/DEPTH, जहां DEPTHहैश टकराव के कारण बाल्टी श्रृंखला की लंबाई का अनुमान है।

संपादित करें: हावर्ड ने मुझे टिप्पणियों में बताया कि अधिकतम लोड कारक unordered_mapहै 1। इसलिए, DEPTHकोड कितनी बार रिहर्सल करेगा , इस पर नियंत्रण करता है।

#define SIZE 10000000
#define DEPTH 3
std::vector<uint64_t> vec(SIZE);
boost::mt19937 rng;
boost::uniform_int<uint64_t> dist(std::numeric_limits<uint64_t>::min(),
                                  std::numeric_limits<uint64_t>::max());
std::unordered_map<int, long double> map(SIZE/DEPTH);

void
test_insert () {
    for (int i = 0; i < SIZE; ++i) {
        map[vec[i]] = 0.0;
    }
}

void
test_get () {
    long double val;
    for (int i = 0; i < SIZE; ++i) {
        val = map[vec[i]];
    }
}

int main () {
    for (int i = 0; i < SIZE; ++i) {
        uint64_t val = 0;
        while (val == 0) {
            val = dist(rng);
        }
        vec[i] = val;
    }
    time_test(test_insert, "inserts");
    std::random_shuffle(vec.begin(), vec.end());
    time_test(test_insert, "get");
}

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

मैंने कोड को संशोधित किया ताकि मैं DEPTHऔर अधिक आसानी से बदल सकूं।

#ifndef DEPTH
#define DEPTH 10000000
#endif

इसलिए, डिफ़ॉल्ट रूप से, हैश तालिका के लिए सबसे खराब आकार चुना जाता है।

elapsed: 7.12 inserts, elapsed: 2.32 get, -DDEPTH=10000000
elapsed: 6.99 inserts, elapsed: 2.58 get, -DDEPTH=1000000
elapsed: 8.94 inserts, elapsed: 2.18 get, -DDEPTH=100000
elapsed: 5.23 inserts, elapsed: 2.41 get, -DDEPTH=10000
elapsed: 5.35 inserts, elapsed: 2.55 get, -DDEPTH=1000
elapsed: 6.29 inserts, elapsed: 2.05 get, -DDEPTH=100
elapsed: 6.76 inserts, elapsed: 2.03 get, -DDEPTH=10
elapsed: 2.86 inserts, elapsed: 2.29 get, -DDEPTH=1

मेरा निष्कर्ष यह है कि किसी भी प्रारंभिक हैश तालिका आकार के लिए बहुत महत्वपूर्ण प्रदर्शन अंतर नहीं है, जो इसे अद्वितीय आवेषण की पूरी अपेक्षित संख्या के बराबर बनाता है। इसके अलावा, मैं परिमाण प्रदर्शन अंतर के क्रम को नहीं देख रहा हूँ जिसे आप देख रहे हैं।


6
std::unordered_map1 का डिफ़ॉल्ट अधिकतम लोड फैक्टर है। इसलिए बाल्टी की प्रारंभिक संख्या को छोड़कर, आपके DEPTH को नजरअंदाज कर दिया जाता है। अगर वांछित आप कर सकते हैं map.max_load_factor(DEPTH)
हावर्ड हिनांट

@ हॉवर्डहिनेंट: उस जानकारी के लिए धन्यवाद। तो DEPTHनजरअंदाज कर दिया जाता है, लेकिन यह अभी भी नियंत्रित करता है कि मानचित्र को कितनी बार बड़े मानचित्र में बदल दिया जाएगा। उत्तर अपडेट किया गया है, और फिर से धन्यवाद
jxh

@ user315052 हाँ मुझे पता है कि मैं शुरुआत में इसे एक साइज़ का आकार देकर बेहतर बना सकता हूं - लेकिन मैं अपने सॉफ़्टवेयर में ऐसा नहीं कर सकता (यह एक शोध परियोजना है - एक डीबीएमएस - और वहां मुझे नहीं पता कि मैं कितना सम्मिलित करूँगा - यह 0 और 1 बिलियन के बीच भिन्न हो सकता है ...)। लेकिन यहां तक ​​कि preallication के साथ यह हमारे नक्शे की तुलना में धीमा है और जिस तरह से googles dense_map की तुलना में धीमी है - मैं अभी भी सोच रहा हूं कि यह क्या है जो बड़ा अंतर बनाता है।
मार्कस पिलमैन

@ मर्कसपिलमैन: मुझे नहीं पता कि मेरे परिणाम आपकी तुलना में कैसे हैं, क्योंकि आपने कभी नहीं बताया कि आप कितने बड़े SIZEकाम कर रहे हैं। मैं कह सकता हूं कि सेट unordered_mapसे दोगुना तेज DEPTHहै 1और ठीक से प्रचारित है।
jxh

1
@ मर्कसपिलमैन: मेरा समय पहले से ही सेकंड में है। मुझे लगा कि आपका समय मिलीसेकंड में था। यदि DEPTHसेट के साथ सम्मिलन सेकंड 1से कम समय ले रहा है 3, तो यह परिमाण धीमा कैसे है?
jxh

3

मैंने 64 बिट / AMD / 4 कोर (2.1GHz) कंप्यूटर का उपयोग करके आपका कोड चलाया है और इसने मुझे निम्नलिखित परिणाम दिए हैं:

MinGW-W64 4.9.2:

Std का उपयोग करना :: unordered_map:

inserts: 9280 
get: 3302

Std :: map का उपयोग करना :

inserts: 23946
get: 24824

कुलपति 2015 सभी अनुकूलन झंडे के साथ मुझे पता है:

Std का उपयोग करना :: unordered_map:

inserts: 7289
get: 1908

Std :: map का उपयोग करना :

inserts: 19222 
get: 19711

मैंने GCC का उपयोग करके कोड का परीक्षण नहीं किया है, लेकिन मुझे लगता है कि यह VC के प्रदर्शन के लिए तुलनीय हो सकता है, इसलिए यदि यह सच है, तो GCC 4.9 std :: unordered_map यह अभी भी टूटा हुआ है।

[संपादित करें]

तो हां, जैसा कि किसी ने टिप्पणियों में कहा, यह सोचने का कोई कारण नहीं है कि जीसीसी 4.9.x का प्रदर्शन वीसी प्रदर्शन के लिए तुलनीय होगा। जब मेरे पास बदलाव होगा तो मैं जीसीसी पर कोड का परीक्षण करूंगा।

मेरा उत्तर सिर्फ अन्य उत्तरों के लिए किसी प्रकार का ज्ञान आधार स्थापित करने के लिए है।


"मैंने जीसीसी का उपयोग करके कोड का परीक्षण नहीं किया है, लेकिन मुझे लगता है कि यह वीसी के प्रदर्शन के लिए तुलनीय हो सकता है।" मूल पोस्ट में पाए गए किसी भी बेंचमार्किंग के बिना पूरी तरह से निराधार दावा। यह "उत्तर" किसी भी अर्थ में प्रश्न का उत्तर नहीं देता है, अकेले "क्यों" प्रश्न का उत्तर दें।
4a11e1

2
"मैंने जीसीसी का उपयोग करते हुए कोड का परीक्षण नहीं किया है" ... यह कैसे है कि आप इसके बारे में इतना कम जानते हुए भी मिनगडब्ल्यू का अधिग्रहण और उपयोग करने में कामयाब रहे? MinGW मूल रूप से GCC का एक नज़दीकी ट्रैकिंग पोर्ट है।
अंडरस्कोर_ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.