From 05dd1f05d5364bc7adfd151502e7de1d8e50d760 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 24 Aug 2019 13:46:28 +0200 Subject: [PATCH] Reduce GC produced by AbstractByteBuf.indexOf(..) implementation (#9502) Motivation: AbstractByteBuf.indexOf(...) currently delegates to ByteBufUtils.indexOf(...) which will create a new ByteBufProcessor on each call. This is done to reduce overhead of bounds-checks. Unfortunally while this reduces bounds checks it produces a lot of GC. We can just implement our own version in AbstractByteBuf which makes use of _getByte(...) and so does no bound checks as well but also not need to create any garbage. Modifications: Write optimized implementation of indexOf(...) for AbstractByteBuf Result: Fixes https://github.com/netty/netty/issues/9499. --- .../java/io/netty/buffer/AbstractByteBuf.java | 39 ++++++++++++++++++- .../io/netty/buffer/AbstractByteBufTest.java | 36 ++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/buffer/src/main/java/io/netty/buffer/AbstractByteBuf.java b/buffer/src/main/java/io/netty/buffer/AbstractByteBuf.java index ca411c05e7..107540da48 100644 --- a/buffer/src/main/java/io/netty/buffer/AbstractByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/AbstractByteBuf.java @@ -1209,7 +1209,44 @@ public abstract class AbstractByteBuf extends ByteBuf { @Override public int indexOf(int fromIndex, int toIndex, byte value) { - return ByteBufUtil.indexOf(this, fromIndex, toIndex, value); + if (fromIndex <= toIndex) { + return firstIndexOf(fromIndex, toIndex, value); + } else { + return lastIndexOf(fromIndex, toIndex, value); + } + } + + private int firstIndexOf(int fromIndex, int toIndex, byte value) { + fromIndex = Math.max(fromIndex, 0); + if (fromIndex >= toIndex || capacity() == 0) { + return -1; + } + checkIndex(fromIndex, toIndex - fromIndex); + + for (int i = fromIndex; i < toIndex; i ++) { + if (_getByte(i) == value) { + return i; + } + } + + return -1; + } + + private int lastIndexOf(int fromIndex, int toIndex, byte value) { + fromIndex = Math.min(fromIndex, capacity()); + if (fromIndex < 0 || capacity() == 0) { + return -1; + } + + checkIndex(toIndex, fromIndex - toIndex); + + for (int i = fromIndex - 1; i >= toIndex; i --) { + if (_getByte(i) == value) { + return i; + } + } + + return -1; } @Override diff --git a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java index 52e5e4efa5..3bb22c8cf5 100644 --- a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java +++ b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java @@ -2117,6 +2117,9 @@ public abstract class AbstractByteBufTest { @Test public void testIndexOf() { buffer.clear(); + // Ensure the buffer is completely zero'ed. + buffer.setZero(0, buffer.capacity()); + buffer.writeByte((byte) 1); buffer.writeByte((byte) 2); buffer.writeByte((byte) 3); @@ -2127,6 +2130,38 @@ public abstract class AbstractByteBufTest { assertEquals(-1, buffer.indexOf(4, 1, (byte) 1)); assertEquals(1, buffer.indexOf(1, 4, (byte) 2)); assertEquals(3, buffer.indexOf(4, 1, (byte) 2)); + + try { + buffer.indexOf(0, buffer.capacity() + 1, (byte) 0); + fail(); + } catch (IndexOutOfBoundsException expected) { + // expected + } + + try { + buffer.indexOf(buffer.capacity(), -1, (byte) 0); + fail(); + } catch (IndexOutOfBoundsException expected) { + // expected + } + + assertEquals(4, buffer.indexOf(buffer.capacity() + 1, 0, (byte) 1)); + assertEquals(0, buffer.indexOf(-1, buffer.capacity(), (byte) 1)); + } + + @Test + public void testIndexOfReleaseBuffer() { + ByteBuf buffer = releasedBuffer(); + if (buffer.capacity() != 0) { + try { + buffer.indexOf(0, 1, (byte) 1); + fail(); + } catch (IllegalReferenceCountException expected) { + // expected + } + } else { + assertEquals(-1, buffer.indexOf(0, 1, (byte) 1)); + } } @Test @@ -2553,7 +2588,6 @@ public abstract class AbstractByteBufTest { private ByteBuf releasedBuffer() { ByteBuf buffer = newBuffer(8); - // Clear the buffer so we are sure the reader and writer indices are 0. // This is important as we may return a slice from newBuffer(...). buffer.clear();