MappedByteBuffer.asFloatBuffer()与内存中float []性能 - java

假设您正在对一大组较大的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[])正在复制内存,我想这就是速度下降的原因。可以更快吗?有没有办法完全避免任何内存复制,而只是从OS的缓冲区中获取数据?

我已经将完整的基准测试上传到this gist,以防您想尝试运行它:

$ java -Xmx1024m ArrayVsMMap 100 100000 100

编辑:

最后,在这种情况下,我能够摆脱MappedByteBuffer的最佳结果仍然比使用常规float[]慢35%。到目前为止的技巧是:

  • 使用本机字节顺序来避免转换:buffer.order(ByteOrder.nativeOrder())
  • 使用MappedByteBuffer
  • FloatBuffer换成buffer.asFloatBuffer()

  • 使用简单的floatBuffer.get(int index)而不是批量版本,这样可以避免内存复制。
  • 您可以在this gist上看到新的基准测试和结果。

    1.35的降速比5的降幅要好得多,但它仍远未达到1。我可能仍然缺少某些东西,否则它应该在JVM中得到改善。

    参考方案

    您基于数组的时间非常快!我得到每个浮点0.000002纳秒。 JVM可能正在优化循环。

    这就是问题:

        void iterate() {
            for (int i = 0; i < nVectors; i++) {
                calc(data, i * vectorSize, vectorSize);
            }
        }
    

    JVM意识到calc没有副作用,因此iterate也没有副作用,因此可以将其替换为NOP。一个简单的解决方法是从calc累积结果并将其返回。您还需要在定时循环中对iterate的结果执行相同的操作,然后打印结果。这样可以防止优化程序删除所有代码。

    编辑:

    看起来这可能只是Java方面的开销,与内存映射本身无关,而与它的接口无关。尝试以下测试,该测试只是将FloatBuffer包裹在ByteBuffer周围:

      private static final class ArrayByteBufferTest extends IterationTest {
        private final FloatBuffer floatBuffer;
        private final int vectorSize;
        private final int nVectors;
    
        ArrayByteBufferTest(float[] data, int vectorSize, int nVectors) {
          ByteBuffer bb = ByteBuffer.wrap(new byte[data.length * 4]);
          for (int i = 0; i < data.length; i++) {
            bb.putFloat(data[i]);
          }
          bb.rewind();
          this.floatBuffer = bb.asFloatBuffer();
          this.vectorSize = vectorSize;
          this.nVectors = nVectors;
        }
    
        float iterate() {
          float sum = 0;
          floatBuffer.rewind();
          float[] vector = new float[vectorSize];
          for (int i = 0; i < nVectors; i++) {
            floatBuffer.get(vector);
            sum += calc(vector, 0, vector.length);
          }
          return sum;
        }
      }
    

    由于您对浮点数本身所做的工作很少(仅添加它,可能需要1个周期),因此读取4个字节,构建一个浮点数并将其复制到数组的开销全部加在一起。我注意到,拥有更少,更大的向量至少在向量大于(L1?)缓存之前会有所帮助。

    性能:Java中的BufferedOutputStream与FileOutputStream - java

    我已经读到BufferedOutputStream类可以提高效率,并且必须以这种方式与FileOutputStream一起使用-BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream("myfile.txt")); 对于下面的语句写入同一文件也是有…

    Java:正则表达式模式匹配器是否有大小限制? - java

    我的模式类似于OR:“word1 | word2 | word3”我大约有800个字。可能有问题吗? 参考方案 您仅受记忆和理智的限制。 :)

    Java:线程池如何将线程映射到可运行对象 - java

    试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …

    JAVA:字节码和二进制有什么区别? - java

    java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

    java:继承 - java

    有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…