我的目标是创建一个函数来计算某些符号(字符)的出现。 一个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));
}
如果有人有更好的想法如何以更好的表现来解决这个任务,那么你的帮助就会非常有用。
答案 0 :(得分:2)
根据建议here,您可以尝试做类似
的事情List<Integer> line = //get line as a list;
Map<Integer, Long> intCount = line.parallelStream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
答案 1 :(得分:1)
您可以将char
直接转换为int
并将其用作索引
for (i=0; ; i++){
occurences[(int)line[i]]++;
}
答案 2 :(得分:1)
很可能没有参数化HashMap会导致很多性能问题。
我要做的是创建一个名为IntegerCounter
的类。查看AtomicInteger
(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java)代码并从那里复制所有内容,除了使其成为Atomic的代码。使用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();
}
答案 3 :(得分:1)
在大多数循环迭代的代码中,您将在Map
中查找条目3次:
1
occurances.containsKey(symbol)
2
occurances.get(symbol);
3
occurances.put(symbol, amount);
这不仅仅是需要,您可以简单地使用get
返回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++;
}
同样使用不同的数据结构也可以提供帮助。我想到的是Perfect Hashing或将数据存储在与Map
不同的数据结构中。但是,我建议使用ArrayList
数组,而不是使用int[]
,因为这不需要任何方法调用,也不需要对Integer
进行装箱/取消装箱转换。在计算频率后,数据仍然可以转换为更合适的数据结构。
答案 4 :(得分:1)
您可以尝试这样的事情:
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标志,您可以看到原始方法需要进行相当多的垃圾收集,而替代方法并不需要任何方法。