LongAdder vs Integer in Hash map for frequency map
Posted
技术标签:
【中文标题】LongAdder vs Integer in Hash map for frequency map【英文标题】: 【发布时间】:2021-05-01 18:44:43 【问题描述】:我正在使用 HashMap 在单线程环境中构建频率图。键是需要跟踪频率的字符串。
如果我使用 HashMap
LongAdder 会不会因为我可以简单地调用 increment() 而在这个用例中表现得更好?一些初步测试表明,LongAdder 的性能确实稍好一些,但我不知道为什么。
【问题讨论】:
那是多线程环境。除非您遇到并发问题,否则我看不出这将如何改进。 是的,这就是为什么我尝试添加 1 亿个。 LongAdder 的速度始终是 Integer 的两倍。我不知道为什么会这样。 这取决于映射条目的数量与每个条目的平均增量数。装箱与同步开销。但是您不需要支付同步开销。您可以创建自己的对象来保存一个可变的int
字段。或者使用长度为 1 的 int[]
的常见技巧,尽管带有字段的专用对象可能会稍微快一些,因为它不需要范围检查。
【参考方案1】:
测试以确定递增整数类型的相对性能。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Function;
public class LongAdderTest
public static void main(String[] args)
new LongAdderTest().start();
public void start()
int N = 100_000_000;
int warmup = 3;
String[] testNames = "LongAdder", "Long", "Integer", "long",
"int", "Object", "int[]", "long[]" ;
List<Function<Integer, Long>> tests = List.of(
this::longAdderTest, this::longWrapperTest,
this::integerWrapperTest, this::primitiveLongTest,
this::primitiveIntTest, this::objectTest,
this::intArrayTest, this::longArrayTest);
int i = 0;
for (Function<Integer, Long> test : tests)
runTest(test, warmup, N, testNames[i++]);
public void runTest(Function<Integer, Long> test, int warmup,
int iterations, String testName)
// warmup cycle
for (int i = 0; i < warmup; i++)
long v = test.apply(iterations);
if (v != iterations)
System.out
.println("Unexpected result - return = " + v);
long start = System.nanoTime();
long val = test.apply(iterations);
System.out.printf("%-10s : %12f %d%n", testName,
(System.nanoTime() - start) / 1_000_000., val);
public long longAdderTest(int iter)
LongAdder val = new LongAdder();
Map<String, LongAdder> freq = new HashMap<>();
freq.put("A", val);
for (int i = 0; i < iter; i++)
freq.get("A").increment();
return freq.get("A").longValue();
public long longWrapperTest(int iter)
Long val = 0L;
Map<String, Long> freq = new HashMap<>();
freq.put("A", val);
for (int i = 0; i < iter; i++)
freq.computeIfPresent("A", (k, v) -> v + 1);
return freq.get("A");
public long integerWrapperTest(int iter)
Integer val = 0;
Map<String, Integer> freq = new HashMap<>();
freq.put("A", val);
for (int i = 0; i < iter; i++)
freq.computeIfPresent("A", (k, v) -> v + 1);
return freq.get("A");
public long primitiveLongTest(int iter)
Map<String, Long> freq = new HashMap<>();
long val = 0;
freq.put("A", val);
for (int i = 0; i < iter; i++)
freq.computeIfPresent("A", (k, v) -> v + 1);
return freq.get("A");
public long primitiveIntTest(int iter)
Map<String, Integer> freq = new HashMap<>();
int val = 0;
freq.put("A", val);
for (int i = 0; i < iter; i++)
freq.computeIfPresent("A", (k, v) -> v + 1);
return freq.get("A");
public long intArrayTest(int iter)
Map<String, int[]> freq = new HashMap<>();
int[] val = 0 ;
freq.put("A", val);
for (int i = 0; i < iter; i++)
freq.get("A")[0] += 1;
return freq.get("A")[0];
public long longArrayTest(int iter)
Map<String, long[]> freq = new HashMap<>();
long[] val = 0L ;
freq.put("A", val);
for (int i = 0; i < iter; i++)
freq.get("A")[0] += 1;
return freq.get("A")[0];
public long objectTest(int iter)
MyLongIncrement longObject = new MyLongIncrement(0);
Map<String, MyLongIncrement> freq = new HashMap<>();
freq.put("A", longObject);
for (int i = 0; i < iter; i++)
freq.get("A").increment();
return freq.get("A").get();
static class MyLongIncrement
long val;
public MyLongIncrement(long v)
this.val = v;
public long get()
return val;
public void increment()
val += 1l;
示例运行。
LongAdder : 4166.724472 100000000
Long : 2929.021352 100000000
Integer : 5487.358323 100000000
long : 2993.538570 100000000
int : 2505.171838 100000000
Object : 1032.322116 100000000
int[] : 1132.710126 100000000
long[] : 1107.633331 100000000
详情
使用地图使值更接近。伊莫,太接近了,无法做出明确的决定。 但似乎最好使用最后三种类型递增,因为值本身不必在映射中更新。LongAdder
也一样,但同步代码可能是其性能不佳的一个因素(或测试设计者)。但是,可能有很多因素,包括我访问地图值的方法。
我想我已经完成了。希望它能对这个问题有所启发。
【讨论】:
首先,将每个测试放入一个单独的方法中,多次调用这些方法进行预热,然后测量它们的执行时间。在单独的运行时测试它们也是值得的,例如让主程序带一个参数,告诉测试哪个方法。尽管我认为在这种情况下这并不重要。但请注意,OP 在HashMap
值更新的上下文中使用了增量操作,这可能需要比整个增量更多的时间。使用普通的 int
不是一种选择,但长度为 1 的 int[]
可以(或具有可变 int
字段的专用对象)。
@holger 我听取了您的建议并进行了一些更改。测试类应该按原样运行。请注意帖子末尾的异常情况。
在最好的情况下,优化器会检测到您没有使用总和。您可以更改方法以返回总和,然后对其进行处理,例如if(sum != expectedValue) throw …
。但即便如此,优化器可能会识别出您正在执行 n 增量,只需返回值 n 即可将其替换。如果您想更接近 OPs 场景,请使用 Map
并真正增加其值。并将多余的时间测量代码移动到调用者......
现在更接近预期。请注意,您不能将原始值直接存储在 HashMap
中,因此,long
和 Long
完全相同,这解释了关闭数字。这同样适用于int
和Integer
,因此差异表明出现问题。也许,垃圾收集发生了。
@tourniquet_grab 如前所述,int
和 Integer
实际上也是如此。当您比较 int
、long
和 Long
的结果时,它们是合理的。 Integer
的结果是异常值。以上是关于LongAdder vs Integer in Hash map for frequency map的主要内容,如果未能解决你的问题,请参考以下文章
sql integer vs binary_integer vs pls_integer
Juc16_LongAdder引入原理Striped64分散热点思想深度解析LongAdder源码和AtomicLong区别