2012-03-06 14 views
9

मैं जावा के साथ मेमोरी में एक बड़ा टेक्स्ट कॉर्पस पढ़ने की कोशिश कर रहा हूं। किसी बिंदु पर यह एक दीवार हिट करता है और केवल कचरा अंततः इकट्ठा होता है। मैं जानना चाहता हूं कि किसी को भी बड़े डेटा सेट के साथ प्रस्तुत करने के लिए जावा के जीसी को मारने का अनुभव है।बड़ी जावा सूचियों के साथ खराब प्रदर्शन

मैं यूटीएफ -8 में, एक वाक्य के साथ एक वाक्य के साथ, अंग्रेजी पाठ की 8 जीबी फ़ाइल पढ़ रहा हूं। मैं split() व्हाइटस्पेस पर प्रत्येक पंक्ति चाहता हूं और परिणामस्वरूप स्ट्रिंग एरे को ArrayList<String[]> में आगे प्रोसेसिंग के लिए स्टोर करना चाहता हूं। यहां एक सरल प्रोग्राम है जो समस्या को प्रदर्शित करता है:

/** Load whitespace-delimited tokens from stdin into memory. */ 
public class LoadTokens { 
    private static final int INITIAL_SENTENCES = 66000000; 

    public static void main(String[] args) throws IOException { 
     List<String[]> sentences = new ArrayList<String[]>(INITIAL_SENTENCES); 
     BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); 
     long numTokens = 0; 
     String line; 

     while ((line = stdin.readLine()) != null) { 
      String[] sentence = line.split("\\s+"); 
      if (sentence.length > 0) { 
       sentences.add(sentence); 
       numTokens += sentence.length; 
      } 
     } 
     System.out.println("Read " + sentences.size() + " sentences, " + numTokens + " tokens."); 
    } 
} 

सुंदर कट-एंड-सूखे लगता है, है ना? आप देखेंगे कि मैं अपने ArrayList का आकार भी चुकाता हूं; मेरे पास 66 मिलियन से कम वाक्यों और 1.3 बिलियन टोकन हैं। अब अगर आप अपने Java object sizes संदर्भ और अपने पेंसिल बाहर कोड़ा, तो आप उस के बारे में की आवश्यकता होती है चाहिए मिल जाएगा:

  • 66e6 String[] संदर्भ @ 8 बाइट्स ईए = 0.5 जीबी
  • 66e6 String[] वस्तुओं @ 32 बाइट्स ईए = 2 जीबी
  • 66e6 char[] वस्तुओं @ 32 बाइट्स ईए = 2 जीबी
  • 1.3e9 String संदर्भ @ 8 बाइट्स ईए = 10 जीबी
  • 1.3e9 String रों @ 44 बाइट्स ईए = 53 जीबी
  • +०१२३५१६४१०
  • 8e9 char रों @ 2 बाइट्स ईए = 15 जीबी

83 जीबी। (आपको पता चलेगा कि मुझे वास्तव में 64-बिट ऑब्जेक्ट आकारों का उपयोग करने की आवश्यकता है, क्योंकि Compressed OOPs मुझे 32 जीबी ढेर के साथ मदद नहीं कर सकता है।) हम भाग्यशाली हैं कि 128 जीबी रैम के साथ रेडहाट 6 मशीन है, इसलिए मैं आग लगाना मेरे जावा हॉटस्पॉट (टीएम) 64-बिट सर्वर वीएम (20.4-बी 022, मिश्रित मोड का निर्माण) मेरे जावा एसई 1.6.0_2 9 किट से pv giant-file.txt | java -Xmx96G -Xms96G LoadTokens के साथ बस सुरक्षित होने के लिए, और top देखते समय वापस लातें।

इनपुट के माध्यम से कहीं भी कम से कम 50-60 जीबी आरएसएस पर, समांतर कचरा कलेक्टर 1300% सीपीयू (16 प्रोसेस बॉक्स) तक पहुंच जाता है और प्रगति स्टॉप पढ़ता है। फिर यह कुछ और जीबी चला जाता है, फिर प्रगति भी लंबे समय तक रुक जाती है। यह 96 जीबी भरता है और अभी तक नहीं किया गया है। मैंने इसे डेढ़ घंटे तक जाने दिया है, और यह सिर्फ जीसी कर रहे ~ 90% सिस्टम समय जल रहा है। यह चरम लगता है।

यह सुनिश्चित करने के लिए कि मैं पागल नहीं था, मैंने समकक्ष पायथन (सभी दो पंक्तियों) को मार दिया और यह लगभग 12 मिनट और 70 जीबी आरएसएस में पूरा होने के लिए भाग गया।

तो: क्या मैं कुछ गूंगा कर रहा हूं? (आम तौर पर अक्षम तरीके से चीजों को संग्रहीत किया जा रहा है, जो मैं वास्तव में मदद नहीं कर सकता - और यहां तक ​​कि यदि मेरी डेटा संरचनाएं वसा होती हैं, तब तक जब तक वे फिट होते हैं, जावा को को सख्त नहीं करना चाहिए।) क्या जादू है वास्तव में बड़े ढेर के लिए जीसी सलाह? मैंने -XX:+UseParNewGC को आजमाया और यह और भी बदतर लगता है।

+0

तारों का समर्थन करने वाले 'char []' ऑब्जेक्ट कहां हैं? –

+0

'स्ट्रिंग' ऑब्जेक्ट्स में: 24 बाइट ऑब्जेक्ट हेडर + 8 बाइट 'char []' पॉइंटर + 4 बाइट स्टार्ट, ऑफसेट और हैशकोड, अगर मेरी गणना सही है। –

+0

यह 'char [] '* संदर्भ * है - लेकिन' char []' * ऑब्जेक्ट्स * के बारे में क्या है? एक 'char [] 'सरणी में ऑब्जेक्ट ओवरहेड भी है ... –

उत्तर

3

-XX:+UseConcMarkSweepGC: 78 जीबी और ~ 12 मिनट में खत्म होता है। (लगभग पाइथन के रूप में अच्छा!) हर किसी की मदद के लिए धन्यवाद।

+0

मैं प्रतिक्रिया समय पर जीसी प्रभाव को कम करने के लिए अक्सर बड़े सर्वर के साथ जावा सर्वर के लिए सीएमएस का उपयोग करता हूं। मुझे विश्वास नहीं था कि पॉलिसी बदलने से इस तरह के कार्य में आपके कोड की मदद मिलेगी। मुझे लगता है कि सीएमएस का उपयोग करके ढेर को भागों में विभाजित किया गया है और आपके जेवीएम को एक बड़ा ओल्डजेन मिलता है। –

2

आइडिया 1

इस पर विचार करके प्रारंभ:

while ((line = stdin.readLine()) != null) { 

यह कम से कम इस्तेमाल किया मामला है कि readLine कम से कम 80 वर्णों की एक समर्थन char[] के साथ एक String वापसी होगी किया जाना है।चाहे या नहीं है कि एक समस्या बन जाता है क्या अगली पंक्ति करता है पर निर्भर करता है:

String[] sentence = line.split("\\s+"); 

आप यह निर्धारित करना चाहिए कि क्या तार split द्वारा दिया एक ही समर्थन char[] रहते हैं।

यदि वे ऐसा करते (यह मानते हुए अपने लाइनों अक्सर 80 की तुलना में कम चरित्र) का उपयोग करना चाहिए:

line = new String(line); 

यह एक "सही आकार" स्ट्रिंग सरणी के साथ स्ट्रिंग की प्रतिलिपि का क्लोन पैदा करेगा

वे नहीं कर है, तो आप संभवतः बाहर काम करना चाहिए समान व्यवहार बनाने, लेकिन यह बदल रहा है ताकि वे ही समर्थन char[] का उपयोग करते हैं की किसी तरह से (यानी वे मूल लाइन की सबस्ट्रिंग रहे हैं) - और एक ही क्लोनिंग ऑपरेशन करते हैं निश्चित रूप से प्याज। आप एक अलग char[] प्रति शब्द नहीं चाहते हैं, क्योंकि यह रिक्त स्थान की तुलना में कहीं अधिक स्मृति बर्बाद कर देगा।

आइडिया 2

सूचियों के खराब प्रदर्शन के बारे में आपका शीर्षक वार्ता - बस कम से कम परीक्षण प्रयोजनों के लिए एक String[][] बनाकर लेकिन निश्चित रूप से आप आसानी से समीकरण यहाँ से बाहर सूची ले सकते हैं। ऐसा लगता है कि आप पहले से ही फ़ाइल के आकार को जानते हैं - और यदि आप नहीं करते हैं, तो आप इसे पहले से जांचने के लिए wc के माध्यम से चला सकते हैं। से शुरू करने के लिए बस यह देखने के लिए कि क्या आप उस समस्या से बच सकते हैं

आइडिया 3

अलग शब्द कितने अपने कोष में देखते हैं? क्या आपने HashSet<String> को रखने पर विचार किया है और जब आप इसे पार करते हैं तो प्रत्येक शब्द को जोड़ते हैं? इस तरह आप से कम तारों के साथ समाप्त होने की संभावना है। इस बिंदु पर आप शायद पहले विचार से "एकल बैकिंग char[] प्रति पंक्ति" को त्यागना चाहेंगे - आप प्रत्येक स्ट्रिंग को अपने स्वयं के चार सरणी द्वारा समर्थित करने के लिए चाहते हैं, अन्यथा एक नए शब्द के साथ एक पंक्ति है अभी भी बहुत सारे पात्रों की आवश्यकता होगी। (वैकल्पिक रूप से, असली ठीक करने के लिए, आप कितने "नए शब्दों" एक पंक्ति में देखते हैं देख सकते हैं और प्रत्येक स्ट्रिंग या नहीं क्लोन कर सकते हैं।)

+0

पुन: आइडिया 3, क्या आप 'String.intern()' का उपयोग करने पर विचार कर सकते हैं? –

+0

@ लुइस वासरमैन: संभावित रूप से - लेकिन केवल अगर प्रक्रिया कुछ और नहीं करने जा रही थी। प्रक्रिया-व्यापी एक "प्रदूषण" से बचने के लिए, मैं आमतौर पर अपना खुद का इंटर्निंग सेट करना पसंद करता हूं। (हालांकि इस बात का मजाकिया बात हो सकती है कि इन दिनों कोई समस्या नहीं है। यह सिर्फ * साफ * महसूस करता है।) –

+2

हम्म। वैकल्पिक सुझाव - अमरूद ['Interners.newWeakInterner'] (http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Interners.html#newWeakInterner()) इसे कमजोर संदर्भों के साथ करने के लिए, बस जब आप पूरा कर लें तो आंतरिक तारों को जीसी'd मिल सकता है। –

2

आप निम्न चाल का उपयोग करना चाहिए:

  • सहायता JVM एक ही स्ट्रिंग संदर्भ में एक ही टोकन एकत्र करने के लिए sentences.add(sentence.intern()) पर धन्यवाद। विवरण के लिए String.intern देखें। जहां तक ​​मुझे पता है, इसका प्रभाव जॉन स्कीट के प्रभाव के बारे में भी होना चाहिए, यह चार सरणी में चार सरणी में कटौती करता है।

  • उपयोग experimental HotSpot options कॉम्पैक्ट स्ट्रिंग और चार [] कार्यान्वयन और संबंधित लोगों के लिए:

    -XX:+UseCompressedStrings -XX:+UseStringCache -XX:+OptimizeStringConcat 
    
जैसे स्मृति राशि के साथ

, आप use large pages के लिए अपने सिस्टम और JVM कॉन्फ़िगर करना चाहिए।

अकेले जीसी ट्यूनिंग और 5% से अधिक के साथ प्रदर्शन में सुधार करना वाकई मुश्किल है।आपको पहले प्रोफाइलिंग उपभोग खपत को प्रोफाइलिंग के लिए कम करना चाहिए।

वैसे, मुझे आश्चर्य है कि आपको वास्तव में स्मृति में एक पुस्तक की पूरी सामग्री प्राप्त करने की आवश्यकता है - मुझे नहीं पता कि आपका कोड सभी वाक्यों के साथ क्या करता है लेकिन आपको Lucene indexing tool जैसे शब्दों को गिनने के लिए वैकल्पिक विकल्प पर विचार करना चाहिए या अपने पाठ से किसी भी अन्य जानकारी निकालने।

+0

सुझावों के लिए धन्यवाद। मैंने पिछले ऐप्स में स्ट्रिंग इंटर्निंग की कोशिश की है; यह बहुत सारे डेटा के साथ बहुत धीमा हो जाता है, और इसके लिए एक विशाल परमजिन की आवश्यकता होती है, जो वास्तव में जीसी को भ्रमित करता है। मैंने आपके स्ट्रिंग ऑप्टिमाइज़ेशन विकल्पों को आजमाया है, और यह स्मृति उपयोग को कम कर सकता है, लेकिन यह अभी भी अंततः स्मृति और बर्क भरता है। बड़े पृष्ठ विचार एक अच्छा है; दुर्भाग्य से, आपको वास्तव में पर्याप्त संगत मुक्त स्मृति प्राप्त करने के लिए रीबूट करना होगा (यह क्या है, डॉस?;), और उस स्मृति का उपयोग किसी और चीज़ के लिए नहीं किया जा सकता है। मैं जीसी ट्यूनिंग पर पढ़ रहा हूं, और मुझे लगता है कि मैं समवर्ती कलेक्टर को आजमाने की कोशिश कर रहा हूं। –

0

आपको VisualGC पर आपके ढेर की जगह को विभाजित करने के तरीकों की जांच करनी चाहिए, जो अब VisualVM के लिए एक प्लगइन है।

आपके मामले में, आप शायद ईडन और बचे को कम करने के OldGen बढ़ाने के लिए इतना है कि अपने जीसी एक पूर्ण OldGen इकट्ठा करने में स्पिन नहीं करता है ...

ऐसा करने के लिए चाहते हैं, आप की तरह उन्नत विकल्प का उपयोग करने के :

-XX:NewRatio=2 -XX:SurvivorRatio=8 

इन क्षेत्रों और उनके डिफ़ॉल्ट आवंटन नीति कलेक्टर आप उपयोग पर निर्भर करता है सावधान रहें। तो एक समय में एक पैरामीटर बदलें और फिर से जांचें।

हैं कि सभी स्ट्रिंग सभी JVM Livetime स्मृति में रहना चाहिए, यह उन्हें PermGen में internalising लिए एक अच्छा विचार है -XX:MaxPermSize साथ इतना बड़ा परिभाषित और -Xnoclassgc करने पर कि क्षेत्र धन्यवाद संग्रह से बचने के लिए।

मैं आपको इन डिबगिंग विकल्पों को सक्षम करने की सलाह देता हूं (कोई ओवरहेड अपेक्षित नहीं) और अंत में जीसी लॉग पोस्ट करें ताकि हम आपकी जीसी गतिविधि का विचार कर सकें।

-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:verbosegc.log 
+0

मैं इसे देख रहा था, और मैं इसे आज़मा सकता हूं। सलाह के लिये धन्यवाद। –

संबंधित मुद्दे