在 IO 绑定任务上使用 CompletableFuture 时的运行时差异
Runtime discrepancy when using CompletableFuture on an IO bound task
我对JVM多线程模型的理解是,当一个线程执行IO调用时,线程BLOCKED
被JVM/OS放入等待队列,直到有数据可用。
我正在尝试在我的代码中模拟此行为,并使用 JMH
和 CompletableFuture
.
运行 具有各种线程大小的基准
然而,结果并不是我所期望的。我期待一个恒定的执行时间(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阻塞和线程上下文切换,加上 - 取决于磁盘 - 更多的寻道时间。
我对JVM多线程模型的理解是,当一个线程执行IO调用时,线程BLOCKED
被JVM/OS放入等待队列,直到有数据可用。
我正在尝试在我的代码中模拟此行为,并使用 JMH
和 CompletableFuture
.
然而,结果并不是我所期望的。我期待一个恒定的执行时间(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阻塞和线程上下文切换,加上 - 取决于磁盘 - 更多的寻道时间。