Skip to content

Commit d0f68c4

Browse files
committed
Optimize writeCharacters for NIO Buffers.
- Remove extra bounds checking. - Add ASCII fast-loop similar to String.getBytes().
1 parent 304f854 commit d0f68c4

File tree

7 files changed

+151
-2
lines changed

7 files changed

+151
-2
lines changed

bson/src/main/org/bson/ByteBuf.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ public interface ByteBuf {
136136
*/
137137
byte[] array();
138138

139+
boolean hasArray();
140+
139141
/**
140142
* Returns this buffer's limit.
141143
*

bson/src/main/org/bson/ByteBufNIO.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ public byte[] array() {
108108
return buf.array();
109109
}
110110

111+
@Override
112+
public boolean hasArray() {
113+
return buf.hasArray();
114+
}
115+
111116
@Override
112117
public int limit() {
113118
return buf.limit();

bson/src/main/org/bson/io/OutputBuffer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public void writeLong(final long value) {
196196
writeInt64(value);
197197
}
198198

199-
private int writeCharacters(final String str, final boolean checkForNullCharacters) {
199+
protected int writeCharacters(final String str, final boolean checkForNullCharacters) {
200200
int len = str.length();
201201
int total = 0;
202202

driver-core/src/main/com/mongodb/internal/connection/ByteBufferBsonOutput.java

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.mongodb.internal.connection;
1818

19+
import org.bson.BsonSerializationException;
1920
import org.bson.ByteBuf;
2021
import org.bson.io.OutputBuffer;
2122

@@ -25,8 +26,10 @@
2526
import java.util.ArrayList;
2627
import java.util.List;
2728

29+
import static com.mongodb.assertions.Assertions.assertFalse;
2830
import static com.mongodb.assertions.Assertions.assertTrue;
2931
import static com.mongodb.assertions.Assertions.notNull;
32+
import static java.lang.String.format;
3033

3134
/**
3235
* <p>This class is not part of the public API and may be removed or changed at any time</p>
@@ -100,6 +103,11 @@ private ByteBuf getCurrentByteBuffer() {
100103
return getByteBufferAtIndex(curBufferIndex);
101104
}
102105

106+
private ByteBuf getNextByteBuffer() {
107+
assertFalse(bufferList.get(curBufferIndex).hasRemaining());
108+
return getByteBufferAtIndex(++curBufferIndex);
109+
}
110+
103111
private ByteBuf getByteBufferAtIndex(final int index) {
104112
if (bufferList.size() < index + 1) {
105113
bufferList.add(bufferProvider.getBuffer(index >= (MAX_SHIFT - INITIAL_SHIFT)
@@ -282,4 +290,128 @@ private static final class BufferPositionPair {
282290
this.position = position;
283291
}
284292
}
293+
294+
protected int writeCharacters(final String str, final boolean checkNullTermination) {
295+
int len = str.length();
296+
int sp = 0;
297+
int prevPos = position;
298+
299+
ByteBuf buf = getCurrentByteBuffer();
300+
int currBufferPos = buf.position();
301+
int limit = buf.limit();
302+
int remaining = limit - currBufferPos;
303+
304+
if (buf instanceof PowerOfTwoBufferPool.PooledByteBufNIO && buf.hasArray()) {
305+
byte[] dst = buf.array();
306+
if (remaining >= str.length() + 1) {
307+
sp = writeOnArrayAscii(str, dst, currBufferPos, checkNullTermination);
308+
currBufferPos += sp;
309+
if (sp == len) {
310+
dst[currBufferPos++] = 0;
311+
position += sp + 1;
312+
buf.position(currBufferPos);
313+
return sp + 1;
314+
}
315+
position += sp;
316+
buf.position(currBufferPos);
317+
}
318+
}
319+
320+
while (sp < len) {
321+
remaining = limit - currBufferPos;
322+
int c = str.charAt(sp);
323+
324+
if (checkNullTermination && c == 0x0) {
325+
throw new BsonSerializationException(
326+
format("BSON cstring '%s' is not valid because it contains a null character " + "at index %d", str, sp));
327+
}
328+
329+
if (c < 0x80) {
330+
if (remaining == 0) {
331+
buf = getNextByteBuffer();
332+
currBufferPos = 0;
333+
limit = buf.limit();
334+
}
335+
buf.put((byte) c);
336+
currBufferPos++;
337+
position++;
338+
} else if (c < 0x800) {
339+
if (remaining < 2) {
340+
write((byte) (0xc0 + (c >> 6)));
341+
write((byte) (0x80 + (c & 0x3f)));
342+
343+
buf = getCurrentByteBuffer();
344+
currBufferPos = buf.position();
345+
limit = buf.limit();
346+
} else {
347+
buf.put((byte) (0xc0 + (c >> 6)));
348+
buf.put((byte) (0x80 + (c & 0x3f)));
349+
currBufferPos += 2;
350+
position += 2;
351+
}
352+
} else {
353+
c = Character.codePointAt(str, sp);
354+
if (c < 0x10000) {
355+
if (remaining < 3) {
356+
write((byte) (0xe0 + (c >> 12)));
357+
write((byte) (0x80 + ((c >> 6) & 0x3f)));
358+
write((byte) (0x80 + (c & 0x3f)));
359+
360+
buf = getCurrentByteBuffer();
361+
currBufferPos = buf.position();
362+
limit = buf.limit();
363+
} else {
364+
buf.put((byte) (0xe0 + (c >> 12)));
365+
buf.put((byte) (0x80 + ((c >> 6) & 0x3f)));
366+
buf.put((byte) (0x80 + (c & 0x3f)));
367+
currBufferPos += 3;
368+
position += 3;
369+
}
370+
} else {
371+
if (remaining < 4) {
372+
write((byte) (0xf0 + (c >> 18)));
373+
write((byte) (0x80 + ((c >> 12) & 0x3f)));
374+
write((byte) (0x80 + ((c >> 6) & 0x3f)));
375+
write((byte) (0x80 + (c & 0x3f)));
376+
377+
buf = getCurrentByteBuffer();
378+
currBufferPos = buf.position();
379+
limit = buf.limit();
380+
} else {
381+
buf.put((byte) (0xf0 + (c >> 18)));
382+
buf.put((byte) (0x80 + ((c >> 12) & 0x3f)));
383+
buf.put((byte) (0x80 + ((c >> 6) & 0x3f)));
384+
buf.put((byte) (0x80 + (c & 0x3f)));
385+
currBufferPos += 4;
386+
position += 4;
387+
}
388+
}
389+
}
390+
sp += Character.charCount(c);
391+
}
392+
393+
getCurrentByteBuffer().put((byte) 0);
394+
position++;
395+
return position - prevPos;
396+
}
397+
398+
private static int writeOnArrayAscii(final String str,
399+
final byte[] dst,
400+
final int currentPos,
401+
final boolean checkNullTermination) {
402+
int pos = currentPos;
403+
int sp = 0;
404+
for (; sp < str.length(); sp++, pos++) {
405+
char c = str.charAt(sp);
406+
if (checkNullTermination && c == 0) {
407+
throw new BsonSerializationException(
408+
format("BSON cstring '%s' is not valid because it contains a null character " + "at index %d", str, sp));
409+
}
410+
if (c >= 0x80) {
411+
break;
412+
}
413+
dst[pos] = (byte) c;
414+
}
415+
return sp;
416+
}
285417
}

driver-core/src/main/com/mongodb/internal/connection/CompositeByteBuf.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ public byte[] array() {
213213
throw new UnsupportedOperationException("Not implemented yet!");
214214
}
215215

216+
@Override
217+
public boolean hasArray() {
218+
return false;
219+
}
220+
216221
@Override
217222
public ByteBuf limit(final int newLimit) {
218223
if (newLimit < 0 || newLimit > capacity()) {

driver-core/src/main/com/mongodb/internal/connection/PowerOfTwoBufferPool.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ static int roundUpToNextHighestPowerOfTwo(final int size) {
156156
return v;
157157
}
158158

159-
private class PooledByteBufNIO extends ByteBufNIO {
159+
public class PooledByteBufNIO extends ByteBufNIO {
160160

161161
PooledByteBufNIO(final ByteBuffer buf) {
162162
super(buf);

driver-core/src/main/com/mongodb/internal/connection/netty/NettyByteBuf.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ public byte[] array() {
100100
return proxied.array();
101101
}
102102

103+
@Override
104+
public boolean hasArray() {
105+
return proxied.hasArray();
106+
}
107+
103108
@Override
104109
public int limit() {
105110
if (isWriting) {

0 commit comments

Comments
 (0)