From a8f53b7084fd38a305a4d8ed849d5d11f3cf840a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 6 Jan 2016 10:50:31 +0100 Subject: [PATCH] Increased performance of non-random-access read/write by switching to block-aligned mode only when necessary. --- .../BlockAlignedReadableFile.java | 24 +++++++++++++++++- .../BlockAlignedWritableFile.java | 22 ++++++++++++++++ .../BlockAlignedReadableFileTest.java | 21 ++++++++++++++++ .../BlockAlignedWritableFileTest.java | 25 +++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFile.java index dd4215fbc..a6d21ef46 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFile.java @@ -20,6 +20,11 @@ class BlockAlignedReadableFile extends DelegatingReadableFile { private final int blockSize; private final ByteBuffer currentBlockBuffer; private boolean eofReached = false; + private Mode mode = Mode.PASSTHROUGH; + + private enum Mode { + BLOCK_ALIGNED, PASSTHROUGH; + } public BlockAlignedReadableFile(ReadableFile delegate, int blockSize) { super(delegate); @@ -28,11 +33,12 @@ class BlockAlignedReadableFile extends DelegatingReadableFile { } this.blockSize = blockSize; this.currentBlockBuffer = ByteBuffer.allocate(blockSize); - this.currentBlockBuffer.flip(); // so the next attempt will read from source. + this.currentBlockBuffer.flip(); // so remaining() is 0 -> next read will read from physical source. } @Override public void position(long logicalPosition) throws UncheckedIOException { + switchToBlockAlignedMode(); long blockNumber = logicalPosition / blockSize; long physicalPosition = blockNumber * blockSize; assert physicalPosition <= logicalPosition; @@ -45,8 +51,24 @@ class BlockAlignedReadableFile extends DelegatingReadableFile { currentBlockBuffer.position(diff); } + // visible for testing + void switchToBlockAlignedMode() { + mode = Mode.BLOCK_ALIGNED; + } + @Override public int read(ByteBuffer target) throws UncheckedIOException { + switch (mode) { + case PASSTHROUGH: + return super.read(target); + case BLOCK_ALIGNED: + return readBlockAligned(target); + default: + throw new IllegalStateException("Unsupported mode " + mode); + } + } + + private int readBlockAligned(ByteBuffer target) { int read = -1; while (!eofReached && target.hasRemaining()) { read += ByteBuffers.copy(currentBlockBuffer, target); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFile.java index 489334b6f..c671e6962 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFile.java @@ -21,6 +21,11 @@ class BlockAlignedWritableFile extends DelegatingWritableFile { private final int blockSize; private final ReadableFile readableFile; private final ByteBuffer currentBlockBuffer; + private Mode mode = Mode.PASSTHROUGH; + + private enum Mode { + BLOCK_ALIGNED, PASSTHROUGH; + } public BlockAlignedWritableFile(WritableFile delegate, ReadableFile readableFile, int blockSize) { super(delegate); @@ -31,6 +36,7 @@ class BlockAlignedWritableFile extends DelegatingWritableFile { @Override public void position(long logicalPosition) throws UncheckedIOException { + switchToBlockAlignedMode(); long blockNumber = logicalPosition / blockSize; long physicalPosition = blockNumber * blockSize; readableFile.position(physicalPosition); @@ -40,8 +46,24 @@ class BlockAlignedWritableFile extends DelegatingWritableFile { super.position(physicalPosition); } + // visible for testing + void switchToBlockAlignedMode() { + mode = Mode.BLOCK_ALIGNED; + } + @Override public int write(ByteBuffer source) throws UncheckedIOException { + switch (mode) { + case PASSTHROUGH: + return super.write(source); + case BLOCK_ALIGNED: + return writeBlockAligned(source); + default: + throw new IllegalStateException("Unsupported mode " + mode); + } + } + + private int writeBlockAligned(ByteBuffer source) { int written = 0; while (source.hasRemaining()) { written += ByteBuffers.copy(source, currentBlockBuffer); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFileTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFileTest.java index 5c5f4492d..ca8544485 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFileTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFileTest.java @@ -18,6 +18,7 @@ import org.cryptomator.filesystem.WritableFile; import org.cryptomator.filesystem.inmem.InMemoryFileSystem; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; public class BlockAlignedReadableFileTest { @@ -27,6 +28,26 @@ public class BlockAlignedReadableFileTest { ReadableFile r = new BlockAlignedReadableFile(null, 0); } + @Test + public void testSwitchingModes() { + FileSystem fs = new InMemoryFileSystem(); + File file = fs.file("test"); + try (WritableFile w = file.openWritable()) { + w.write(ByteBuffer.wrap(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09})); + } + + BlockAlignedReadableFile readable = Mockito.spy(new BlockAlignedReadableFile(file.openReadable(), 2)); + ByteBuffer firstRead = ByteBuffer.allocate(4); + readable.read(firstRead); + Mockito.verify(readable, Mockito.never()).switchToBlockAlignedMode(); + readable.position(0); + Mockito.verify(readable).switchToBlockAlignedMode(); + ByteBuffer secondRead = ByteBuffer.allocate(4); + readable.read(secondRead); + Assert.assertArrayEquals(firstRead.array(), secondRead.array()); + readable.close(); + } + @Test public void testRead() { FileSystem fs = new InMemoryFileSystem(); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFileTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFileTest.java index 6762ab77c..2b9d6fb08 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFileTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFileTest.java @@ -17,9 +17,34 @@ import org.cryptomator.filesystem.WritableFile; import org.cryptomator.filesystem.inmem.InMemoryFileSystem; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; public class BlockAlignedWritableFileTest { + @Test + public void testSwitchingModes() { + FileSystem fs = new InMemoryFileSystem(); + File file = fs.file("test"); + try (WritableFile w = file.openWritable()) { + w.write(ByteBuffer.wrap(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09})); + } + + BlockAlignedWritableFile writable = Mockito.spy(new BlockAlignedWritableFile(file.openWritable(), file.openReadable(), 2)); + writable.write(ByteBuffer.wrap(new byte[] {0x11, 0x12, 0x13})); + Mockito.verify(writable, Mockito.never()).switchToBlockAlignedMode(); + writable.position(1); + Mockito.verify(writable).switchToBlockAlignedMode(); + writable.write(ByteBuffer.wrap(new byte[] {0x14, 0x15, 0x16})); + writable.close(); + + try (ReadableFile r = file.openReadable()) { + ByteBuffer buf = ByteBuffer.allocate(10); + r.read(buf); + buf.flip(); + Assert.assertArrayEquals(new byte[] {0x11, 0x14, 0x15, 0x16, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}, buf.array()); + } + } + @Test public void testWrite() { FileSystem fs = new InMemoryFileSystem();