Java 多线程字符串处理以#线程结束

Java 多线程字符串处理以#线程结束,java,string,parsing,executorservice,Java,String,Parsing,Executorservice,我正在从事一个多线程项目,我们必须将文件中的一些文本解析成一个神奇的对象,对该对象进行一些处理,并聚合输出。旧版本的代码在一个线程中解析文本,并使用Java的ExecutorService在线程池中进行对象处理。我们没有得到我们想要的性能提升,结果是相对于每个对象的处理时间而言,解析所花费的时间比我们想象的要长,所以我尝试将解析转移到工作线程中 这应该是可行的,但实际发生的是,每个对象的时间随着池中线程数的增加而增加。它比线性的差,但没有指数的差 我已经将其缩减为一个小示例(无论如何,在我的机器

我正在从事一个多线程项目,我们必须将文件中的一些文本解析成一个神奇的对象,对该对象进行一些处理,并聚合输出。旧版本的代码在一个线程中解析文本,并使用Java的
ExecutorService
在线程池中进行对象处理。我们没有得到我们想要的性能提升,结果是相对于每个对象的处理时间而言,解析所花费的时间比我们想象的要长,所以我尝试将解析转移到工作线程中

这应该是可行的,但实际发生的是,每个对象的时间随着池中线程数的增加而增加。它比线性的差,但没有指数的差

我已经将其缩减为一个小示例(无论如何,在我的机器上)显示了这种行为。该示例甚至没有创建魔法对象;它只是在做字符串操作。我看不到线程间的依赖关系;我知道
split()。我错过什么了吗

我在一台24核的机器上运行Java7。线路很长,每个线路约1MB。
功能
中可以有几十个项目,而
边缘
中可以有超过100k个项目

样本输入:

1    1    156    24    230    1350    id(foo):id(bar):w(house,pos):w(house,neg)    1->2:1@1.0    16->121:2@1.0,3@0.5
使用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);
}
}

奇怪的是,这不会随着线程数量的增加而爆炸,我也不知道为什么。不过,这是一种功能性的解决方法,因此我将接受它…

在Java 7中,
String.split()
在内部使用
String.subString()
,出于“优化”原因,它不会创建真正的新
字符串,而是指向原始字符串的子部分的空
String
shell

因此,当您将
split()
一个
String
分割成小块时,原来的一个(可能很大)仍在内存中,可能会吃掉所有的堆。我看到您解析大文件,这可能是一个风险(在Java8中已经改变了)


鉴于您的格式是众所周知的,我建议您“手动”解析每一行,而不是使用
String.split()
(regex对性能非常有害),并为子部分创建真正的新行。

String.split实际上是使用正则表达式进行拆分,请参见: 这意味着每次迭代都要编译一大堆正则表达式


最好是为整行编译一次模式,然后在读取时将其应用于每行以解析它们。或者,编写您自己的解析器来查找字符中断,而不是对其进行正则化。

pool.awaitTermination(7,TimeUnit.DAYS)-人们希望这就足够了。在我开始深入研究它之前,有一个简单的想法:您是否尝试过先将整个文件(或其中的一大块)读入内存,然后尝试解析?这只是一种预感,但我会研究
String#split(…)的实现
并查看它在有大量元素时的性能。例如,它可能会假定较少的元素数量,并且必须多次重新分配结果数组。您是否尝试过分析以确定实际速度较慢的原因
BufferedReader
(和扩展名
LineNumberReader
)必须逐个读取输入中的每个字符,直到找到行终止符。如果这是速度减慢的原因,我想知道
Scanner
是否能提供更好的整体性能。您能更改格式吗?如果可能,我会避免完全创建字符串。如果格式只允许基本ASCII字符(从示例中看是这样的),则可以直接处理字节。>在Java 7中,String.split()在内部使用String.subString(),用于“优化”原因并没有创建真正的新字符串——看起来它只对java 7的早期版本有效:?虽然实现细节是隐藏的,但对于分隔符为单个字符(在某些情况下为两个字符)的拆分,Oracle JRE在(至少)Java7通过
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;       
}