假设您正在对一大组大float
向量进行一些计算,例如计算每个向量的平均值:
public static float avg(float[] data, int offset, int length) {
float sum = 0;
for (int i = offset; i < offset + length; i++) {
sum += data[i];
}
return sum / length;
}
如果您将所有向量都存储在内存中float[]
,则可以这样实现循环:
float[] data; // <-- vectors here
float sum = 0;
for (int i = 0; i < nVectors; i++) {
sum += avg(data, i * vectorSize, vectorSize);
}
如果您的向量存储在一个文件中,那么理论上,内存映射应该与第一个解决方案一样快,一旦操作系统缓存了整个内容:
RandomAccessFile file; // <-- vectors here
MappedByteBuffer buffer = file.getChannel().map(READ_WRITE, 0, 4*data.length);
FloatBuffer floatBuffer = buffer.asFloatBuffer();
buffer.load(); // <-- this forces the OS to cache the file
float[] vector = new float[vectorSize];
float sum = 0;
for (int i = 0; i < nVectors; i++) {
floatBuffer.get(vector);
sum += avg(vector, 0, vector.length);
}
但是,我的测试表明,内存映射版本比内存版本慢约 5 倍。我知道这FloatBuffer.get(float[])
是在复制内存,我想这就是速度变慢的原因。能不能快点?有没有办法完全避免任何内存复制,只从操作系统的缓冲区中获取我的数据?
我已经将我的完整基准上传到这个 gist,以防你想尝试它只是运行:
$ java -Xmx1024m ArrayVsMMap 100 100000 100
编辑:
最后,在这种情况下,我能够摆脱的最好结果MappedByteBuffer
仍然比使用常规慢float[]
约 35%。到目前为止的技巧是:
- 使用本机字节顺序来避免转换:
buffer.order(ByteOrder.nativeOrder())
MappedByteBuffer
用FloatBuffer
using包裹buffer.asFloatBuffer()
- 使用简单
floatBuffer.get(int index)
版本而不是批量版本,这样可以避免内存复制。
您可以在这个要点上看到新的基准和结果。
1.35 的减速比 5 中的一个要好得多,但距离 1 还很远。我可能仍然缺少一些东西,或者它是 JVM 中应该改进的东西。