我有一个5GB的文件,我想按块读取,例如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)
);
}
}
RESULT = 2121714428
可以看出,平均需要2121714428纳米。之所以如此,是因为该实现会将(*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);
的数据读入here所示的malloc
ed或堆栈分配的缓冲区中。因此memcpy
占用了大量CPU时间:
因为JNI规范定义了
在关键区域内,本机代码不得调用其他JNI 函数或任何可能导致当前线程执行的系统调用 阻塞并等待另一个Java线程。 (例如,当前 线程不得在另一个Java编写的流上调用read 线程。)
从关键部分的 常规文件 中读取没有任何问题。从常规文件读取仅被短暂阻止,并且不依赖于任何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纳米,这几乎是效率的两倍。
问题: 从关键区域中的 常规文件 中读取数据的实际问题是什么?
答案 0 :(得分:2)
带有FileInputStream的2MB缓冲区可能不是最佳选择。有关详细信息,请参见this question。尽管它在Windows上,但我在Linux上看到过similar performance issue。根据操作系统的不同,分配临时大缓冲区可能会导致额外的mmap
调用和后续的页面错误。如此大的缓冲区也会使L1 / L2缓存无效。
从常规文件读取仅被短暂阻止,不会 取决于任何Java线程。
并非总是如此。在您的基准测试中,文件显然已缓存在OS页面缓存中,并且没有发生设备I / O。访问实际硬件(尤其是旋转磁盘)可能会慢几个数量级。磁盘I / O的最坏时间无法完全预测-可能长达数百毫秒,具体取决于硬件条件,I / O队列的长度,调度策略等。
JNI关键部分的问题是每当发生延迟时,它可能会影响所有线程,不仅影响I / O。对于单线程应用程序来说,这不是一个问题,但是这可能会导致多线程应用程序出现意外的世界停顿。
反对JNI严重的另一个原因是与GCLocker相关的JVM错误。有时,它们可能会导致多余的GC循环或忽略某些GC标志。以下是一些示例(仍未解决):
因此,问题是您是否关心吞吐量或延迟。如果只需要更高的吞吐量,那么JNI关键可能是正确的选择。但是,如果您还关注可预测的延迟(不是平均延迟,而是说99.9%),那么JNI关键似乎不是一个好选择。