作者sbrhsieh (十年一梦)
看板java
标题Re: [分享] java nio performance tuning
时间Sun Oct 6 00:48:40 2013
※ 引述《dryman (dryman)》之铭言:
: http://www.idryman.org/blog/2013/09/28/java-fast-io-using-java-nio-api/
: [略]
我也做了一些测试,来分享一下我的数据与观察。
说明:
以下使用一个 4*26Mb 内容为随机产生数值的档案作测试(M=K^2, K=1024, b=byte);
method parameter 有名为 bufSize 者,实际测试时 actual argument=64K;
测试数据是以先跑 10 次要量测的 method,再跑 10 次取後十次的平均值;
这种测试方式应该会有 OS level cache 牵涉其中;
配合所使用的演算法,有些 method 测试时会提供 length 等於测试档案长度+64Kb
的 byte array。
我彷原作者的 startegy 2/3 的作法与数据如下:
Record: 1096 ms
Code:
public static final void readAll(int[] arr, InputStream in, int bufSize)
throws IOException {
DataInputStream dataIn = new DataInputStream(
new BufferedInputStream(in, bufSize));
try {
for (int i = 0; i < arr.length; ++i) {
arr[i] = dataIn.readInt();
}
}
finally {
dataIn.close();
}
}
Record: 126 ms
Code:
public static final void readAllNIO(int[] arr, File input)
throws IOException {
FileChannel channel = new FileInputStream(input).getChannel();
try {
ByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024);
int offset = 0;
int n;
while ((n = channel.read(buffer)) != -1) {
buffer.flip();
buffer.asIntBuffer().get(arr, offset, n >> 2);
buffer.clear();
offset += n >> 2;
}
}
finally {
channel.close();
}
}
不过严格来说 strategy 3(readAllNIO) 不十分正确,他假设了每次从 channel
作 read 操作所 consume 的 data size 为 4 的倍数。故其他的测试也会以这个
假设成立为前提(套用到 InputStream)。
我还做了其它的测试,我认为可以推论为 strategy 2 的瓶颈不在 IO 上:
假如只是把档案所有的 byte 读出到一个 byte array,一次读一区块,其实是蛮
快的。
Record: 78 ms
Code:
public static final void readAllBytes(byte[] arr, InputStream in)
throws IOException {
in = new BufferedInputStream(in, 64 * 1024);
try {
int n, offset = 0;
while ((n = in.read(arr, offset, 64 * 1024)) != -1) offset += n;
}
finally {
in.close();
}
}
但稍作修改每次最多只读 4 bytes,大数量 iteration 里的运算会多耗很多时间,
约占了 readAll 的 7 成。
Record: 758 ms
Code:
public static final void readAllBytes3(byte[] arr, InputStream in)
throws IOException {
InputStream dataIn = new BufferedInputStream(in, 64 * 1024);
int offset = 0;
int n;
try {
while ((n = dataIn.read(arr, offset, 4)) != -1) {
offset += n;
}
}
finally {
dataIn.close();
}
}
采用 DataInputStream::readFully 来实作则更差一点。
Record: 940 ms
Code:
public static final void readAllBytes2(byte[] arr, InputStream in)
throws IOException {
DataInputStream dataIn = new DataInputStream(
new BufferedInputStream(in, 64 * 1024));
int offset = 0;
try {
while (true) {
dataIn.readFully(arr, offset, 4);
offset += 4;
}
}
catch (EOFException e) {
}
finally {
in.close();
}
}
readAllNIO 的作法是一次读取一整块数据,一整块数据一批次 compose 成
int value(s),我想如果要公平一点,对照的 non-NIO 作法应该也要一次读
一整块,然後一整块批次作 composing。
我选择使用 nio Buffer 来实作的情况:
Record: 820 ms
Code:
public static final void readAll2(int[] arr, InputStream in, int bufSize)
throws IOException {
InputStream dataIn = new BufferedInputStream(in, bufSize);
byte[] block = new byte[bufSize];
int n, offset = 0;
try {
while ((n = dataIn.read(block)) != -1) {
ByteBuffer.wrap(block, 0, n).asIntBuffer().get(
arr, offset, n >> 2);
offset += n >> 2;
}
}
finally {
dataIn.close();
}
}
最後考虑到 readAllNIO 是使用 direct Buffer,或许 direct/non-direct Buffer
在 bulk get 的实作上(也许 native 层面)差别很大,於是我自己写了 native
method 来作 composing 部分。
在 Windows 7 上以 sun 1.6.0_45 32-bits client VM 测试,由於处里器是
Little-endian,所以要处理 byte order 问题,若不处理 byte order 则可直接
使用 memcpy,那 performance 几乎与 readAllBytes 无异。
Record: 235 ms
Code:
public static final void readAll3(int[] arr, InputStream in, int bufSize)
throws IOException {
InputStream dataIn = new BufferedInputStream(in, bufSize);
byte[] block = new byte[bufSize];
int n, offset = 0;
try {
while ((n = dataIn.read(block)) != -1) {
arraycopy(block, 0, arr, offset, n >> 2);
offset += n >> 2;
}
}
finally {
dataIn.close();
}
}
public static final void arraycopy(byte[] src, int srcOffset,
int[] dest, int destOffset, int numsInt) {
if (srcOffset + numsInt * 4 > src.length)
throw new IllegalArgumentException("source array is too short");
if (destOffset + numsInt > dest.length)
throw new IllegalArgumentException("destination array is too short");
arraycopyImpl(src, srcOffset, dest, destOffset, numsInt);
}
private static native void arraycopyImpl(byte[] src, int srcOffset,
int[] dest, int destOffset, int numsInt);
/* native method implementation */
#include <memory.h>
#include <winsock2.h>
#include "cc_ptt_java_IOSpeed.h"
class PrimitiveArray
{
public:
PrimitiveArray(JNIEnv *env, jarray arr, jint releaseMode)
: _env(env), _array(arr), _mode(releaseMode), _address(0)
{
jboolean isCopy = JNI_FALSE;
_address = env->GetPrimitiveArrayCritical(_array, &isCopy);
}
~PrimitiveArray()
{
_env->ReleasePrimitiveArrayCritical(_array, _address, _mode);
}
operator jbyte *() const
{
return reinterpret_cast<jbyte *>(_address);
}
private:
JNIEnv *_env;
jarray _array;
void* _address;
jint _mode;
};
JNIEXPORT
void JNICALL Java_cc_ptt_java_IOSpeed_arraycopyImpl(JNIEnv *env, jclass klass,
jbyteArray srcArray, jint srcOffset,
jintArray destArray, jint destOffset, jint numsInt)
{
PrimitiveArray source(env, srcArray, JNI_ABORT);
PrimitiveArray dest(env, destArray, 0);
u_long *srcP = reinterpret_cast<u_long *>(
source + srcOffset * sizeof (jbyte));
u_long *end = srcP + numsInt;
u_long *destP = reinterpret_cast<u_long *>(
dest + destOffset * sizeof(jint));
while (srcP < end)
*destP++ = ntohl(*srcP++);
}
看到这里 NIO 能带来多大的助益,各位心里各自有一把尺,我自己则是对我
之前推文里的看法「NIO 带来效率数十倍的提升」持怀疑的心态。
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 218.164.110.25
※ 编辑: sbrhsieh 来自: 218.164.110.25 (10/06 01:05)
1F:→ realmeat:我也开始怀疑了 10/06 07:26
2F:→ bitlife:我当初看到nio第一个感觉是移植C的指标+索引以直接存取缓 10/06 09:15
3F:→ bitlife:冲区会比较方便有效率,当然还有其它的特异功能 10/06 09:28