简体   繁体   English

为什么Collections.synchronizedSet(HashSet)比HashSet对addAll,retainAll和contains更快?

[英]Why is Collections.synchronizedSet(HashSet) faster than HashSet for addAll, retainAll, and contains?

I ran a test to find the best concurrent Set implementation for my program, with a non-synchronized HashSet as a control, and ran into an interesting result: the addAll , retainAll , and contains operations for a Collections.synchronizedSet(HashSet) appear to be faster than those of a regular HashSet . 我运行了一个测试来找到我的程序的最佳并发Set实现,使用非同步的HashSet作为控件,并遇到了一个有趣的结果: addAllretainAllcontains Collections.synchronizedSet(HashSet)似乎比常规HashSet快。 My understanding is that a SynchronizedSet(HashSet) should never be faster than a HashSet because it consists of a HashSet with synchronization locks. 我的理解是SynchronizedSet(HashSet)永远不应该比HashSet更快,因为它由带有同步锁的HashSet组成。 I've run the test quite a few times now, with similar results. 我现在已经进行了几次测试,结果相似。 Am I doing something wrong? 难道我做错了什么?

Relevant results: 相关结果:

Testing set: HashSet
Add: 17.467758 ms
Retain: 28.865039 ms
Contains: 22.18998 ms
Total: 68.522777 ms
--
Testing set: SynchronizedSet
Add: 17.54269 ms
Retain: 20.173502 ms
Contains: 19.618188 ms
Total: 57.33438 ms

Relevant code: 相关代码:

public class SetPerformance {
    static Set<Long> source1 = new HashSet<>();
    static Set<Long> source2 = new HashSet<>();
    static Random rand = new Random();
    public static void main(String[] args) {
        Set<Long> control = new HashSet<>();
        Set<Long> synch = Collections.synchronizedSet(new HashSet<Long>());

        //populate sets to draw values from
        System.out.println("Populating source");
        for(int i = 0; i < 100000; i++) {
            source1.add(rand.nextLong());
            source2.add(rand.nextLong());
        }

        //populate sets with initial values
        System.out.println("Populating test sets");
        control.addAll(source1);
        synch.addAll(source1);

        testSet(control);
        testSet(synch);
    }

    public static void testSet(Set<Long> set) {
        System.out.println("--\nTesting set: " + set.getClass().getSimpleName());
        long start = System.nanoTime();
        set.addAll(source1);
        long add = System.nanoTime();
        set.retainAll(source1);
        long retain = System.nanoTime();
        boolean test;
        for(int i = 0; i < 100000; i++) {
            test = set.contains(rand.nextLong());
        }
        long contains = System.nanoTime();
        System.out.println("Add: " + (add - start) / 1000000.0 + " ms");
        System.out.println("Retain: " + (retain - add) / 1000000.0 + " ms");
        System.out.println("Contains: " + (contains - retain) / 1000000.0 + " ms");
        System.out.println("Total: " + (contains - start) / 1000000.0 + " ms");
    }
}

You aren't warming up the JVM. 你没有为JVM做准备。

Note that you run the HashSet test first. 请注意,您首先运行HashSet测试。

  1. I changed your program slightly to run the test in a loop 5 times. 我稍微改变了你的程序,在循环中运行测试5次。 SynchronizedSet was faster, on my machine, in only the first test. SynchronizedSet增快,我的机器上, 在第一次测试。
  2. Then, I tried reversing the order of the two tests, and only running the test once. 然后,我尝试颠倒两个测试的顺序,只运行一次测试。 HashSet won again. HashSet再次获胜。

Read more about this here: How do I write a correct micro-benchmark in Java? 在这里阅读更多相关内容: 如何在Java中编写正确的微基准测试?

Additionally, check out Google Caliper for a framework that handles all these microbenchmarking issues. 另外,请查看Google Caliper以获取处理所有这些微基准测试问题的框架。

yes try to run the sync set before the regular and you will get your "needed" results. 是尝试在常规之前运行同步设置,您将获得“所需”的结果。 I reckon this has to do with the JVM warm up and nothing else. 我认为这与JVM热身有关,没有别的。 Try to warn up the VM with some computations and then run the benchmark or run it a few times in a mixed order. 尝试通过一些计算警告VM,然后运行基准测试或以混合顺序运行几次。

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

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