在 IO 绑定任务上使用 CompletableFuture 时的运行时差异

Runtime discrepancy when using CompletableFuture on an IO bound task

我对JVM多线程模型的理解是,当一个线程执行IO调用时,线程BLOCKED被JVM/OS放入等待队列,直到有数据可用。

我正在尝试在我的代码中模拟此行为,并使用 JMHCompletableFuture.

运行 具有各种线程大小的基准

然而,结果并不是我所期望的。我期待一个恒定的执行时间(thread/context 切换开销),而不管线程的数量(有内存限制),因为任务是 IO 绑定而不是 CPU 绑定。

我的 cpu 是 4 核/8 线程笔记本电脑处理器,即使有 1 或 2 个线程,预期行为也存在差异。

我正在尝试在异步任务中读取一个 5MB 的文件(每个线程的单独文件)。在每次迭代开始时,我创建一个具有所需线程数的 FixedThreadPool

@Benchmark
public void readAsyncIO(Blackhole blackhole) throws ExecutionException, InterruptedException {
    List<CompletableFuture<Void>> readers = new ArrayList<>();

    for (int i =0; i< threadSize; i++) {
         int finalI = i;
         readers.add(CompletableFuture.runAsync(() -> readFile(finalI), threadPool));
    }

    Object result =  CompletableFuture
                     .allOf(readers.toArray(new CompletableFuture[0]))
                     .get();
    blackhole.consume(result);
}
@Setup(Level.Iteration)
public void setup() throws IOException {
    threadPool = Executors.newFixedThreadPool(threadSize);
}
@TearDown(Level.Iteration)
public void tearDown() {
    threadPool.shutdownNow();
}
public byte[] readFile(int i)  {
    try {
        File file = new File(filePath + "/" + fileName + i);
        byte[] bytesRead = new byte[(int)file.length()];
        InputStream inputStream = new FileInputStream(file);
        inputStream.read(bytesRead);
        return bytesRead;
    } catch (Exception e) {
        throw new CompletionException(e);
    }
}

和 JMH 配置,

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 3)
@Fork(value=1)
@Measurement(iterations = 3)
public class SimpleTest {

    @Param({ "1", "2", "4", "8", "16", "32", "50", "100" })
    public int threadSize;
    .....

}

知道我做错了什么吗?还是我的假设不正确?

好像有道理。对于单线程,您会看到 1 个文件需要大约 2 毫秒的时间来处理,添加更多线程会导致每个线程的平均时间更长,因为非常大的每个 read(bytesRead) 可能会进行多次磁盘读取,因此可能有机会进行 IO阻塞和线程上下文切换,加上 - 取决于磁盘 - 更多的寻道时间。