अनारक्षित सरणी की तुलना में सॉर्ट किए गए एरे को धीमा करने की प्रक्रिया क्यों है?


233

मेरे पास 500000 बेतरतीब ढंग से उत्पन्न Tuple<long,long,string>वस्तुओं की एक सूची है, जिस पर मैं "खोज" के बीच एक सरल प्रदर्शन कर रहा हूं:

var data = new List<Tuple<long,long,string>>(500000);
...
var cnt = data.Count(t => t.Item1 <= x && t.Item2 >= x);

जब मैं अपना रैंडम सरणी उत्पन्न करता हूं और अपनी खोज को यादृच्छिक रूप से उत्पन्न 100 मानों के लिए चलाता हूं x, तो खोजें लगभग चार सेकंड में पूरी हो जाती हैं। उन महान आश्चर्यों के बारे में जानकर, जिन्हें छांटना खोज करने के लिए करता है , हालाँकि, मैंने अपने 100 खोजों को चलाने से पहले अपने डेटा को क्रमबद्ध करने का निर्णय लिया - पहले बाय Item1बाय Item2और फिर अंत में Item3। मुझे उम्मीद थी कि सॉर्ट किए गए संस्करण को शाखा की भविष्यवाणी के कारण थोड़ा तेज़ प्रदर्शन करना होगा: मेरी सोच यह रही है कि एक बार जब हम उस बिंदु पर पहुंच जाते हैं, जहां शाखा के Item1 == xसभी आगे की जाँच t.Item1 <= x"नो नो" के रूप में शाखा का सही अनुमान लगाती है, जिससे टेल भाग की गति बढ़ जाती है। खोज। मेरे आश्चर्य के लिए, खोजों को एक क्रमबद्ध सरणी पर दो बार लिया गया !

मैंने उस क्रम के चारों ओर स्विच करने का प्रयास किया जिसमें मैंने अपने प्रयोगों को चलाया, और यादृच्छिक संख्या जनरेटर के लिए अलग-अलग बीज का उपयोग किया, लेकिन प्रभाव एक ही रहा है: एक अनसुलझी सरणी में खोज उसी सरणी में खोजों के रूप में लगभग दो बार तेजी से चली, लेकिन सॉर्ट किया गया!

किसी को भी इस अजीब प्रभाव की एक अच्छी व्याख्या है? मेरे परीक्षणों का स्रोत कोड निम्न है; मैं .NET 4.0 का उपयोग कर रहा हूं।


private const int TotalCount = 500000;
private const int TotalQueries = 100;
private static long NextLong(Random r) {
    var data = new byte[8];
    r.NextBytes(data);
    return BitConverter.ToInt64(data, 0);
}
private class TupleComparer : IComparer<Tuple<long,long,string>> {
    public int Compare(Tuple<long,long,string> x, Tuple<long,long,string> y) {
        var res = x.Item1.CompareTo(y.Item1);
        if (res != 0) return res;
        res = x.Item2.CompareTo(y.Item2);
        return (res != 0) ? res : String.CompareOrdinal(x.Item3, y.Item3);
    }
}
static void Test(bool doSort) {
    var data = new List<Tuple<long,long,string>>(TotalCount);
    var random = new Random(1000000007);
    var sw = new Stopwatch();
    sw.Start();
    for (var i = 0 ; i != TotalCount ; i++) {
        var a = NextLong(random);
        var b = NextLong(random);
        if (a > b) {
            var tmp = a;
            a = b;
            b = tmp;
        }
        var s = string.Format("{0}-{1}", a, b);
        data.Add(Tuple.Create(a, b, s));
    }
    sw.Stop();
    if (doSort) {
        data.Sort(new TupleComparer());
    }
    Console.WriteLine("Populated in {0}", sw.Elapsed);
    sw.Reset();
    var total = 0L;
    sw.Start();
    for (var i = 0 ; i != TotalQueries ; i++) {
        var x = NextLong(random);
        var cnt = data.Count(t => t.Item1 <= x && t.Item2 >= x);
        total += cnt;
    }
    sw.Stop();
    Console.WriteLine("Found {0} matches in {1} ({2})", total, sw.Elapsed, doSort ? "Sorted" : "Unsorted");
}
static void Main() {
    Test(false);
    Test(true);
    Test(false);
    Test(true);
}

Populated in 00:00:01.3176257
Found 15614281 matches in 00:00:04.2463478 (Unsorted)
Populated in 00:00:01.3345087
Found 15614281 matches in 00:00:08.5393730 (Sorted)
Populated in 00:00:01.3665681
Found 15614281 matches in 00:00:04.1796578 (Unsorted)
Populated in 00:00:01.3326378
Found 15614281 matches in 00:00:08.6027886 (Sorted)

15
शाखा भविष्यवाणी के कारण: पी
सोनार ग्नुएल

8
@jalf मुझे उम्मीद थी कि छांटे गए संस्करण को शाखा की भविष्यवाणी के कारण थोड़ा तेज़ प्रदर्शन करना होगा। मेरी सोच यह थी कि एक बार हम उस बिंदु पर पहुँच जाएँ जहाँ Item1 == x, आगे की सभी जाँच t.Item1 <= xशाखा को "नो टेक" के रूप में सही ढंग से भविष्यवाणी करेगी, जिससे खोज के टेल भाग में तेजी आएगी। जाहिर है, सोच की वह लाइन कठोर वास्तविकता से गलत साबित हुई है :)
dasblinkenlight

1
@ क्रिसहिन्क्लेयर अच्छा अवलोकन! मैंने अपने उत्तर में एक स्पष्टीकरण जोड़ा है।
usr

39
यह प्रश्न यहां मौजूद प्रश्न का डुप्लिकेट नहीं है। इसे एक के रूप में बंद करने के लिए मतदान न करें।
ThiefMaster

2
@ Sar009 बिलकुल नहीं! दो सवाल दो बहुत अलग परिदृश्यों पर विचार करते हैं, स्वाभाविक रूप से विभिन्न परिणामों के लिए आ रहे हैं।
dasblinkenlight

जवाबों:


269

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

जब आप सूची को क्रमबद्ध कर रहे होते हैं तो आप इसे यादृच्छिक क्रम में रख देते हैं क्योंकि आपकी तरह की चाबियाँ यादृच्छिक रूप से उत्पन्न होती हैं। इसका मतलब यह है कि टुपल सदस्यों के लिए मेमोरी एक्सेस अप्रत्याशित है। सीपीयू मेमोरी को प्रीफ़ैच नहीं कर सकता है और ट्यूपल तक लगभग हर एक्सेस कैश मिस है।

जीसी मेमोरी प्रबंधन के एक विशिष्ट लाभ के लिए यह एक अच्छा उदाहरण है : डेटा संरचनाएं जो एक साथ आवंटित की गई हैं और एक साथ उपयोग की जाती हैं बहुत अच्छी तरह से। उनके पास संदर्भ के महान इलाके हैं

कैश से जुर्माना इस मामले में सहेजे गए शाखा पूर्वानुमान दंड से आगे निकल जाता है ।

struct-Tuple पर स्विच करने का प्रयास करें । यह प्रदर्शन को बहाल करेगा क्योंकि टपल सदस्यों तक पहुंचने के लिए रनवे पर किसी भी सूचक-प्रसार की आवश्यकता नहीं है।

क्रिस सिंक्लेयर ने टिप्पणी में लिखा है कि "टोटलकाउंट लगभग 10,000 या उससे कम के लिए, क्रमबद्ध संस्करण तेजी से प्रदर्शन करता है "। ऐसा इसलिए है क्योंकि एक छोटी सूची पूरी तरह से सीपीयू कैश में फिट होती है । मेमोरी एक्सेस अप्रत्याशित हो सकता है लेकिन लक्ष्य हमेशा कैश में होता है। मेरा मानना ​​है कि अभी भी एक छोटा जुर्माना है क्योंकि कैश से एक लोड भी कुछ चक्र लेता है। लेकिन ऐसा लगता है कि समस्या नहीं है क्योंकि सीपीयू कई बकाया भार को रोक सकता है , जिससे थ्रूपुट बढ़ता है। जब भी सीपीयू मेमोरी के लिए प्रतीक्षा को हिट करता है, तब भी यह निर्देश स्ट्रीम में आगे बढ़ेगा क्योंकि यह कई मेमोरी ऑपरेशन को कतार में खड़ा कर सकता है। इस तकनीक का उपयोग विलंबता को छिपाने के लिए किया जाता है।

इस तरह के व्यवहार से पता चलता है कि आधुनिक सीपीयू पर प्रदर्शन की भविष्यवाणी करना कितना कठिन है। तथ्य यह है कि हम केवल 2x धीमे हैं जब अनुक्रमिक से यादृच्छिक मेमोरी एक्सेस पर जा रहे हैं, तो मुझे बताएं कि डिजिटल विलंबता को छिपाने के लिए कवर के तहत कितना चल रहा है। एक मेमोरी एक्सेस 50-200 चक्रों के लिए सीपीयू को रोक सकती है। यह देखते हुए कि नंबर एक से प्रोग्राम की उम्मीद की जा सकती है कि रैंडम मेमोरी एक्सेस शुरू करते समय 10x धीमा हो जाए।


5
अच्छा कारण है कि आप C / C ++ में जो कुछ भी सीखते हैं वह C # जैसी भाषा में शब्दशः लागू नहीं होता है!
user541686

37
आप उस व्यवहार को मैन्युअल रूप new List<Tuple<long,long,string>>(500000)से उस नई सूची का परीक्षण करने से पहले एक-एक करके सॉर्ट किए गए डेटा की प्रतिलिपि बनाकर पुष्टि कर सकते हैं । इस परिदृश्य में, सॉर्ट किया गया परीक्षण अनसर्टेड के समान तेज़ है, जो इस उत्तर पर तर्क के साथ मेल खाता है।
बोबसन

3
बहुत बढ़िया! आपका बहुत धन्यवाद! मैंने एक समतुल्य Tupleसंरचना बनाई , और कार्यक्रम ने मेरे पूर्वानुमान के तरीके का व्यवहार करना शुरू कर दिया: सॉर्ट किया गया संस्करण थोड़ा तेज था। इसके अलावा, अनसुलझा संस्करण दो बार तेजी से बन गया! तो संख्या के साथ struct2s अनसोल्ड बनाम 1.9s सॉर्ट किए गए हैं।
dasblinkenlight

2
तो क्या हम इससे यह निष्कर्ष निकाल सकते हैं कि कैश-मिस ब्रांच-मिसप्रिंट से ज्यादा दुख देता है? मुझे ऐसा लगता है, और हमेशा ऐसा सोचा है। C ++ में, std::vectorलगभग हमेशा से बेहतर प्रदर्शन करता है std::list
नवाज

3
@ मेहरदाद: नहीं। यह सी ++ के लिए भी सही है। सी ++ में भी, कॉम्पैक्ट डेटा संरचनाएं तेज हैं। कैश-मिस से बचना C ++ में उतना ही महत्वपूर्ण है जितना किसी अन्य भाषा में। std::vectorबनाम std::listएक अच्छा उदाहरण है।
नवाज

4

LINQ नहीं जानता कि आप सूची को क्रमबद्ध करते हैं या नहीं।

चूंकि विधेय पैरामीटर के साथ गणना सभी IEnumerables के लिए विस्तार विधि है, मुझे लगता है कि यह पता नहीं है कि क्या यह कुशल यादृच्छिक पहुंच के साथ संग्रह पर चल रहा है। तो, यह बस हर तत्व की जाँच करता है और Usr ने बताया कि प्रदर्शन कम क्यों हुआ।

सॉर्ट किए गए सरणी (जैसे बाइनरी खोज) के प्रदर्शन लाभ का फायदा उठाने के लिए, आपको थोड़ा और कोडिंग करना होगा।


5
मुझे लगता है कि आप सवाल गलत समझा: निश्चित रूप से मैं उम्मीद कर रहा था कि Countया Whereहोगा "किसी भी तरह" ऊपर विचार है कि अपने डेटा सॉर्ट हो जाता है पर लेने और इसके बदले एक सादे "चेक सब कुछ" खोज के एक द्विआधारी खोज चलाते हैं। बेहतर शाखा भविष्यवाणी (मेरे प्रश्न के अंदर लिंक देखें) के कारण मैं कुछ सुधार की उम्मीद कर रहा था, लेकिन जैसा कि यह पता चला है, संदर्भ ट्रम्प शाखा भविष्यवाणी का स्थानीयता बड़ा समय है।
dasblinkenlight
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.