[英]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
的类。 查看AtomicInteger
( http://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.