,请阅读这个问题的最新编辑.
问题:我需要编写一个正确的基准来比较使用不同线程池(也来自外部库)的不同工作池实现(也来自外部库),使用不同的执行方法来执行到使用E 211使用E 112其他线程池<代码>E 213实现,以及与E 114工作<代码>E 215没有任何线程读取。
例如,我有24个任务要完成,10000个随机字符串处于基准状态:
@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设置和关闭:
@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()方法获得期货结果。我试图写的实现:
@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个线程)上对这个实现进行基准测试之后,我得到了:
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/opnoThreading和executorService可能是正确的(但我仍然不确定),noThreadingResult和executorServiceResult看起来一点也不正确。
编辑:
我发现了一些新的细节,但我认为结果仍然是不正确的:正如这中回答的那样,线程池没有等待提交的任务完成,但这不仅仅是一个问题: javac还以某种方式优化了工作类中的doWork()方法(为了简单起见,该操作的结果是由JVM预测的),所以为了简单起见,我使用了Thread.sleep()作为“工作”,并设置了amountOfTasks新的两个params:"1“和" 128”来演示在一个任务线程上的读取速度将比noThreading慢,24和128将是大约的。它比noThreading快四倍,对于测量的正确性,我在基准测试中设置了启动和关闭线程池:
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();
}
}这一基准的结果是:
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实例和文档。所以我换了工作:
@State(Scope.Thread)
public static class Work {
public void doWork() {
Blackhole.consumeCPU(4096);
}
}以及这一变化的基准:
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示例之间的差异就更小了。里面发生了什么?在这个问题的开头,我看到了字符串连接的相同现象,但我没有报告。(顺便说一句,谢谢你读了这本小说,试着回答这个问题,你真的帮了我。)
发布于 2021-11-09 20:56:21
在其他回答者的帮助下,我自己解决了这个问题。在上一次编辑(以及所有其他编辑)中,问题都在我的gradle配置中,所以我在所有系统线程中运行这个基准测试,我使用这个gradle插件运行JMH,在我的gradle构建脚本中设置所有基准测试之前,我设置了threads = 4值,所以您看到了这些奇怪的基准测试结果,因为JMH试图对所有可用线程池进行基准测试,对所有可用线程进行测试。我删除了这个配置,并在benchmark类中设置了@State(Scope.Thread)和@Threads(1)注释,这是一个编辑过的runInThreadPool()方法,用于:
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);
}因此,这个线程池中的每个线程都以最大优先级运行。以及所有这些变化的基准:
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组件中的常见问题:
@Threads(1)和@State(Scope.Thread)注释使您的微基准在一个线程中执行。(例如,使用htop命令找出消耗最多CPU百分比的线程有多少个,哪些线程消耗最多)Thread.sleep()来模仿真正的工作,而是JMH提供了Blackhole.consumeCPU(long tokens)方法,您可以自由地使用它来模仿某些工作。发布于 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倍。
此外,您在以下方面也有一个错误:
@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,但不会等待它们完成。
发布于 2021-10-31 19:20:07
看看这段代码:
@TearDown(Level.Iteration)
public void downMethod() {
service.shutdownNow();
service = null;
}你不需要等待线程停止。详细信息请阅读医生们。
因此,您的一些基准测试可能与cachedThreadPool在以前的基准测试中生成的另外128个线程并行运行。
所以为了简单起见,我使用了Thread.sleep()作为“工作”
真的吗?
真正的工作和Thread.sleep()有很大的区别
Thread.sleep():任何数量的任务都可以在一个2核系统上同时运行。https://stackoverflow.com/questions/69760827
复制相似问题