9

I tried to find out why OkHttp uses Okio but not BufferedInputStream and BufferedOutputStream to buffer data. I used following code to verify:

private String targetPath = Environment.getExternalStorageDirectory()
        + File.separator + "performance.dat";

private InputStream getInputStream() {
    try {
        File targetFile = new File(targetPath);

        if (targetFile.exists()) {
            targetFile.delete();
        }

        targetFile.createNewFile();
        return new FileInputStream("/sdcard/file.zip");
    } catch (IOException e) {
        e.printStackTrace();
    }

    return null;
}

public void bufferedIO(View view) {
    new Thread() {

        @Override
        public void run() {
            InputStream inputStream = getInputStream();

            if (inputStream == null) {
                return;
            }

            long start = System.currentTimeMillis();

            inputStream = new BufferedInputStream(inputStream, 8192);
            File targetFile = new File(targetPath);
            BufferedOutputStream fileOutputStream = null;

            try {
                fileOutputStream = new BufferedOutputStream(new FileOutputStream(targetFile, true), 8192);
                byte[] buffer = new byte[4096];

                int count;
                while ((count = inputStream.read(buffer)) > 0) {
                    fileOutputStream.write(buffer, 0, count);
                }

                fileOutputStream.flush();
                Log.i("performance", "BufferedInputStream and BufferedOutputStream: " + (System.currentTimeMillis() - start) + "ms");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                if (fileOutputStream != null) {
                    try {
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }.start();
}

public void okio(View view) {
    new Thread() {

        @Override
        public void run() {
            InputStream inputStream = getInputStream();

            if (inputStream == null) {
                return;
            }

            long start = System.currentTimeMillis();

            File targetFile = new File(targetPath);
            Source bufferSource = Okio.buffer(Okio.source(inputStream));
            BufferedSink bufferSink = null;

            try {
                bufferSink = Okio.buffer(Okio.sink(targetFile));

                while ((bufferSource.read(bufferSink.buffer(), 4096)) != -1) {
                    bufferSink.emitCompleteSegments();
                }
                bufferSink.flush();

                Log.i("performance", "okio: " + (System.currentTimeMillis() - start) + "ms");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    bufferSource.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                if (bufferSink != null) {
                    try {
                        bufferSink.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }.start();
}

I called bufferedIO() 5 times, the results are:

357ms
299ms
311ms
324ms
331ms

I called okio() 5 times, the results are:

524ms
661ms
555ms
525ms
573ms

According to the results, BufferedInputStream and BufferedOutputStream are more efficient than Okio. Is there anything wrong with my verification?

dreamer
  • 153
  • 2
  • 10

2 Answers2

11

I ran this benchmark on my desktop and my results were very inconsistent. I think the benchmark is ultimately measuring filesystem performance more than the I/O library.

I took out the extra indirection in the Okio benchmark, starting with a Source rather than the intermediate FileInputStream. I also removed the page-by-page loop which isn’t necessary for Okio: you can just call writeAll() to copy an entire source to a sink:

public void okio() throws IOException {
  long start = System.currentTimeMillis();

  File targetFile = new File(targetPath);
  targetFile.delete();

  try (BufferedSink sink = Okio.buffer(Okio.sink(targetFile));
      Source bufferSource = Okio.source(new File(sourcePath))) {
    sink.writeAll(bufferSource);
    System.out.println("okio: " + (System.currentTimeMillis() - start) + "ms");
  }
}

My results were wildly inconsistent due to file system performance - individual runs varied by more than 200%.

okio:  67ms   java.io: 106ms
okio:  98ms   java.io: 106ms
okio: 108ms   java.io: 110ms
okio: 121ms   java.io: 113ms
okio: 125ms   java.io: 116ms
okio: 131ms   java.io: 118ms
okio: 143ms   java.io: 143ms
okio: 154ms   java.io: 145ms
okio: 191ms   java.io: 146ms
okio: 217ms   java.io: 239ms

Overall Okio was more efficient here, but that might just be luck. A better benchmark would isolate the unreliable filesystem I/O. If you’d like to try that, I’m interested in the results!

Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
-1

I changed your code from

Source bufferSource = Okio.buffer(Okio.source(inputStream));

to

Source bufferSource = null;
try {
      bufferSource = Okio.buffer(Okio.source(targetFile));
} catch (Exception e) {
      e.printStackTrace();
}

It looks much faster than before but i'm not sure why. It need time to check the source code of okio.

Dean Wang
  • 9
  • 2