繁体   English   中英

Java中收集符号出现的最快方法是什么

[英]What is the fastest way to gather symbol occurances in java

我的目标是创建一个函数,该函数可以计算行中某些符号(字符)的出现次数。 一个int ID赋予我需要计数的每个字符。 字符集是有限的,我从一开始就知道。 所有行仅包含给定集中的字符。 该函数处理线条的闪烁。 我的探查器始终显示收集统计信息的功能最慢(97%),尽管该程序还有很多其他事情。 首先,我使用了HashMap和如下代码:

    occurances = new HashMap<>();
    for (int symbol : line) {
        Integer amount = 1;
        if (occurances.containsKey(symbol)) {
            amount += occurances.get(symbol);
        }
        occurances.put(symbol, amount);
    }

分析器显示hashMap.put占用97%的处理器使用率

然后我尝试用创建的一次ArrayList替换它:并对其进行了优化(行数总是大于1个字符),但是它仍然很慢。

    int symbol = line[0];
    occurances.set(symbol, 1);

    for (int i = 1; i < length; i++) {
        symbol = line[i];
        occurances.set(symbol, 1 + occurances.get(symbol));
    }

如果有人有更好的主意,如何以更好的性能解决此任务,请您多多帮助。

如此处建议您可以尝试做类似的事情

List<Integer> line = //get line as a list;
Map<Integer, Long> intCount = line.parallelStream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

您可以将char直接转换为int并将其用作索引

for (i=0; ; i++){
    occurences[(int)line[i]]++;
}

不参数化HashMap很有可能导致很多性能问题。

我要做的是创建一个名为IntegerCounter的类。 查看AtomicIntegerhttp://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java )代码并从此处复制所有内容除了使它成为原子的代码。 使用IntegerCounter并增加它的单个实例应该为您节省大量垃圾收集。

使用new Integer(x)进行键查找应该可以进行转义分析以自动进行垃圾回收。

HashMap<Integer, IntegerCounter> occurances;

// since the set of characters are already known, add all of them here with an initial count of 0

for (int i = 0; i < length; i++) {
    occurances.get(new Integer(line[i])).incrementAndGet();
}

在大多数循环迭代的代码中,您将在Map查找条目3次:

1。

occurances.containsKey(symbol)

2。

occurances.get(symbol);

3。

occurances.put(symbol, amount);

这远远超出了需要,您可以简单地使用get return null将此事实改进为2次查找:

Integer currentCount = occurances.get(symbol);
Integer amount = currentCount == null ? 1 : currentCount + 1;
occurances.put(symbol, amount);

此外,通过使用Integer ,需要经常创建新的Integer对象(当它们超过127或用于缓存值的上限时),这会降低性能。

同样,由于您在分析数据之前就知道字符集,因此可以为所有字符插入0 s(或等效值)作为值,这样就无需检查映射中是否已存在映射。

以下代码使用包含int count字段的帮助程序类来存储数据,从而允许在不进行装箱/拆箱转换的情况下增加值。

class Container {
    public int count = 0;
}

int[] symbolSet = ...
Map<Integer, Container> occurances = new HashMap<>();
for (int s : symbolSet) {
    occurances.put(s, new Container());
}

for (int symbol : line) {
    occurances.get(symbol).count++;
}

另外,使用其他数据结构也有帮助。 我想到的是“ 完美散列”或将数据存储在不同于Map的数据结构中。 但是,我建议您使用int[]数组,而不是使用ArrayList ,因为它不需要任何方法调用,并且也不需要对Integer进行装箱/拆箱转换。 在计算频率之后,仍可以将数据转换为更合适的数据结构。

您可以尝试这样的事情:

public class CharCounter {

    final int max;
    final int[] counts;

    public CharCounter(char max) {
        this.max = (int) max;
        counts = new int[this.max + 1];
    }

    public void addCounts(char[] line) {
        for (int symbol : line) {
            counts[symbol]++;
        }
    }

    public Map<Integer, Integer> getCounts() {
        Map<Integer, Integer> countsMap = new HashMap<>();
        for (int symbol = 0; symbol < counts.length; symbol++) {
            int count = counts[symbol];
            if (count > 0) {
                countsMap.put(symbol, count);
            }
        }
        return countsMap;
    }
}

这使用数组保留计数,并将char本身用作数组的索引。
这消除了检查地图是否包含给定键等的需要。它也消除了对字符进行自动装箱的需要。

性能比较显示出大约20倍的加速:

public static final char MIN = 'a';
public static final char MAX = 'f';

private static void count1(Map<Integer, Integer> occurrences, char[] line) {
    for (int symbol : line) {
        Integer amount = 1;
        if (occurrences.containsKey(symbol)) {
            amount += occurrences.get(symbol);
        }
        occurrences.put(symbol, amount);
    }
}

private static void count2(CharCounter counter, char[] line) {
    counter.addCounts(line);
}

public static void main(String[] args) {
    char[] line = new char[1000];
    for (int i = 0; i < line.length; i++) {
        line[i] = (char) ThreadLocalRandom.current().nextInt(MIN, MAX + 1);
    }

    Map<Integer, Integer> occurrences;
    CharCounter counter;

    // warmup
    occurrences = new HashMap<>();
    counter = new CharCounter(MAX);
    System.out.println("Start warmup ...");
    for (int i = 0; i < 500_000; i++) {
        count1(occurrences, line);
        count2(counter, line);
    }
    System.out.println(occurrences);
    System.out.println(counter.getCounts());
    System.out.println("Warmup done.");


    // original method
    occurrences = new HashMap<>();
    System.out.println("Start timing of original method ...");
    long start = System.nanoTime();
    for (int i = 0; i < 500_000; i++) {
        count1(occurrences, line);
    }
    System.out.println(occurrences);
    long duration1 = System.nanoTime() - start;
    System.out.println("End timing of original method.");
    System.out.println("time: " + duration1);


    // alternative method
    counter = new CharCounter(MAX);
    System.out.println("Start timing of alternative method ...");
    start = System.nanoTime();
    for (int i = 0; i < 500_000; i++) {
        count2(counter, line);
    }
    System.out.println(counter.getCounts());
    long duration2 = System.nanoTime() - start;
    System.out.println("End timing of alternative method.");
    System.out.println("time: " + duration2);

    System.out.println("Speedup: " + (double) duration1 / duration2);
}

输出:

Start warmup ...
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
Warmup done.
Start timing of original method ...
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
End timing of original method.
time: 7110894999
Start timing of alternative method ...
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
End timing of alternative method.
time: 388308432
Speedup: 18.31249185698857

另外,如果添加-verbose:gc JVM标志,您会看到原始方法需要进行大量的垃圾收集,而替代方法则不需要任何垃圾收集。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM