Программа Java резко замедляется при индексировании corpus для k-граммов

Sahand спросил: 28 марта 2018 в 04:47 в: java

У меня проблема, которая меня озадачивает. Я индексирую corpus (17 000 файлов) текстовых файлов, и при этом я также сохраняю все k-граммы (k-длинные части слов) для каждого слова в HashMap который будет использоваться позже:

public void insert( String token ) {
    //For example, car should result in "^c", "ca", "ar" and "r$" for a 2-gram index        // Check if token has already been seen. if it has, all the
        // k-grams for it have already been added.
        if (term2id.get(token) != null) {
            return;
        }    id2term.put(++lastTermID, token);
    term2id.put(token, lastTermID);        // is word long enough? for example, "a" can be bigrammed and trigrammed but not four-grammed.
        // K must be <= token.length + 2. "ab". K must be <= 4
        List<KGramPostingsEntry> postings = null;
        if(K > token.length() + 2) {
            return;
        }else if(K == token.length() + 2) {
            // insert the one K-gram "^<String token>$" into index
            String kgram = "^"+token+"$";
            postings = index.get(kgram);
            SortedSet<String> kgrams = new TreeSet<String>();
            kgrams.add(kgram);
            term2KGrams.put(token, kgrams);
            if (postings == null) {
                KGramPostingsEntry newEntry = new KGramPostingsEntry(lastTermID);
                ArrayList<KGramPostingsEntry> newList = new ArrayList<KGramPostingsEntry>();
                newList.add(newEntry);
                index.put("^"+token+"$", newList);
            }
            // No need to do anything if the posting already exists, so no else clause. There is only one possible term in this case
            // Return since we are done
            return;
        }else {
            // We get here if there is more than one k-gram in our term
            // insert all k-grams in token into index
            int start = 0;
            int end = start+K;
            //add ^ and $ to token.
            String wrappedToken = "^"+token+"$";
            int noOfKGrams = wrappedToken.length() - end + 1; 
            // get K-Grams
            String kGram;
            int startCurr, endCurr;
            SortedSet<String> kgrams = new TreeSet<String>();            for (int i=0; i<noOfKGrams; i++) {                startCurr = start + i;
                endCurr = end + i;                kGram = wrappedToken.substring(startCurr, endCurr);
                kgrams.add(kGram);                postings = index.get(kGram);
            KGramPostingsEntry newEntry = new KGramPostingsEntry(lastTermID);
                // if this k-gram has been seen before
                if (postings != null) {
                    // Add this token to the existing postingsList.
                    // We can be sure that the list doesn't contain the token
                    // already, else we would previously have terminated the 
                    // execution of this function.
                    int lastTermInPostings = postings.get(postings.size()-1).tokenID;
                    if (lastTermID == lastTermInPostings) {
                        continue;
                    }
                    postings.add(newEntry);
                    index.put(kGram, postings);
                }
                // if this k-gram has not been seen before 
                else {
                    ArrayList<KGramPostingsEntry> newList = new ArrayList<KGramPostingsEntry>();
                    newList.add(newEntry);
                    index.put(kGram, newList);
                }
            }            Clock c = Clock.systemDefaultZone();
            long timestart = c.millis();            System.out.println(token);
            term2KGrams.put(token, kgrams);            long timestop = c.millis();
            System.out.printf("time taken to put: %d\n", timestop-timestart);
            System.out.print("put ");
            System.out.println(kgrams);
            System.out.println();        }}

Вставка в HashMap происходит в строках term2KGrams.put(token, kgrams); (в коде 2 из них сниппет). При индексировании все работает нормально до тех пор, пока события внезапно, в 15 000 индексированных файлов, не испортятся. Все замедляется очень медленно, и программа не заканчивается в разумные сроки, если вообще.

Чтобы попытаться понять эту проблему, я добавил некоторые отпечатки в конце функции. Это результат, который они генерируют:

http://soccer.org
time taken to put: 0
put [.or, //s, /so, ://, ^ht, cce, cer, er., htt, occ, org, p:/, r.o, rg$, soc, tp:, ttp]aysos
time taken to put: 0
put [^ay, ays, os$, sos, yso]http://www.davisayso.org/contacts.htm
time taken to put: 0
put [.da, .ht, .or, //w, /co, /ww, ://, ^ht, act, avi, ays, con, cts, dav, g/c, htm, htt, isa, nta, o.o, ont, org, p:/, rg/, s.h, say, so., tac, tm$, tp:, ts., ttp, vis, w.d, ww., www, yso]playsoccer
time taken to put: 0
put [^pl, ays, cce, cer, er$, lay, occ, pla, soc, yso]

Это выглядит хорошо для меня, укладывание, похоже, не занимает много времени, а k-граммы (в данном случае триграммы) Правильно.

Но можно наблюдать странное поведение в темпе, на котором мой компьютер печатает эту информацию. В начале все печатает на сверхскоростной скорости. Но на 15 000 эта скорость останавливается, и вместо этого мой компьютер начинает печатать несколько строк за раз, что, конечно же, означает, что индексирование других 2000 файлов корпуса займет целую вечность.

Еще одна интересная что я наблюдал, когда делал прерывание клавиатуры (ctrl + c) после того, как он печатался беспорядочно и медленно, как описано некоторое время. Он дал мне это сообщение:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.base/java.lang.StringLatin1.newString(StringLatin1.java:549)sahandzarrinkoub@Sahands-MBP:~/Documents/Programming/Information Retrieval/lab3 2$ sh compile_all.sh
Note: ir/PersistentHashedIndex.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

Означает ли это, что у меня не хватает памяти? Это проблема? Если это так, это удивительно, потому что раньше я сохранял довольно много вещей, таких как HashMap, содержащий идентификаторы документа каждое слово в корпусе, HashMap, содержащем каждое слово, где появляется каждый один k-грамм и т. д.

Пожалуйста, дайте мне знать, что вы думаете и что я могу сделать, чтобы исправить эта проблема.


1 ответ

Есть решение
Turing85 ответил: 28 марта 2018 в 05:09

Чтобы понять это, вы должны сначала понять, что Java не выделяет память динамически (или, по крайней мере, не неопределенно). По умолчанию JVM настроена на запуск с минимальным размером кучи и максимальным размером кучи. Когда максимальный размер кучи будет превышен при некотором выделении, вы получите OutOfMemoryError

Вы можете изменить минимальный и максимальный размер кучи для вашего выполнения с помощью параметров vm -Xms и -Xmx соответственно. Примером выполнения, по крайней мере, с 2, но не более 4 ГБ будет

java -Xms2g -Xmx4g ...

. На странице руководства для java можно найти дополнительные параметры.

Перед изменением кучи памяти, однако, внимательно посмотрите на ваши системные ресурсы, особенно, если ваша система начинает подкачку. Если ваша система меняет местами, больший размер кучи может позволить программе работать дольше, но с одинаково плохой производительностью. Тогда единственное, что возможно, - это оптимизировать вашу программу, чтобы использовать меньше памяти или обновить ОЗУ вашей машины.