【问题标题】:Bench Mark in Multi threaded environment多线程环境中的基准测试
【发布时间】:2015-12-16 13:47:39
【问题描述】:

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

但根据我的理解,并行执行此操作应该花费类似的时间。

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

我看到 4x 大约 2.3 秒,但 1x 大约 0.9 秒。

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

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

【问题讨论】:

  • 请分享您在 1 到 4 个线程之间所做的更改
  • 这里的时间测量不一定能告诉你太多。见stackoverflow.com/questions/504103/…
  • 你可能没有测量正确的东西:GC、创建执行器及其线程、线程协调、对象实例化、内存分配等等。无论如何,beanchmark 是非常没用的,因为无论如何您都无法对 Object 的 hashCode() 实现进行任何更改。
  • 您不是在测量 hashCode(),而是在单线程时测量 2000 万个对象的实例化,在运行 4 个线程时测量 8000 万个对象。将新的 Object() 逻辑移出 Callable 中的 for 循环,然后您将测量 hashCode()
  • 此外,对象的 hashCode 实际上是通过本地平台特定调用实现的,因此您可能不会发现任何性能问题。

标签: java multithreading executorservice microbenchmark


【解决方案1】:

我创建了一个简单的 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() 速度。

【讨论】:

  • 你能告诉..关于你用于基准标记的工具
【解决方案2】:

查看帕拉米诺的评论:

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

【讨论】:

  • 他说你可以改变线程数来观察他描述的问题
  • 我把它移出同样的结果..:(
【解决方案3】:

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

  1. allWork [] 数组的大小等于 ITERATIONS。
  2. 并且在迭代时,在 call() 方法中确保每个线程都得到它的负载份额。 ITERATIONS/THREAD_COUNT 个。

以下是修改后的版本,你可以试试:

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();
    }
 }

 }

【讨论】:

  • run()/call() 方法中,您仍在分配对象-因此您正在测量哈希码和分配速度。你的回答有问题。
猜你喜欢
  • 2017-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-18
  • 2011-07-27
  • 2016-03-28
  • 2010-12-20
  • 1970-01-01
相关资源
最近更新 更多