【问题标题】:RandomAccessFile writeInt(int i) vs write(byte[] b) - performanceRandomAccessFile writeInt(int i) 与 write(byte[] b) - 性能
【发布时间】:2018-02-05 20:15:09
【问题描述】:

我今天遇到了一件关于RandomAccessFile 的有趣事情。

我注意到使用RandomAccessFilewriteInt(int i) 方法比使用RandomAccessFilewrite(byte[] b) 慢得多,我首先将int 值转换为byte[4] 数组。

我正在用这段代码进行转换

private static byte[] intToByte(int i)
{
   byte[] result = new byte[4];

   result[0] = (byte) (i >> 24);
   result[1] = (byte) (i >> 16);
   result[2] = (byte) (i >> 8);
   result[3] = (byte) (i);

  return result;
}

差异非常显着,有利于write(byte[] b)

使用 JDK 8 在我的笔记本电脑上写入 100 万个ints:

  • 通过writeInt(int i) 方法耗时~9 秒
  • 通过write(byte[] b) 耗时~2.3 秒

我在另一个环境中也有类似的结果,我使用的是 JDK 7 和完全不同的机器。

writeInt(int i) 方法委托给原生 write0(int b) 方法,write(byte[] b) 委托给原生 writeBytes

当我进行分析时,我注意到大部分执行时间都花在了writeInt 方法上。

有谁知道为什么我看到了这么大的差异?似乎writeInt 的效率较低。

【问题讨论】:

  • 反向调用这些方法怎么样?您可能已经用第一个加热了 JVM,只是用热 VM 调用了第二个。无论如何,JMH 的存在是有原因的,并且在这里受到如此多的赞誉;除非你真的发现了一些有趣的东西;)
  • 感谢您的评论。我明白你在说什么,我会做 JMH 基准测试并返回结果。虽然我在“冷”JVM 上进行了两个测试,但在不同的运行中分别调用这些测试 - 结果相同。
  • 明白,但同时你需要明确区分一些数字和一些有意义的数字。通过适当的 JMH 测试(这也不是在公园里散步),你的数字可能是有意义的......
  • 好的,我有一些数字。测试源可以在这里找到github.com/kristoffSC/RafBenchmark/tree/master/src/main/java/… 似乎即使在 JMH 中 write(byte[] b) 也更快。结果可以在这里找到:github.com/kristoffSC/RafBenchmark/blob/master/JMH_Results.txt 不过有一件事 - 因为 raf.writeInt 和 raf.write(byte[] b) 方法没有返回任何值,所以我无法在这里使用 JMH blackhole。
  • 如果你不是在两次写入之间寻找,只是顺序写入,你根本不应该使用RandomAccessFileDataOutputStreamaround a BufferedOutputStream around a FileOutputStream 将快几个数量级。 RAF 用于错误的随机访问文件。它没有以任何方式进行优化。

标签: java file-writing randomaccessfile


【解决方案1】:

RandomAccessFile 实际上有两种写入字节的原生方法:

//writes an array
private native void writeBytes(byte b[], int off, int len) throws IOException;

//writes one byte
public native void write(int b) throws IOException;

writeInt(int)方法用原生的write(int)方法分别写入每个字节,而write(byte[])使用原生 writeBytes(byte[],int,int) 方法。

writeInt 方法执行 4 次方法调用来写入传递的整数值的每个字节,另一个方法仅使用一次调用来写入数组。方法调用实际上是 java 中昂贵的操作:对于每次调用,JVM 都会为操作数堆栈和局部变量数组分配额外的内存。

【讨论】:

    【解决方案2】:

    不打算详细介绍我所做的更改,但您的测试有点缺陷。我冒昧地对它们进行了一些更新并进行了一些测试:

    @BenchmarkMode(value = { Mode.AverageTime })
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
    @Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
    public class RandomAccessWriteFileTest {
    
        public static void main(String[] args) throws Exception {
            Options opt = new OptionsBuilder().include(RandomAccessWriteFileTest.class.getSimpleName())
                    .jvmArgs("-ea")
                    .shouldFailOnError(true)
                    .build();
            new Runner(opt).run();
        }
    
        @Benchmark()
        @Fork(1)
        public long benchamrkWriteDirectInt(BenchmarkPlainIntSetup setupTest) {
            try {
                setupTest.raf.writeInt(6969);
                return setupTest.raf.length();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Benchmark()
        @Fork(1)
        public long benchamrkWriteConvertedInt(BenchmarkConvertedIntSetup setupTest) {
            try {
                setupTest.raf.write(intToBytes(6969));
                return setupTest.raf.length();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        private static byte[] intToBytes(int i) {
            byte[] result = new byte[4];
    
            result[0] = (byte) (i >> 24);
            result[1] = (byte) (i >> 16);
            result[2] = (byte) (i >> 8);
            result[3] = (byte) i;
    
            return result;
        }
    
        @State(Scope.Thread)
        static public class BenchmarkConvertedIntSetup {
    
            public RandomAccessFile raf;
    
            public File f;
    
            @Setup(Level.Iteration)
            public void setUp() {
                try {
                    f = new File("jmhDirectIntBenchamrk.ser" + ThreadLocalRandom.current().nextInt());
                    raf = new RandomAccessFile(f, "rw");
                } catch (FileNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
    
            @TearDown(Level.Iteration)
            public void tearDown() {
                f.delete();
            }
        }
    
        @State(Scope.Thread)
        static public class BenchmarkPlainIntSetup {
    
            public RandomAccessFile raf;
    
            public File f;
    
            @Setup(Level.Iteration)
            public void setUp() {
                try {
                    f = new File("jmhDirectIntBenchamrk.ser" + ThreadLocalRandom.current().nextInt());
                    raf = new RandomAccessFile(f, "rw");
                } catch (FileNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
    
            @TearDown(Level.Iteration)
            public void tearDown() {
                f.delete();
            }
        }
    }
    

    绝对结果存在差异(这些是每次操作的毫秒数)

     benchamrkWriteConvertedInt  0.008 
     benchamrkWriteDirectInt     0.026
    

    不知道为什么(可能会在一段时间后挖掘程序集以了解,但我可以确认您的发现。好问题!)

    这是使用最新的 java-8 和 java-9 btw 运行的

    【讨论】:

    • 谢谢!你能用你的更改创建一个拉取请求吗?
    • @Kristoff np,但我现在太忙了 :( 抱歉
    • 它,很酷 - 我不再需要 PR,只需从您的答案中获取代码。更改后我有类似的结果 RandomAccessWriteFileTest.benchamrkWriteConvertedInt 0,004 ms/op RandomAccessWriteFileTest.benchamrkWriteDirectInt 0,011 ms/op
    • @Kristoff 好吧,下一步将是实际查看反汇编代码或更好地尝试理解本机实现......再次,我希望今天有一些时间尝试这样做(因为男孩,这很有趣!):)
    • 每次调用写入磁盘时都会产生开销。因此,在写入磁盘时,最好将写入批量化为更少的调用。在 convertInt 方法中,这就是你正在做的。如果您一次编写更大的块,您可能会得到更好的结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-14
    • 1970-01-01
    • 2012-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-09
    相关资源
    最近更新 更多