java8并行字符串统计/流/ map / reduce

时间:2016-09-09 13:23:08

标签: java performance mapreduce java-stream word-count

我在java中有一个很长的字符串,我正在尝试获取该字符串的统计信息。

例如String s =“afafaf”

我希望获得长度为2的所有现有子串的所有计数。 对于上面的这个小例子,这将是:

“af” - 3 “fa” - 2

另一个例子: String s =“hsdjs”

结果: “hs” - 1 “sd” - 1 “dj” - 1 “js” - 1

我所做的和正在做的是用for(int i = 0; i< s.length; i ++)和迭代Map条目来查找字符串。

问题是该死的慢。 我想也许用于并行处理的新Java8函数可能对我有帮助。但不幸的是,我无法运行......也许有人可以帮助我。

当前代码:

    import com.google.common.collect.HashMultiset;


    String inputString = s;
    HashMultiset<String> multi = HashMultiset.create();

    for (int i=0;i <inputString.length()-1;i++) {             
        String aktuellerString = inputString.substring(i, i+2);
        multi.add(aktuellerString);       
    }   

以下是当前的分析: http://fs5.directupload.net/images/160909/naadsfxi.png

google guava库的HashMultiset的add()方法实际上花费了大部分时间。但这是我能找到的最快的收藏品。 (尝试了几个其他优化的库,包括普通的HashMap,Tie,GS Collections,gnu.trove.map.hash.THashMap; import org.apache.commons.collections.FastHashMap,...)。

这就是为什么我认为并行处理可能是加速的唯一方法。

1 个答案:

答案 0 :(得分:3)

更新:正如Marko指出的那样,创建子字符串的成本很高,即使有多个CPU,你也可以通过一个避免创建它们的结构来做得更好。在这种情况下,我们只有两个字符,这些字符可以编码为int值。在这种情况下,我们可以假设ASCII字符。

public static void main(String[] args) throws IOException {
    char[] chars = new char[1000000000];
    Random rand = new Random();
    for (int i = 0; i < chars.length; i++)
        chars[i] = (char) (rand.nextInt(26) + 'a');
    String s = new String(chars);

    long start = System.currentTimeMillis();
    Map<String, Integer> freq = IntStream.range(0, s.length() - 1).parallel()
            .mapToObj(i -> s.substring(i, i + 2))
            .collect(Collectors.groupingBy(w -> w, Collectors.summingInt(e -> 1)));
    long time = System.currentTimeMillis() - start;
    System.out.println("Took " + time + " ms " + freq);
}

打印

Took 8401 ms {aa=1479201, ab=1478451, ac=1479055, ...

但是,如果我们直接使用collect,我们可以使用不会创建任何对象的结构。

public static void main(String[] args) throws IOException {
    char[] chars = new char[1000000000];
    Random rand = new Random();
    for (int i = 0; i < chars.length; i++)
        chars[i] = (char) (rand.nextInt(26) + 'a');
    String s = new String(chars);

    long start = System.currentTimeMillis();
    int[] freqArr = IntStream.range(0, s.length() - 1).parallel()
            .collect(() -> new int[128 * 128],
                    (arr, i) -> arr[s.charAt(i) * 128 + s.charAt(i + 1)]++,
                    (a, b) -> sum(a, b));
    Map<String, Integer> freq = new TreeMap<>();
    for (int i = 0; i < freqArr.length; i++) {
        int c = freqArr[i];
        if (c == 0) continue;
        String key = "" + (char) (i >> 7) + (char) (i & 0x7f);
        freq.put(key, c);
    }
    long time = System.currentTimeMillis() - start;
    System.out.println("Took " + time + " ms " + freq);
}

static int[] sum(int[] a, int[] b) {
    for (int i = 0; i < a.length; i++)
        a[i] += b[i];
    return a;
}

打印以下内容,速度提高约20倍。

Took 404 ms {aa=1479575, ab=1480511, ac=1476255, 

这有很大的不同,因为我们正在处理小字符串

您可以替换

for (int i=0; i < s.length;i++) { something(i) }

IntStream.range(0, s.length()).parallel().forEach(i -> { something(i) })

但更好的解决方案是使用映射......

String s = "afafaffafafafffafaaaf";

Map<String, Long> freq = IntStream.range(0, s.length()-1).parallel() // 1
        .mapToObj(i -> s.substring(i, i + 2)) // 2
        .collect(Collectors.groupingBy(w -> w, Collectors.counting())); //3

System.out.println(freq);
  1. 浏览将有两个字符的所有索引 字符串,并行。
  2. 获取这些索引的两个字符串。
  3. 同时按名称分组,所有出现次数的计数。
  4. 打印

    {ff=3, aa=2, af=8, fa=7}
    

    关于霍尔格关于分组可能更慢的观点,我测试了四个案例。

        long start = System.currentTimeMillis();
        Map<Integer, Long> freq = IntStream.range(0,1000000000)/*.parralel()*/
                .mapToObj(i -> i % 10)
                .collect(Collectors.groupingBy/*Concurrent*/(w -> w, Collectors.counting()));
        long time = System.currentTimeMillis() - start;
        System.out.println("Took " + time+" ms " + freq);
    
    without parallel(), with groupingBy : Took 14156 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
    with parallel(), with groupingBy : Took 5581 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
    without parallel(), with groupingByConcurrent : Took 38218 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
    with parallel(), with groupingByConcurrent : Took 27619 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
    

    使用groupingBy是最好的解决方案,无论是否并行。

    进一步使用Holger的评论,通过使用summingInt,这再次被证明更快。

    long start = System.currentTimeMillis();
    Map<Integer, Integer> freq = IntStream.range(0, 1000000000).parallel()
            .mapToObj(i -> i % 10)
            .collect(Collectors.groupingBy(w -> w, Collectors.summingInt(e -> 1)));
    long time = System.currentTimeMillis() - start;
    System.out.println("Took " + time+" ms " + freq);
    

    打印

    Took 4131 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
    
相关问题