हशसेट <पॉइंट> हशसेट <string> से इतना धीमा क्यों है?


165

मैं कुछ पिक्सेल स्थानों को डुप्लिकेट की अनुमति के बिना संग्रहीत करना चाहता था, इसलिए पहली बात यह है कि मन में आता है HashSet<Point>या इसी तरह की कक्षाएं। हालाँकि ऐसा कुछ की तुलना में बहुत धीमा लगता है HashSet<string>

उदाहरण के लिए, यह कोड:

HashSet<Point> points = new HashSet<Point>();
using (Bitmap img = new Bitmap(1000, 1000))
{
    for (int x = 0; x < img.Width; x++)
    {
        for (int y = 0; y < img.Height; y++)
        {
            points.Add(new Point(x, y));
        }
    }
}

लगभग 22.5 सेकंड लगते हैं।

जबकि निम्नलिखित कोड (जो स्पष्ट कारणों के लिए एक अच्छा विकल्प नहीं है) केवल 1.6 सेकंड लेता है:

HashSet<string> points = new HashSet<string>();
using (Bitmap img = new Bitmap(1000, 1000))
{
    for (int x = 0; x < img.Width; x++)
    {
        for (int y = 0; y < img.Height; y++)
        {
            points.Add(x + "," + y);
        }
    }
}

तो, मेरे सवाल हैं:

  • क्या इसका कोई कारण है? मैंने इस उत्तर की जाँच की , लेकिन 22.5 सेकंड उस उत्तर में दर्शाई गई संख्या से अधिक है।
  • क्या डुप्लिकेट के बिना अंक स्टोर करने का एक बेहतर तरीका है?


समवर्ती तारों का उपयोग नहीं करने के लिए ये "स्पष्ट कारण" क्या हैं? अगर मैं अपने खुद के IEqualityComparer को लागू नहीं करना चाहता तो इसे करने का बेहतर तरीका क्या है?
इवान येचेंको

जवाबों:


290

बिंदु संरचना द्वारा प्रेरित दो पूर्ण समस्याएं हैं। जब आप Console.WriteLine(GC.CollectionCount(0));परीक्षण कोड में जोड़ते हैं तो कुछ आप देख सकते हैं । आप देखेंगे कि प्वाइंट टेस्ट के लिए ~ 3720 संग्रह की आवश्यकता है लेकिन स्ट्रिंग टेस्ट को केवल ~ 18 संग्रह की आवश्यकता है। मुक्त करने के लिए नहीं। जब आप एक मूल्य प्रकार इतने सारे संग्रहों को देखते हैं तो आपको "उह-ओह, बहुत अधिक मुक्केबाजी" निष्कर्ष निकालने की आवश्यकता है।

इस मुद्दे पर काम करने के लिए इसकी HashSet<T>जरूरत IEqualityComparer<T>है। चूँकि आपने एक प्रदान नहीं किया था, इसलिए इसे वापस लौटाए जाने की आवश्यकता है EqualityComparer.Default<T>()। यह विधि स्ट्रिंग के लिए एक अच्छा काम कर सकती है, यह IEquatable को लागू करती है। लेकिन प्वाइंट के लिए नहीं, यह एक प्रकार है जो .NET 1.0 से परेशान है और इसे कभी भी जेनरिक प्यार नहीं मिला। सभी यह कर सकते हैं वस्तु विधियों का उपयोग करें।

दूसरा मुद्दा यह है कि Point.GetHashCode () इस परीक्षण में बहुत अधिक टकराव नहीं करता है, इसलिए यह Object.Equals () को बहुत भारी बनाता है। स्ट्रिंग में एक उत्कृष्ट GetHashCode कार्यान्वयन है।

आप एक अच्छी तुलना के साथ HashSet प्रदान करके दोनों समस्याओं को हल कर सकते हैं। इस तरह:

class PointComparer : IEqualityComparer<Point> {
    public bool Equals(Point x, Point y) {
        return x.X == y.X && x.Y == y.Y;
    }

    public int GetHashCode(Point obj) {
        // Perfect hash for practical bitmaps, their width/height is never >= 65536
        return (obj.Y << 16) ^ obj.X;
    }
}

और इसका उपयोग करें:

HashSet<Point> list = new HashSet<Point>(new PointComparer());

और यह अब लगभग 150 गुना तेज है, आसानी से स्ट्रिंग परीक्षण को हरा देता है।


26
GetHashCode विधि कार्यान्वयन प्रदान करने के लिए +1। बस जिज्ञासा के लिए, आप विशेष obj.X << 16 | obj.Y;कार्यान्वयन के साथ कैसे आए ।
आकाश केसी

32
यह उस तरह से प्रेरित था जिस तरह से माउस खिड़कियों में अपनी स्थिति से गुजरता है। यह किसी भी बिटमैप के लिए एक सही हैश है जिसे आप कभी भी प्रदर्शित करना चाहते हैं।
हंस पासेंट

2
जानकर खुशी हुई। किसी भी प्रलेखन या तुम्हारा जैसे हैशकोड लिखने के लिए सबसे अच्छा दिशानिर्देश? वास्तव में, मैं अभी भी जानना चाहूंगा कि क्या उपरोक्त हैशकोड आपके अनुभव या किसी भी दिशानिर्देश के साथ आता है, जिसका आप अनुसरण करते हैं।
आकाश केसी

5
@AkashKC मुझे C # के साथ बहुत अनुभव नहीं है, लेकिन जहां तक ​​मुझे पता है कि पूर्णांक आमतौर पर 32 बिट हैं। इस स्थिति में आप 2 नंबरों का हैश चाहते हैं और एक 16 बिट्स को बाएं-किनारे करके आप सुनिश्चित करते हैं कि प्रत्येक संख्या के "निचले" 16 बिट्स दूसरे को "प्रभावित" नहीं करते हैं |। 3 नंबर के लिए यह 22 और 11 को शिफ्ट के रूप में उपयोग करने के लिए समझ में आता है। 4 संख्याओं के लिए यह 24, 16, 8 होगा। हालांकि, अभी भी टकराव होंगे, लेकिन केवल अगर संख्या बड़ी हो। लेकिन यह महत्वपूर्ण रूप से HashSetकार्यान्वयन पर भी निर्भर करता है । यदि यह "बिट ट्रंकेशन" के साथ खुले-उपयोग का उपयोग करता है (मुझे नहीं लगता कि यह करता है!) बाएं-शिफ्ट दृष्टिकोण खराब हो सकता है।
MSeifert

3
@HansPassant: मुझे आश्चर्य है कि अगर XH के बजाय या GetHashCode में X का उपयोग करना थोड़ा बेहतर हो सकता है - इस स्थिति में कि बिंदु निर्देशांक 16 बिट्स से अधिक हो सकता है (शायद आम प्रदर्शनों पर नहीं, लेकिन निकट भविष्य में)। // XOR आमतौर पर OR की तुलना में हैश कार्यों में बेहतर होता है, क्योंकि यह कम जानकारी खो देता है, उलटा होता है, आदि। जैसे यदि नकारात्मक निर्देशांक की अनुमति है, तो विचार करें कि यदि Y नकारात्मक है तो X योगदान का क्या होता है।
क्रेजी गेलव

85

प्रदर्शन ड्रॉप का मुख्य कारण सभी मुक्केबाजी चल रहा है (जैसा कि पहले ही हंस पसंत के जवाब में बताया गया है )।

इसके अलावा, हैश कोड एल्गोरिथ्म समस्या को और बदतर कर देता है, क्योंकि यह अधिक कॉलिंग का कारण बनता है ताकि Equals(object obj)बॉक्सिंग रूपांतरणों की मात्रा बढ़ सके।

यह भी ध्यान दें कि हैश कोडPoint द्वारा गणना की जाती है x ^ y। यह आपके डेटा रेंज में बहुत कम फैलाव पैदा करता है, और इसलिए बकेट HashSetओवरपॉपलेटेड होते हैं - ऐसा कुछ जिसके साथ नहीं होता है string, जहां हैश का फैलाव बहुत बड़ा होता है।

आप अपनी खुद की Pointसंरचना (तुच्छ) को लागू करके और अपने अपेक्षित डेटा रेंज के लिए बेहतर हैश एल्गोरिथ्म का उपयोग करके इस समस्या को हल कर सकते हैं , जैसे निर्देशांक को स्थानांतरित करके।

(x << 16) ^ y

कुछ अच्छी सलाह के लिए जब हैश कोड की बात आती है, तो विषय पर एरिक लिपर्ट के ब्लॉग पोस्ट को पढ़ें ।


4
बिंदु के संदर्भ स्रोत को देखते हुए GetHashCodeप्रदर्शन: unchecked(x ^ y)इसके लिए stringऔर अधिक जटिल लग रहा है ..
गिल्ड ग्रीन

2
हम्म .. खैर, यह जांचने के लिए कि क्या आपकी धारणा सही है, मैंने सिर्फ HashSet<long>()इसके बजाय का उपयोग करने की कोशिश की , और list.Add(unchecked(x ^ y));हैशसेट में मूल्यों को जोड़ा। यह वास्तव में HashSet<string> (345 एमएस) से भी तेज था । क्या यह आपके द्वारा वर्णित किसी भी तरह से अलग है?
अहमद अब्देलहामेद

4
@AhmedAbdelhameed शायद ऐसा इसलिए है क्योंकि आप अपने हैश सेट से कम सदस्यों को अपने एहसास से जोड़ रहे हैं (फिर से हैश कोड एल्गोरिथ्म के भयानक फैलाव के कारण)। listजब आप इसे पॉप्युलेट कर रहे हों, तो इसकी क्या गिनती है?
inbetween

4
@AhmedAbdelhameed आपका परीक्षण गलत है। आप एक ही लॉन्ग को बार-बार जोड़ रहे हैं, इसलिए वास्तव में आपके द्वारा डाले जा रहे कुछ तत्व हैं। सम्मिलित करते समय point, HashSetवसीयत आंतरिक रूप से GetHashCodeऔर उसी हैशकोड के साथ उन बिंदुओं में से प्रत्येक के लिए, Equalsयह निर्धारित करने के लिए कॉल करेगी कि क्या यह पहले से मौजूद है
Ofir वाइनगार्टन

49
लागू करने की कोई आवश्यकता नहीं है Pointजब आप एक वर्ग बना सकते हैं जो लागू करता है IEqualityComparer<Point>और अन्य चीजों के साथ संगतता रखता है Pointजो गरीबों को नहीं होने का लाभ GetHashCodeऔर बॉक्सिंग की आवश्यकता के साथ काम करते हैं Equals()
जॉन हन्ना
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.