postgresql-jdbc.git / summary / log / commit / refs
commit b97031ecf29ce4e80aadcf80fe3e262aa4422423
Author: Vladimir Sitnikov <sitnikov.vladimir@gmail.com>
Date: Sun Dec 03 10:38:24 2023 +0000
perf: reduce array allocation in ByteStreamWriter.writeTo(ByteStreamTarget)
Previously, the implementation always created a Channel with Channels.newChannel(outputStream),
however, OpenJDK (e.g. 22) still allocates arrays.
The improvement is to access the arrays in case ByteBuffer allows so.
DirectByteBuffer and read-only ByteBuffers forbid array access.
.../util/ByteBufferByteStreamWriter.java | 12 +++--
.../util/ByteBuffersByteStreamWriter.java | 28 ++++++++++--
.../postgresql/test/util/ByteStreamWriterTest.java | 51 +++++++++++++++++++++-
3 files changed, 82 insertions(+), 9 deletions(-)
diff --git a/pgjdbc/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java b/pgjdbc/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java
index be25be54..f0a8aa03 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java
@@ -36,13 +36,17 @@ public class ByteBufferByteStreamWriter implements ByteStreamWriter {
@Override
public void writeTo(ByteStreamTarget target) throws IOException {
+ if (buf.hasArray()) {
+ // Avoid copying the array if possible
+ target.getOutputStream()
+ .write(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
+ return;
+ }
+
// this _does_ involve some copying to a temporary buffer, but that's unavoidable
// as OutputStream itself only accepts single bytes or heap allocated byte arrays
- WritableByteChannel c = Channels.newChannel(target.getOutputStream());
- try {
+ try (WritableByteChannel c = Channels.newChannel(target.getOutputStream())) {
c.write(buf);
- } finally {
- c.close();
}
}
}
diff --git a/pgjdbc/src/main/java/org/postgresql/util/ByteBuffersByteStreamWriter.java b/pgjdbc/src/main/java/org/postgresql/util/ByteBuffersByteStreamWriter.java
index ea8feb53..9a954307 100644
--- a/pgjdbc/src/main/java/org/postgresql/util/ByteBuffersByteStreamWriter.java
+++ b/pgjdbc/src/main/java/org/postgresql/util/ByteBuffersByteStreamWriter.java
@@ -6,6 +6,7 @@
package org.postgresql.util;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
@@ -40,11 +41,30 @@ class ByteBuffersByteStreamWriter implements ByteStreamWriter {
@Override
public void writeTo(ByteStreamTarget target) throws IOException {
- // this _does_ involve some copying to a temporary buffer, but that's unavoidable
- // as OutputStream itself only accepts single bytes or heap allocated byte arrays
- try (WritableByteChannel c = Channels.newChannel(target.getOutputStream());) {
+ boolean allArraysAreAccessible = true;
+ for (ByteBuffer buffer : buffers) {
+ if (!buffer.hasArray()) {
+ allArraysAreAccessible = false;
+ break;
+ }
+ }
+
+ OutputStream os = target.getOutputStream();
+ if (allArraysAreAccessible) {
+ for (ByteBuffer buffer : buffers) {
+ os.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
+ }
+ return;
+ }
+ // Channels.newChannel does not buffer writes, so we can mix writes to the channel with writes
+ // to the OutputStream
+ try (WritableByteChannel c = Channels.newChannel(os);) {
for (ByteBuffer buffer : buffers) {
- c.write(buffer);
+ if (buffer.hasArray()) {
+ os.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
+ } else {
+ c.write(buffer);
+ }
}
}
}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java b/pgjdbc/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java
index 6f3d771e..34069e31 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java
@@ -36,9 +36,12 @@ public class ByteStreamWriterTest extends BaseTest4 {
private ByteBuffer testData(int size) {
ByteBuffer data = ByteBuffer.allocate(size);
Random random = new Random(31459);
- while (data.remaining() > 0) {
+ while (data.remaining() > 8) {
data.putLong(random.nextLong());
}
+ while (data.remaining() > 0) {
+ data.put((byte) (random.nextInt() % 256));
+ }
data.rewind();
return data;
}
@@ -95,6 +98,52 @@ public class ByteStreamWriterTest extends BaseTest4 {
validateContent(testData);
}
+ @Test
+ public void testLength37b() throws Exception {
+ ByteBuffer testData = testData(37);
+ insertStream(testData);
+ validateContent(testData);
+ }
+
+ @Test
+ public void testLength2KbReadOnly() throws Exception {
+ ByteBuffer testData = testData(2 * 1024);
+ // Read-only buffer does not provide access to the array, so we test it separately
+ insertStream(testData.asReadOnlyBuffer());
+ validateContent(testData);
+ }
+
+ @Test
+ public void testTwoBuffers() throws Exception {
+ ByteBuffer testData = testData(20);
+ ByteBuffer part1 = testData.duplicate();
+ part1.position(0);
+ part1.limit(9);
+ ByteBuffer part2 = testData.duplicate();
+ part2.position(part1.limit());
+ part2.limit(testData.limit());
+ // Read-only buffer does not provide access to the array, so we test it separately
+ insertStream(ByteStreamWriter.of(part1, part2));
+ validateContent(testData);
+ }
+
+ @Test
+ public void testThreeBuffersWithReadonly() throws Exception {
+ ByteBuffer testData = testData(20);
+ ByteBuffer part1 = testData.duplicate();
+ part1.position(0);
+ part1.limit(9);
+ ByteBuffer part2 = testData.duplicate();
+ part2.position(part1.limit());
+ part2.limit(15);
+ ByteBuffer part3 = testData.duplicate();
+ part3.position(part2.limit());
+ part3.limit(testData.limit());
+ // Read-only buffer does not provide access to the array, so we test it separately
+ insertStream(ByteStreamWriter.of(part1, part2.asReadOnlyBuffer(), part3));
+ validateContent(testData);
+ }
+
@Test
public void testLength10Kb() throws Exception {
ByteBuffer testData = testData(10 * 1024);
[parent: b419750929ae]