Я работаю над многопоточным проектом, где мы должны разбирать какой-либо текст из файла в волшебный объект, выполнять некоторую обработку объекта и агрегировать вывод. Старая версия кода анализировала текст в одном потоке и обрабатывала объект в пуле потоков с использованием 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 т ...
'pool.awaitTermination (7, TimeUnit.DAYS);' - Можно надеяться, что этого достаточно. –
Прежде чем я начну углубляться в нее, вы пытаетесь сначала прочитать весь файл (или большой кусок его) в память, а затем попробовать разбор? – biziclop
Просто догадка, но я бы рассмотрел реализацию 'String # split (...)' и посмотрел, как он работает, когда имеется большое количество элементов. Например, он может принимать меньшее количество элементов и должен повторно назначить массив результатов несколько раз. –