यह देखते हुए कि HdMaps jdk1.6 में और ऊपर बहु ​​= थ्रेडिंग के साथ समस्याओं का कारण बनता है, मुझे अपना कोड कैसे ठीक करना चाहिए


83

मैंने हाल ही में स्टैकओवरफ्लो में एक सवाल उठाया, फिर जवाब मिला। प्रारंभिक प्रश्न यह था कि म्यूटेक्स या कचरा संग्रह के अलावा अन्य तंत्र क्या मेरे बहु-थ्रेडेड जावा प्रोग्राम को धीमा कर सकते हैं?

मुझे अपने आतंक का पता चला कि HashMap को JDK1.6 और JDK1.7 के बीच मोडिफाई किया गया है। अब इसमें कोड का एक ब्लॉक है जो सभी थ्रेड्स को बनाने के लिए HashMaps को सिंक्रनाइज़ करने का कारण बनता है।

JDK1.7.0_10 में कोड की लाइन है

 /**A randomizing value associated with this instance that is applied to hash code of  keys to make hash collisions harder to find.     */
transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);

जिससे कॉलिंग खत्म होती है

 protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
 }    

अन्य JDKs में देखते हुए, मुझे लगता है कि यह JDK1.5.0_22, या JDK1.6.0_26 में मौजूद नहीं है।

मेरे कोड पर प्रभाव बहुत बड़ा है। यह ऐसा करता है कि जब मैं 64 थ्रेड्स पर चलता हूं, तो जब मैं 1 थ्रेड पर चलता हूं तो मुझे कम प्रदर्शन मिलता है। एक JStack से पता चलता है कि अधिकांश धागे रैंडम में उस लूप में घूमने में अपना अधिकांश समय बिता रहे हैं।

इसलिए मुझे लगता है कि कुछ विकल्प हैं:

  • मेरे कोड को फिर से लिखें ताकि मैं हाशपैप का उपयोग न करूं, लेकिन कुछ इसी तरह का उपयोग करें
  • किसी तरह rt.jar के साथ गड़बड़ करें, और उसके अंदर हैशमैप को बदल दें
  • किसी तरह क्लास पथ के साथ मेस करें, इसलिए प्रत्येक थ्रेड को हाशपैप का अपना संस्करण मिलता है

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

सहायता के लिए धन्यवाद


2
आपको कई हैशमैप बनाने के लिए क्या चाहिए? तुम क्या करने की कोशिश कर रहे हो?
12

3
2 टिप्पणियाँ: 1. ConcurrentHashMap का उपयोग नहीं लगता है कि - यह एक विकल्प हो सकता है? 2. कोड का यह टुकड़ा केवल मानचित्र निर्माण पर कहा जाता है। इसका मतलब है कि आप उच्च विवाद के तहत लाखों हैशमैप बना रहे हैं - क्या यह वास्तव में एक यथार्थवादी उत्पादन भार को दर्शाता है?
अस्वच्छता

1
वास्तव में ConcurrentHashMap उस विधि का उपयोग भी करता है (oracle jdk 1.7_10 में) - लेकिन स्पष्ट रूप से OpenJDK 7 नहीं करता है
12

1
@assylias आपको यहां नवीनतम संस्करण की जांच करनी चाहिए । यह एक खेल कोड की एक ऐसी लाइन करता है।
मार्को टोपोलनिक

3
@StaveEscura AtomicLongने अच्छी तरह से काम करने के लिए कम लेखन-विवाद पर दांव लगाया। आपके पास उच्च लेखन-विवाद है, इसलिए आपको नियमित अनन्य लॉकिंग की आवश्यकता है। एक सिंक्रनाइज़ HashMapफैक्ट्री लिखें और आप शायद एक सुधार देखेंगे, जब तक कि आप इन थ्रेड्स में कभी भी नक्शा मैपिंग इंस्टेंटेशन न करें।
मार्को टोपोलनिक

जवाबों:


56

मैं पैच का मूल लेखक हूं जो 7u6, CR # 7118743 में दिखाई दिया: हैश आधारित मैप्स के साथ स्ट्रिंग के लिए वैकल्पिक हैशिंग।

मैं ठीक सामने यह स्वीकार करूंगा कि हैशसाइड का आरंभ एक अड़चन है, लेकिन यह एक ऐसा नहीं है जिसकी हमें उम्मीद है क्योंकि यह केवल हैश मैप उदाहरण के अनुसार एक बार होता है। इस कोड को अड़चन बनाने के लिए आपको प्रति सेकंड सैकड़ों या हजारों हैश मैप बनाने होंगे। यह निश्चित रूप से विशिष्ट नहीं है। क्या वास्तव में आपके आवेदन का ऐसा करने का कोई वैध कारण है? ये हैश मैप कब तक रहते हैं?

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

EDIT 3

टोंटी के लिए एक फिक्स JDK7 अपडेट मर्क्यूरियल रेपो में धकेल दिया गया:

http://hg.openjdk.java.net/jdk7u/jdk7u-dev/jdk/rev/b03bbdef3a88

फिक्स आगामी 7u40 रिलीज़ का हिस्सा होगा और IcedTea 2.4 रिलीज़ में पहले से ही उपलब्ध है।

7u40 के अंतिम परीक्षण बिल्ड के पास यहां उपलब्ध हैं:

https://jdk7.java.net/download.html

प्रतिक्रिया अभी भी स्वागत किया है। इसे http://mail.openjdk.java.net/mailman/listinfo/core-libs-dev पर भेजें ताकि यह सुनिश्चित हो सके कि यह ओपनजेडके देवों द्वारा देखा जाए।


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

2
माइक, एक नियत अवधि के लिए एक सुझाव: थ्रेडलोकल्रैंडिक के अलावा (जिसमें अनुप्रयोगों के साथ अपनी समस्याएं होंगी जो थ्रेड-लोकल स्टोरेज के साथ गड़बड़ होती हैं) यह बहुत आसान और सस्ता नहीं होगा (समय, जोखिम और परीक्षण के संदर्भ में) स्ट्रिप Hashing.Holder.SEED_MAKER के एक सरणी में (कहना) <num cores> यादृच्छिक उदाहरण और कॉलिंग थ्रेड की आईडी का उपयोग% -index में करने के लिए? यह बिना किसी ध्यान देने योग्य साइड इफेक्ट के प्रति थ्रेड विवाद पर तुरंत (हालांकि समाप्त नहीं) राहत देना चाहिए।
होल्गर हॉफस्टैट

10
@mduigou वेब एप्लिकेशन जिनकी उच्च अनुरोध दर है और JSON का उपयोग प्रति सेकंड बड़ी संख्या में HashMaps बनाने जा रहा है, क्योंकि अधिकांश JSON लाइब्रेरीज़ JSON ऑब्जेक्ट्स को डिसेरेट करने के लिए HashMaps या LinkedHashMaps का उपयोग नहीं करती हैं। JSON का उपयोग करने वाले वेब एप्लिकेशन व्यापक हैं, और HashMaps का निर्माण एप्लिकेशन (लेकिन एक लाइब्रेरी एप्लिकेशन उपयोग) द्वारा नियंत्रित नहीं किया जा सकता है, इसलिए मैं कहूंगा कि HashMaps बनाते समय अड़चन न होने के वैध कारण हैं।
sbordet

3
@ मडुइगौ शायद एक साधारण उपादान है बस यह जांचना कि क्या उस पर CAS कॉल करने से पहले OldSeed समान है। यह अनुकूलन (परीक्षण-परीक्षण और सेट या टीटीएएस के रूप में जाना जाता है) बेमानी लग सकता है, लेकिन विवाद के तहत एक महत्वपूर्ण प्रदर्शन प्रभाव पड़ सकता है क्योंकि सीएएस का प्रयास नहीं किया जाता है अगर यह पहले से ही जानता है कि यह विफल हो जाएगा। असफल कैस में कैश लाइन की MESI स्थिति को अमान्य करने के लिए दुर्भाग्यपूर्ण पक्ष-प्रभाव है - सभी पार्टियों को स्मृति से मूल्य को पुनः प्राप्त करने की आवश्यकता होती है। बेशक, होल्गर के बीज की पट्टी एक उत्कृष्ट दीर्घकालिक फिक्स है, लेकिन फिर भी टीटीएएस अनुकूलन का उपयोग किया जाना चाहिए।
जेड वेसले-स्मिथ

5
क्या आपका मतलब "सैकड़ों या हजारों" के बजाय "सैकड़ों हजारों" है? - बड़ा अंतर
माइकल निले

30

यह एक "बग" की तरह दिखता है जिसे आप काम कर सकते हैं। एक संपत्ति है जो नई "वैकल्पिक हैशिंग" सुविधा को निष्क्रिय करती है:

jdk.map.althashing.threshold = -1

हालांकि, वैकल्पिक हैशिंग को अक्षम करना पर्याप्त नहीं है क्योंकि यह यादृच्छिक हैश बीज की पीढ़ी को बंद नहीं करता है (हालांकि यह वास्तव में होना चाहिए)। तो भले ही आप ऑल्ट हैशिंग को बंद कर दें, फिर भी आपके पास हैश मैप इंस्टेंटेशन के दौरान थ्रेड कंटेस्टेंट है।

इसके चारों ओर काम करने का एक विशेष रूप से बुरा तरीका है जबरदस्ती Randomअपने स्वयं के गैर-सिंक्रनाइज़ किए गए संस्करण के साथ हैश बीज उत्पादन के लिए उपयोग किए जाने वाले उदाहरण को बदलना :

// Create an instance of "Random" having no thread synchronization.
Random alwaysOne = new Random() {
    @Override
    protected int next(int bits) {
        return 1;
    }
};

// Get a handle to the static final field sun.misc.Hashing.Holder.SEED_MAKER
Class<?> clazz = Class.forName("sun.misc.Hashing$Holder");
Field field = clazz.getDeclaredField("SEED_MAKER");
field.setAccessible(true);

// Convince Java the field is not final.
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

// Set our custom instance of Random into the field.
field.set(null, alwaysOne);

ऐसा करने के लिए यह (शायद) सुरक्षित क्यों है? क्योंकि ऑल्ट हैशिंग को निष्क्रिय कर दिया गया है, जिससे रैंडम हैश के बीजों को नजरअंदाज किया जा सकता है। तो यह कोई फर्क नहीं पड़ता कि हमारे उदाहरण Randomवास्तव में यादृच्छिक नहीं है। हमेशा की तरह इस तरह के गंदा हैक्स के साथ, कृपया सावधानी के साथ उपयोग करें।

( स्थिर अंतिम फ़ील्ड सेट करने वाले कोड के लिए https://stackoverflow.com/a/3301720/1899721 का धन्यवाद )।

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

FWIW, निम्न बदलाव HashMapथ्रेड विवाद को समाप्त करेगा जब ऑल्ट हैशिंग अक्षम है:

-   transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
+   transient final int hashSeed;

...

         useAltHashing = sun.misc.VM.isBooted() &&
                 (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0;
         init();

एक समान दृष्टिकोण के लिए इस्तेमाल किया जा सकता है ConcurrentHashMap, आदि।


1
धन्यवाद। यह वास्तव में एक हैक है, लेकिन यह समस्या को अस्थायी रूप से हल करता है। यह निश्चित रूप से ऊपर बताई गई सूची में किसी भी तरह से बेहतर समाधान है। लंबे समय तक मुझे वैसे भी तेज हाशप के साथ कुछ करना है। यह मुझे पुराने रिसोर्सबंड कैश के क्लीयर न होने के समाधान की याद दिलाता है। कोड लगभग समान है!
स्टेव एस्कारा

1
FYI करें, इस हैशिंग फ़ीचर का वर्णन यहाँ किया गया है: समीक्षा अनुरोध CR # 7118743: हैश-आधारित मैप्स के साथ स्ट्रिंग के लिए वैकल्पिक हैशिंग । यह murmur3 हैश फ़ंक्शन का कार्यान्वयन है।
कैमबेक

3

वहाँ बहुत सारे ऐप हैं जो बड़े डेटा अनुप्रयोगों में प्रति रिकॉर्ड एक क्षणिक हैशमैप बनाते हैं। उदाहरण के लिए, यह पार्सर और धारावाहिक। किसी भी सिंक्रोनाइज़ेशन को अनसिंक्रनाइज़्ड कलेक्शन क्लासेस में रखना एक वास्तविक गेटा है। मेरी राय में, यह अस्वीकार्य है और एएसएपी तय करने की आवश्यकता है। जो परिवर्तन जाहिरा तौर पर 7u6 में पेश किया गया था, CR # 7118743 को बिना किसी तुल्यकालन या परमाणु ऑपरेशन की आवश्यकता के बिना वापस किया जाना चाहिए या तय किया जाना चाहिए।

किसी तरह यह मुझे StringBuffer और वेक्टर बनाने की भारी गलती की याद दिलाता है और JDK 1.1 / 1.2 में सिंक्रनाइज़ किया गया है। उस गलती के लिए लोगों ने सालों तक मंहगा कर दिया। उस अनुभव को दोहराने की जरूरत नहीं है।


2

आपके उपयोग पैटर्न को उचित मानते हुए, आप अपने स्वयं के संस्करण का उपयोग करना चाहते हैं।

कोड का वह टुकड़ा है, जिससे हैश टकराव बहुत मुश्किल हो जाता है, जिससे हमलावरों को प्रदर्शन की समस्याएं ( विवरण ) बनाने से रोकना पड़ता है - यह मानकर कि इस समस्या को पहले से ही किसी अन्य तरीके से निपटा गया है, मुझे नहीं लगता कि आपको सिंक्रनाइज़ेशन की आवश्यकता होगी। हालाँकि, यदि आप सिंक्रोनाइज़ेशन का उपयोग करते हैं या नहीं करते हैं, तो ऐसा लगता है कि आप अपने स्वयं के संस्करण का उपयोग करना चाहते हैं ताकि आप JDK प्रदान करने के लिए कितना हो सके, इस पर ध्यान न दें।

तो या तो आप आम तौर पर कुछ इसी तरह लिखते हैं और उस ओर इशारा करते हैं, या जेडीके में एक वर्ग को ओवरराइड करते हैं। उत्तरार्द्ध करने के लिए, आप -Xbootclasspath/p:पैरामीटर के साथ बूटस्ट्रैप क्लासपाथ को ओवरराइड कर सकते हैं । हालांकि ऐसा करना "जावा 2 रनटाइम एनवायरनमेंट बाइनरी कोड लाइसेंस" ( स्रोत ) को नियंत्रित करता है


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