简体   繁体   中英

RandomAccessFile writeInt(int i) vs write(byte[] b) - performance

I've faced an interesting thing today regarding RandomAccessFile .

I've noticed that using RandomAccessFile 's writeInt(int i) method is much more slower than using RandomAccessFile 's write(byte[] b) where I first convert int value to byte[4] array.

I'm doing the conversion with this code

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;
}

The difference is very significant, favoring write(byte[] b) .

Writing 1 million int s on my laptop with JDK 8:

  • via writeInt(int i) method took ~9 seconds
  • via write(byte[] b) took ~2,3 seconds

I have similar results in another environment, where I'm using JDK 7 and a totally different machine.

The writeInt(int i) method delegate to native write0(int b) method and write(byte[] b) delegates to native writeBytes .

When I did profiling I've noticed that the majority of the execution time was spent in writeInt method when it was used.

Does anyone know why I see such a big difference? Seems like writeInt is way less efficient.

RandomAccessFile has actually two native methods to write bytes:

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

and

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

the method writeInt(int) writes each byte separately with the native write(int) method, while write(byte[]) uses the native writeBytes(byte[],int,int) method.

the writeInt method does 4 method invocations to write each byte of the passed integer value, the other method uses only one invocation to write the array. Method invocations are actually expensive operations in java: for each invocation the JVM allocates additional memory for the operand stack and the local variables array.

Not going to go into details of the changes that I made, but your tests are a little bit flawed. I took the liberty of updating them a little and ran a few tests too:

@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();
        }
    }
}

Absolutely there is a difference in results (these are ms per operation)

 benchamrkWriteConvertedInt  0.008 
 benchamrkWriteDirectInt     0.026

No idea why (may be will dig the assembly to understand some time later, but I can confirm your findings. good question!)

This was run with latest java-8 and java-9 btw

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM