2015-04-14 2 views
4

Я работаю над многопоточным проектом, где мы должны разбирать какой-либо текст из файла в волшебный объект, выполнять некоторую обработку объекта и агрегировать вывод. Старая версия кода анализировала текст в одном потоке и обрабатывала объект в пуле потоков с использованием Java ExecutorService. Мы не получали повышение производительности, которого мы хотели, и оказалось, что разбор занимает больше времени, чем мы думали относительно времени обработки для каждого объекта, поэтому я попытался переместить разбор в рабочие потоки.Многопоточная обработка строк взрывается #threads

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

Я уменьшил это до небольшого примера, который (на моей машине так или иначе) показывает поведение. В примере не создается даже волшебный объект; он просто выполняет строковые манипуляции. Я не вижу взаимосвязей между потоками; Я знаю, что split() не очень эффективен, но я не могу себе представить, почему это было бы в постели в многопоточном контексте. Я что-то пропустил?

Я работаю на Java 7 на 24-ядерном компьютере. Линии длинны, ~ 1 МБ каждый. В features могут быть десятки предметов и 100 000 штук в edges.

вход

Пример:

1 1 156 24 230 1350 id(foo):id(bar):w(house,pos):w(house,neg) 1->2:[email protected] 16->121:[email protected],[email protected] 

Пример командной строки для работы с 16 рабочих потоков:

$ java -Xmx10G Foo 16 myfile.txt 

Пример кода:

public class Foo implements Runnable { 
String line; 
int id; 
public Foo(String line, int id) { 
    this.line = line; 
    this.id = id; 
} 
public void run() { 
    System.out.println(System.currentTimeMillis()+" Job start "+this.id); 
    // line format: tab delimited                 
    // x[4] 
    // graph[2] 
    // features[m]  <-- ':' delimited            
    // edges[n] 
    String[] x = this.line.split("\t",5); 
    String[] graph = x[4].split("\t",4); 
    String[] features = graph[2].split(":"); 
    String[] edges = graph[3].split("\t"); 
    for (String e : edges) { 
     String[] ee = e.split(":",2); 
     ee[0].split("->",2); 
     for (String f : ee[1].split(",")) { 
      f.split("@",2); 
     } 
    }                  
    System.out.println(System.currentTimeMillis()+" Job done "+this.id); 
} 
public static void main(String[] args) throws IOException,InterruptedException { 
    System.err.println("Reading from "+args[1]+" in "+args[0]+" threads..."); 
    LineNumberReader reader = new LineNumberReader(new FileReader(args[1])); 
    ExecutorService pool = Executors.newFixedThreadPool(Integer.parseInt(args[0])); 
    for(String line; (line=reader.readLine()) != null;) { 
     pool.submit(new Foo(line, reader.getLineNumber())); 
    } 
    pool.shutdown(); 
    pool.awaitTermination(7,TimeUnit.DAYS); 
} 
} 

Обновления:

  • Чтение всего файла в память сначала не имеет эффекта. Чтобы быть более конкретным, я прочитал весь файл, добавив каждую строку в ArrayList<String>. Затем я перечислил список, чтобы создать задания для пула. Это маловероятно, что гипотеза подстроки-съедобная кучка, не так ли?
  • Компиляция одной копии шаблона разделителя, которая будет использоваться всеми рабочими потоками, не влияет. :(

Разрешение:

Я преобразовал код разбора использовать пользовательские расщепления рутину, основанную на indexOf(), например, так:

private String[] split(String string, char delim) { 
    if (string.length() == 0) return new String[0]; 
    int nitems=1; 
    for (int i=0; i<string.length(); i++) { 
     if (string.charAt(i) == delim) nitems++; 
    } 
    String[] items = new String[nitems]; 
    int last=0; 
    for (int next=last,i=0; i<items.length && next!=-1; last=next+1,i++) { 
     next=string.indexOf(delim,last); 
     items[i]=next<0?string.substring(last):string.substring(last,next); 
    } 
    return items;  
} 

Как ни странно это делает не взорвать, так как число потоков увеличивается, и я понятия не имею, почему. Это функциональное обходное решение, так что я буду жить с i т ...

+1

'pool.awaitTermination (7, TimeUnit.DAYS);' - Можно надеяться, что этого достаточно. –

+0

Прежде чем я начну углубляться в нее, вы пытаетесь сначала прочитать весь файл (или большой кусок его) в память, а затем попробовать разбор? – biziclop

+0

Просто догадка, но я бы рассмотрел реализацию 'String # split (...)' и посмотрел, как он работает, когда имеется большое количество элементов. Например, он может принимать меньшее количество элементов и должен повторно назначить массив результатов несколько раз. –

ответ

1

В Java 7, String.split() использует String.subString() внутренне, что для «оптимизации» причинам не создает реального нового Strings, но пустой String снаряды, которые указывают на подразделов оригинала.

Итак, когда вы набрали split() a String на мелкие кусочки, оригинальная (может быть, огромная) все еще находится в памяти и может в конечном итоге съесть всю вашу кучу. Я вижу, что вы разбираете большие файлы, это может быть риском (это было изменено в Java 8).

Учитывая, что ваш формат хорошо известен, я бы рекомендовал разбор каждой строки «вручную», а использование String.split() (в любом случае, regex действительно плохо для производительности) и создания реальных новых для подчастей.

+1

Я бы не стал полностью создавать строки, если это возможно. Если формат поддерживает только базовые символы ASCII (и из этого примера выглядит так), вы можете напрямую работать с байтами. – biziclop

+2

> В Java 7 String.split() использует String.subString() внутри, что по причинам «оптимизации» не создает настоящие новые строки - похоже, что оно действительно только для ранних версий java 7: http: // java-performance.info/changes-to-string-java-1-7-0_06/? – dbf

1

String.split на самом деле использует регулярные выражения для разделения, см.: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#String.split%28java.lang.String%2Cint%29 Это означает, что вы собираете целую кучу регулярных выражений на каждой итерации.

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

+1

Хотя детали реализации скрыты, для разделов, где разделитель является единственным символом (и в некоторых случаях с двумя символами), Oracle JRE в (по крайней мере) Java 7 внутренне реализует это с помощью 'indexOf' и итерации, а не регулярного выражения , –

Смежные вопросы