测量java.io.InputStream中的表现(Measuring performance of

2019-09-28 07:10发布

我有一个大小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); 的数据的读入malloc ED或堆栈分配的缓冲区,如图这里 。 所以memcpy花费相当大量的CPU时间:

由于JNI规范定义了

里面的临界区域,本地代码不能调用其他JNI函数,或任何系统调用,导致当前线程阻塞和等待另一个Java线程。 (例如,当前线程不能调用读取流正在被另一个Java线程编写的。)

我看不出做从普通文件的关键部分内读取任何问题。 从一个普通文件中读取受阻只是暂时的,并不依赖于任何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;
}

RESULT = 1179852225

在一个循环中捉迷藏这需要平均1179852225毫微秒,这几乎两倍更有效率。

问题:什么是与临界段内的常规文件读取实际问题?

Answer 1:

用的FileInputStream 2MB缓存可能不是最好的选择。 见这个问题的详细信息。 虽然这是在Windows上,我已经看到了类似的性能问题在Linux上。 根据不同的操作系统上,分配一个临时大的缓冲区可能会导致额外的mmap调用和随后的页面错误。 也如此大的缓冲器,使L1 / L2高速缓存无用。

从一个普通文件中读取受阻只是暂时的,并不依赖于任何Java线程。

这并非总是如此。 在您的基准文件显然是缓存在OS页面缓存和没有设备I / O发生。 访问真正的硬件(尤其是旋转盘)可以是数量级的速度较慢。 磁盘I的最糟糕的时候/ O是不能完全预测的 - 它可以大到几百毫秒,这取决于硬件条件,I / O队列,调度策略等的长度。

用JNI关键部分的问题是每当有延迟现象,它可能会影响所有线程,不仅是一个做I / O。 这是不是一个单线程应用程序的问题,但是这可能会导致不良停止的世界在多线程应用程序暂停。

另一个原因对JNI关键是与GCLocker JVM错误 。 有时,他们可能会导致多余GC周期或忽略某些GC标志。 以下是一些例子(依然不动):

  • JDK-8048556不必要GCLocker发起的年轻的GC
  • JDK-8057573 CMSScavengeBeforeRemark忽略,如果GCLocker活跃
  • JDK-8057586忽略显式GC如果GCLocker活跃

所以,问题是你是否在乎吞吐量延迟 。 如果你只需要更高的吞吐量,JNI的关键可能是正确的道路要走。 但是,如果你还在乎预测的延迟(不是平均延迟时间,但表示,99.9%),那么JNI关键似乎并不像不错的选择。



文章来源: Measuring performance of java.io.InputStream