gpt4 book ai didi

java - 测量 java.io.InputStream 的性能

转载 作者:行者123 更新时间:2023-11-30 01:49:59 25 4
gpt4 key购买 nike

我有一个大小为 5GB 的文件,我想按 block 读取,比如 2MB。使用 java.io.InputStream 效果很好。所以我测量了这个东西如下:

static final byte[] buffer = new byte[2 * 1024 * 1024];

public static void main(String args[]) throws IOException {
while(true){
InputStream is = new FileInputStream("/tmp/log_test.log");
long bytesRead = 0;
int readCurrent;
long start = System.nanoTime();
while((readCurrent = is.read(buffer)) > 0){
bytesRead += readCurrent;
}
long end = System.nanoTime();
System.out.println(
"Bytes read = " + bytesRead + ". Time elapsed = " + (end - start)
);
}
}

结果 = 2121714428

可以看出平均需要2121714428纳秒。之所以如此,是因为该实现将 (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf); 将数据读入 malloced 或堆栈分配的缓冲区,如图所示 here 。所以 memcpy 需要相当多的 CPU 时间:

enter image description here

由于 JNI 规范定义了

Inside a critical region, native code must not call other JNI functions, or any system call that may cause the current thread to block and wait for another Java thread. (For example, the current thread must not call read on a stream being written by another Java thread.)

我认为从关键部分内的常规文件读取没有任何问题。从常规文件中读取数据只会被短暂阻止,并且不依赖于任何 java 线程。像这样的事情:

static final byte[] buffer = new byte[2 * 1024 * 1024];

public static void main(String args[]) throws IOException {
while (true) {
int fd = open("/tmp/log_test.log");
long bytesRead = 0;
int readCurrent;
long start = System.nanoTime();
while ((readCurrent = read(fd, buffer)) > 0) {
bytesRead += readCurrent;
}
long end = System.nanoTime();
System.out.println("Bytes read = " + bytesRead + ". Time elapsed = " + (end - start));
}
}

private static native int open(String path);

private static native int read(int fd, byte[] buf);

JNI 函数:

JNIEXPORT jint JNICALL Java_com_test_Main_open
(JNIEnv *env, jclass jc, jstring path){
const char *native_path = (*env)->GetStringUTFChars(env, path, NULL);
int fd = open(native_path, O_RDONLY);
(*env)->ReleaseStringUTFChars(env, path, native_path);
return fd;
}


JNIEXPORT jint JNICALL Java_com_test_Main_read
(JNIEnv *env, jclass jc, jint fd, jbyteArray arr){
size_t java_array_size = (size_t) (*env)->GetArrayLength(env, arr);
void *buf = (*env)->GetPrimitiveArrayCritical(env, arr, NULL);
ssize_t bytes_read = read(fd, buf, java_array_size);
(*env)->ReleasePrimitiveArrayCritical(env, arr, buf, 0);
return (jint) bytes_read;
}

结果 = 1179852225

在循环中运行它平均需要 1179852225 纳秒,这几乎是效率的两倍。

问题:从临界区中的常规文件读取实际问题是什么?

最佳答案

使用 FileInputStream 的 2MB 缓冲区可能不是最佳选择。请参阅 this question了解详情。虽然是在Windows上,但我看到了similar performance issue在 Linux 上。根据操作系统的不同,分配临时大缓冲区可能会导致额外的 mmap 调用和后续页面错误。而且如此大的缓冲区使得 L1/L2 缓存毫无用处。

Reading from a regular file is blocked only briefly and does not depend on any java thread.

这并不总是正确的。在您的基准测试中,该文件显然缓存在操作系统页面缓存中,并且没有发生设备 I/O。访问真实硬件(尤其是旋转磁盘)可能会慢几个数量级。磁盘 I/O 的最坏时间无法完全预测 - 它可能长达数百毫秒,具体取决于硬件条件、I/O 队列长度、调度策略等。

JNI 临界区的问题是,每当发生延迟时,它可能会影响所有线程,而不仅仅是执行 I/O 的线程。对于单线程应用程序来说,这不是问题,但这可能会导致多线程应用程序中出现不必要的“停止世界”暂停。

对 JNI 至关重要的另一个原因是与 GCLocker 相关的 JVM 错误。有时它们可​​能会导致冗余 GC 循环或忽略某些 Gcflags。以下是一些示例(尚未修复):

  • JDK-8048556不必要的 GCLocker 启动的年轻 GC
  • JDK-8057573如果 GCLocker 处于 Activity 状态,则忽略 CMSScavengeBeforeRemark
  • JDK-8057586如果 GCLocker 处于 Activity 状态,则显式 GC 会被忽略

所以,问题是您是否关心吞吐量还是延迟。如果您只需要更高的吞吐量,JNI Critical 可能是正确的选择。但是,如果您还关心可预测的延迟(不是平均延迟,而是 99.9%),那么 JNI 关键似乎不是一个好的选择。

关于java - 测量 java.io.InputStream 的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56402783/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com