多线程环境中的基准标记

时间:2015-12-16 13:47:40

标签: java multithreading executorservice microbenchmark

我正在学习多线程并发现在多线程环境中减慢了Object.hashCode的速度,因为计算运行4个线程的默认哈希码与相同数量的对象的1个线程相比,它需要花费两倍的时间。

但根据我的理解,并行执行此操作需要花费相同的时间。

您可以更改主题数。每个线程都有相同的工作量,所以你希望在我的四核机器上运行4个线程可能需要与运行单个线程大致相同的时间。

我看到4x只有2.3秒,而1x则看到.9秒。

我的理解是否有任何差距,请帮助我理解这种行为。

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;

 public class ObjectHashCodePerformance {

private static final int THREAD_COUNT = 4;
private static final int ITERATIONS = 20000000;

public static void main(final String[] args) throws Exception {
    long start = System.currentTimeMillis();
    new ObjectHashCodePerformance().run();
    System.err.println(System.currentTimeMillis() - start);
 }

private final ExecutorService _sevice =   Executors.newFixedThreadPool(THREAD_COUNT,
        new ThreadFactory() {
            private final ThreadFactory _delegate =   Executors.defaultThreadFactory();

            @Override
            public Thread newThread(final Runnable r) {
                Thread thread = _delegate.newThread(r);
                thread.setDaemon(true);
                return thread;
            }
        });

    private void run() throws Exception {
    Callable<Void> work = new java.util.concurrent.Callable<Void>() {
        @Override
        public Void call() throws Exception {
            for (int i = 0; i < ITERATIONS; i++) {
                Object object = new Object();
                object.hashCode();
            }
            return null;
        }
    };
    @SuppressWarnings("unchecked")
    Callable<Void>[] allWork = new Callable[THREAD_COUNT];
    Arrays.fill(allWork, work);
    List<Future<Void>> futures = _sevice.invokeAll(Arrays.asList(allWork));
    for (Future<Void> future : futures) {
        future.get();
    }
 }

 }

对于线程数4输出

~2.3 seconds

对于线程计数1输出

~.9 seconds

3 个答案:

答案 0 :(得分:6)

我已经创建了一个简单的JMH基准来测试各种情况:

@Fork(1)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 10)
@Warmup(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
public class HashCodeBenchmark {
    private final Object object = new Object();

    @Benchmark
    @Threads(1)
    public void singleThread(Blackhole blackhole){
        blackhole.consume(object.hashCode());
    }

    @Benchmark
    @Threads(2)
    public void twoThreads(Blackhole blackhole){
        blackhole.consume(object.hashCode());
    }

    @Benchmark
    @Threads(4)
    public void fourThreads(Blackhole blackhole){
        blackhole.consume(object.hashCode());
    }

    @Benchmark
    @Threads(8)
    public void eightThreads(Blackhole blackhole){
        blackhole.consume(object.hashCode());
    }
}

结果如下:

Benchmark                       Mode  Cnt  Score   Error  Units
HashCodeBenchmark.eightThreads  avgt   10  5.710 ± 0.087  ns/op
HashCodeBenchmark.fourThreads   avgt   10  3.603 ± 0.169  ns/op
HashCodeBenchmark.singleThread  avgt   10  3.063 ± 0.011  ns/op
HashCodeBenchmark.twoThreads    avgt   10  3.067 ± 0.034  ns/op

所以我们可以看到,只要没有线程而不是核心,每个哈希码的时间保持不变。

PS:正如@Tom Cools评论的那样 - 你正在测试分配速度而不是测试中的hashCode()速度。

答案 1 :(得分:1)

见Palamino的评论:

你没有测量hashCode(),你在单线程时测量2000万个对象的实例化,在运行4个线程时测量8000万个对象。将新的Object()逻辑移出Callable中的for循环,然后你将测量hashCode() - Palamino

答案 2 :(得分:0)

我在代码中看到了两个问题:

  1. allWork []数组的大小等于ITERATIONS。
  2. 迭代时,在call()方法中确保每个线程获得其负载份额。 ITERATIONS / THREAD_COUNT。
  3. 以下是您可以尝试的修改版本:

    import java.util.Arrays;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    import java.util.concurrent.ThreadFactory;
    
     public class ObjectHashCodePerformance {
    
    private static final int THREAD_COUNT = 1;
    private static final int ITERATIONS = 20000;
    private final Object object = new Object();
    
    public static void main(final String[] args) throws Exception {
        long start = System.currentTimeMillis();
        new ObjectHashCodePerformance().run();
        System.err.println(System.currentTimeMillis() - start);
     }
    
    private final ExecutorService _sevice =   Executors.newFixedThreadPool(THREAD_COUNT,
            new ThreadFactory() {
                private final ThreadFactory _delegate =   Executors.defaultThreadFactory();
    
                @Override
                public Thread newThread(final Runnable r) {
                    Thread thread = _delegate.newThread(r);
                    thread.setDaemon(true);
                    return thread;
                }
            });
    
        private void run() throws Exception {
        Callable<Void> work = new java.util.concurrent.Callable<Void>() {
            @Override
            public Void call() throws Exception {
                for (int i = 0; i < ITERATIONS/THREAD_COUNT; i++) {
                    object.hashCode();
                }
                return null;
            }
        };
        @SuppressWarnings("unchecked")
        Callable<Void>[] allWork = new Callable[ITERATIONS];
        Arrays.fill(allWork, work);
        List<Future<Void>> futures = _sevice.invokeAll(Arrays.asList(allWork));
        System.out.println("Futures size : " + futures.size());
        for (Future<Void> future : futures) {
            future.get();
        }
     }
    
     }