首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JMH -如何正确基准线程池?

JMH -如何正确基准线程池?
EN

Stack Overflow用户
提问于 2021-10-28 20:55:37
回答 4查看 949关注 0票数 1

,请阅读这个问题的最新编辑.

问题:我需要编写一个正确的基准来比较使用不同线程池(也来自外部库)的不同工作池实现(也来自外部库),使用不同的执行方法来执行到使用E 211使用E 112其他线程池<代码>E 213实现,以及与E 114工作<代码>E 215没有任何线程读取。

例如,我有24个任务要完成,10000个随机字符串处于基准状态:

代码语言:javascript
复制
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3)
@Measurement(iterations = 3)
@State(Scope.Benchmark)
public class ThreadPoolSamples {
    @Param({"24"})
    int amountOfTasks;
    private static final int tts = Runtime.getRuntime().availableProcessors() * 2;
    private String[] strs = new String[10000];

    @Setup
    public void setup() {
        for (int i = 0; i < strs.length; i++) {
            strs[i] = String.valueOf(Math.random());
        }
    }
}

和两个状态作为表示工作的内部类(string Concat)。以及ExecutorService设置和关闭:

代码语言:javascript
复制
@State(Scope.Thread)
public static class Work {
    public String doWork(String[] strs) {
        StringBuilder conc = new StringBuilder();
        for (String str : strs) {
            conc.append(str);
        }
        return conc.toString();
    }
}

@State(Scope.Benchmark)
public static class ExecutorServiceState {
    ExecutorService service;

    @Setup(Level.Iteration)
    public void setupMethod() {
        service = Executors.newFixedThreadPool(tts);
    }

    @TearDown(Level.Iteration)
    public void downMethod() {
        service.shutdownNow();
        service = null;
    }
}

更严格的问题是:如何编写正确的基准来度量doWork()的平均时间;第一:没有任何线程处理;第二:使用.execute()方法;第三:使用.submit()方法获得期货结果。我试图写的实现:

代码语言:javascript
复制
@Benchmark
public void noThreading(Work w, Blackhole bh) {
    for (int i = 0; i < amountOfTasks; i++) {
        bh.consume(w.doWork(strs));
    }
}

@Benchmark
public void executorService(ExecutorServiceState e, Work w, Blackhole bh) {
    for (int i = 0; i < amountOfTasks; i++) {
         e.service.execute(() -> bh.consume(w.doWork(strs)));
    }
}

@Benchmark
public void noThreadingResult(Work w, Blackhole bh) {
    String[] strss = new String[amountOfTasks];
    for (int i = 0; i < amountOfTasks; i++) {
        strss[i] = w.doWork(strs);
    }
    bh.consume(strss);
}

@Benchmark
public void executorServiceResult(ExecutorServiceState e, Work w, Blackhole bh) throws ExecutionException, InterruptedException {
    Future[] strss = new Future[amountOfTasks];
    for (int i = 0; i < amountOfTasks; i++) {
        strss[i] = e.service.submit(() -> {return w.doWork(strs);});
    }
    for (Future future : strss) {
        bh.consume(future.get());
    }
}

在我的PC (2个内核,4个线程)上对这个实现进行基准测试之后,我得到了:

代码语言:javascript
复制
Benchmark                              (amountOfTasks)  Mode  Cnt         Score         Error  Units
ThreadPoolSamples.executorService                     24  avgt    3    255102,966 ± 4460279,056  ns/op
ThreadPoolSamples.executorServiceResult               24  avgt    3  19790020,180 ± 7676762,394  ns/op
ThreadPoolSamples.noThreading                         24  avgt    3  18881360,497 ±  340778,773  ns/op
ThreadPoolSamples.noThreadingResult                   24  avgt    3  19283976,445 ±  471788,642  ns/op

noThreading和executorService可能是正确的(但我仍然不确定),noThreadingResult和executorServiceResult看起来一点也不正确。

编辑:

我发现了一些新的细节,但我认为结果仍然是不正确的:正如中回答的那样,线程池没有等待提交的任务完成,但这不仅仅是一个问题: javac还以某种方式优化了工作类中的doWork()方法(为了简单起见,该操作的结果是由JVM预测的),所以为了简单起见,我使用了Thread.sleep()作为“工作”,并设置了amountOfTasks新的两个params:"1“和" 128”来演示在一个任务线程上的读取速度将比noThreading慢,24和128将是大约的。它比noThreading快四倍,对于测量的正确性,我在基准测试中设置了启动和关闭线程池:

代码语言:javascript
复制
package io.denery;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.concurrent.*;

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3)
@Measurement(iterations = 3)
@State(Scope.Benchmark)
public class ThreadPoolSamples {
    @Param({"1", "24", "128"})
    int amountOfTasks;
    private static final int tts = Runtime.getRuntime().availableProcessors() * 2;

    @State(Scope.Thread)
    public static class Work {
        public void doWork() {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Benchmark
    public void noThreading(Work w) {
        for (int i = 0; i < amountOfTasks; i++) {
            w.doWork();
        }
    }

    @Benchmark
    public void fixedThreadPool(Work w)
            throws ExecutionException, InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(tts);
        Future[] futures = new Future[amountOfTasks];
        for (int i = 0; i < amountOfTasks; i++) {
            futures[i] = service.submit(w::doWork);
        }
        for (Future future : futures) {
            future.get();
        }

        service.shutdown();
    }

    @Benchmark
    public void cachedThreadPool(Work w)
            throws ExecutionException, InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        Future[] futures = new Future[amountOfTasks];
        for (int i = 0; i < amountOfTasks; i++) {
            futures[i] = service.submit(() -> {
                w.doWork();
            });
        }
        for (Future future : futures) {
            future.get();
        }

        service.shutdown();
    }
}

这一基准的结果是:

代码语言:javascript
复制
Benchmark                         (amountOfTasks)  Mode  Cnt          Score         Error  Units
ThreadPoolSamples.cachedThreadPool                1  avgt    3    1169075,866 ±   47607,783  ns/op
ThreadPoolSamples.cachedThreadPool               24  avgt    3    5208437,498 ± 4516260,543  ns/op
ThreadPoolSamples.cachedThreadPool              128  avgt    3   13112351,066 ± 1905089,389  ns/op
ThreadPoolSamples.fixedThreadPool                 1  avgt    3    1166087,665 ±   61193,085  ns/op
ThreadPoolSamples.fixedThreadPool                24  avgt    3    4721503,799 ±  313206,519  ns/op
ThreadPoolSamples.fixedThreadPool               128  avgt    3   18337097,997 ± 5781847,191  ns/op
ThreadPoolSamples.noThreading                     1  avgt    3    1066035,522 ±   83736,346  ns/op
ThreadPoolSamples.noThreading                    24  avgt    3   25525744,055 ±   45422,015  ns/op
ThreadPoolSamples.noThreading                   128  avgt    3  136126357,514 ±  200461,808  ns/op

我们看到错误并不大,任务1的线程池比noThreading慢,但是如果您比较25525744,055和4721503,799,加速速度是: 5.406,它比截断~4要快,如果你比较136126357,514和18337097,997,加速速度是: 7.4,这个假加速比和amountOfTasks在增长,我认为它仍然是不正确的。我认为使用PrintAssembly来了解这个问题是否存在任何JVM优化。

编辑:

正如user17294549答案中提到的,我使用Thread.sleep()作为对实际工作的模仿,它不正确,因为:

对于实际工作:只有2个任务可以在Thread.sleep()的2核系统上同时运行:任意数量的任务都可以在一个2核系统上同时运行。

我记得关于Blackhole.consumeCPU(长令牌) JMH方法,它“燃烧循环”并模仿一项工作,它有JMH实例文档。所以我换了工作:

代码语言:javascript
复制
@State(Scope.Thread)
public static class Work {
    public void doWork() {
        Blackhole.consumeCPU(4096);
    }
}

以及这一变化的基准:

代码语言:javascript
复制
Benchmark                         (amountOfTasks)  Mode  Cnt         Score          Error  Units
ThreadPoolSamples.cachedThreadPool                1  avgt    3    301187,897 ±    95819,153  ns/op
ThreadPoolSamples.cachedThreadPool               24  avgt    3   2421815,991 ±   545978,808  ns/op
ThreadPoolSamples.cachedThreadPool              128  avgt    3   6648647,025 ±    30442,510  ns/op
ThreadPoolSamples.cachedThreadPool             2048  avgt    3  60229404,756 ± 21537786,512  ns/op
ThreadPoolSamples.fixedThreadPool                 1  avgt    3    293364,540 ±    10709,841  ns/op
ThreadPoolSamples.fixedThreadPool                24  avgt    3   1459852,773 ±   160912,520  ns/op
ThreadPoolSamples.fixedThreadPool               128  avgt    3   2846790,222 ±    78929,182  ns/op
ThreadPoolSamples.fixedThreadPool              2048  avgt    3  25102603,592 ±  1825740,124  ns/op
ThreadPoolSamples.noThreading                     1  avgt    3     10071,049 ±      407,519  ns/op
ThreadPoolSamples.noThreading                    24  avgt    3    241561,416 ±    15326,274  ns/op
ThreadPoolSamples.noThreading                   128  avgt    3   1300241,347 ±   148051,168  ns/op
ThreadPoolSamples.noThreading                  2048  avgt    3  20683253,408 ±  1433365,542  ns/op

我们看到,fixedThreadPool在某种程度上比没有线程的示例慢,当amountOfTasks更大时,fixedThreadPool和noThreading示例之间的差异就更小了。里面发生了什么?在这个问题的开头,我看到了字符串连接的相同现象,但我没有报告。(顺便说一句,谢谢你读了这本小说,试着回答这个问题,你真的帮了我。)

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2021-11-09 20:56:21

在其他回答者的帮助下,我自己解决了这个问题。在上一次编辑(以及所有其他编辑)中,问题都在我的gradle配置中,所以我在所有系统线程中运行这个基准测试,我使用这个gradle插件运行JMH,在我的gradle构建脚本中设置所有基准测试之前,我设置了threads = 4值,所以您看到了这些奇怪的基准测试结果,因为JMH试图对所有可用线程池进行基准测试,对所有可用线程进行测试。我删除了这个配置,并在benchmark类中设置了@State(Scope.Thread)@Threads(1)注释,这是一个编辑过的runInThreadPool()方法,用于:

代码语言:javascript
复制
public static void runInThreadPool(int amountOfTasks, Blackhole bh, ExecutorService threadPool)
            throws InterruptedException, ExecutionException {
        Future<?>[] futures = new Future[amountOfTasks];
        for (int i = 0; i < amountOfTasks; i++) {
            futures[i] = threadPool.submit(PrioritySchedulerSamples::doWork, (ThreadFactory) runnable -> {
                Thread thread = new Thread(runnable);
                thread.setPriority(10);
                return thread;
            });
        }
        for (Future<?> future : futures) {
            bh.consume(future.get());
        }

        threadPool.shutdownNow();
        threadPool.awaitTermination(10, TimeUnit.SECONDS);
    }

因此,这个线程池中的每个线程都以最大优先级运行。以及所有这些变化的基准:

代码语言:javascript
复制
Benchmark                                 (amountOfTasks)  Mode  Cnt         Score         Error  Units
PrioritySchedulerSamples.fixedThreadPool             2048  avgt    3   8021054,516 ± 2874987,327  ns/op
PrioritySchedulerSamples.noThreading                 2048  avgt    3  17583295,617 ± 5499026,016  ns/op

这些结果似乎是正确的。(尤其是对于我的系统。)

我还列出了微基准测试线程池以及基本上所有并发java组件中的常见问题:

  1. 确保您的微基准在一个线程中执行,使用@Threads(1)@State(Scope.Thread)注释使您的微基准在一个线程中执行。(例如,使用htop命令找出消耗最多CPU百分比的线程有多少个,哪些线程消耗最多)
  2. 确保在微基准测试中完全执行任务,并等待所有线程完成此任务。(也许您的微基准测试不需要等待任务完成?)
  3. 不要使用Thread.sleep()来模仿真正的工作,而是JMH提供了Blackhole.consumeCPU(long tokens)方法,您可以自由地使用它来模仿某些工作。
  4. 确保您知道您所测试的组件。(很明显,在此之前我还不太清楚java线程池)
  5. 确保您了解这些JMH小艇中描述的编译器优化效果,基本上非常了解JMH。
票数 0
EN

Stack Overflow用户

发布于 2021-10-29 14:03:51

请参阅这个问题的答案,以了解如何用java编写基准测试。

..。executorService也许是对的(但我仍然不确定). 基准(amountOfTasks)模式Cnt分数误差单位ThreadPoolSamples.executorService 24 avgt 3 255102,966±4460279,056 ns/op

它看起来不像正确的结果:错误4460279,056比基值255102,966大17倍。

此外,您在以下方面也有一个错误:

代码语言:javascript
复制
@Benchmark
public void executorService(ExecutorServiceState e, Work w, Blackhole bh) {
    for (int i = 0; i < amountOfTasks; i++) {
         e.service.execute(() -> bh.consume(w.doWork(strs)));
    }
}

您将任务提交给ExecutorService,但不会等待它们完成。

票数 0
EN

Stack Overflow用户

发布于 2021-10-31 19:20:07

看看这段代码:

代码语言:javascript
复制
    @TearDown(Level.Iteration)
    public void downMethod() {
        service.shutdownNow();
        service = null;
    }

你不需要等待线程停止。详细信息请阅读医生们

因此,您的一些基准测试可能与cachedThreadPool在以前的基准测试中生成的另外128个线程并行运行。

所以为了简单起见,我使用了Thread.sleep()作为“工作”

真的吗?

真正的工作和Thread.sleep()有很大的区别

  • 对于实际工作:只有两个任务可以在一个双核系统上同时运行。
  • 对于Thread.sleep():任何数量的任务都可以在一个2核系统上同时运行。
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69760827

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档