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,每个增量都需要一个新的 Integer。

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 中,因此,longLong 完全相同,这解释了关闭数字。这同样适用于intInteger,因此差异表明出现问题。也许,垃圾收集发生了。 @tourniquet_grab 如前所述,intInteger 实际上也是如此。当您比较 intlongLong 的结果时,它们是合理的。 Integer 的结果是异常值。

以上是关于LongAdder vs Integer in Hash map for frequency map的主要内容,如果未能解决你的问题,请参考以下文章

传递大小作为参数VS在Fortran过程中假定形状

sql integer vs binary_integer vs pls_integer

03_LongAdder 源码分析

Juc16_LongAdder引入原理Striped64分散热点思想深度解析LongAdder源码和AtomicLong区别

Integer VS AtomicInteger VS MutableInteger

死磕 java并发包之LongAdder源码分析