Skip to content

Commit 290719d

Browse files
vbabaninjyeminNathanQingyangXustIncMale
authored
Optimize String length computation. (#1685)
JAVA-5842 --------- Co-authored-by: Jeff Yemin <[email protected]> Co-authored-by: Nathan Xu <[email protected]> Co-authored-by: Valentin Kovalenko <[email protected]>
1 parent 144d05e commit 290719d

File tree

1 file changed

+56
-2
lines changed

1 file changed

+56
-2
lines changed

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

+56-2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public String readString() {
134134

135135
@Override
136136
public String readCString() {
137+
ensureOpen();
137138
int size = computeCStringLength(buffer.position());
138139
return readString(size);
139140
}
@@ -182,11 +183,64 @@ public void skipCString() {
182183
buffer.position(pos + length);
183184
}
184185

186+
/**
187+
* Detects the position of the first NULL (0x00) byte in a 64-bit word using SWAR technique.
188+
* <a href="https://en.wikipedia.org/wiki/SWAR">
189+
*/
185190
private int computeCStringLength(final int prevPos) {
186-
ensureOpen();
187-
int pos = buffer.position();
191+
int pos = prevPos;
188192
int limit = buffer.limit();
189193

194+
// `>>> 3` means dividing without remainder by `Long.BYTES` because `Long.BYTES` is 2^3
195+
int chunks = (limit - pos) >>> 3;
196+
// `<< 3` means multiplying by `Long.BYTES` because `Long.BYTES` is 2^3
197+
int toPos = pos + (chunks << 3);
198+
for (; pos < toPos; pos += Long.BYTES) {
199+
long chunk = buffer.getLong(pos);
200+
/*
201+
Subtract 0x0101010101010101L to cause a borrow on 0x00 bytes.
202+
if original byte is 00000000, then 00000000 - 00000001 = 11111111 (borrow causes the most significant bit set to 1).
203+
*/
204+
long mask = chunk - 0x0101010101010101L;
205+
/*
206+
mask will only have the most significant bit in each byte set iff it was a 0x00 byte (0x00 becomes 0xFF because of the borrow).
207+
~chunk will have bits that were originally 0 set to 1.
208+
mask & ~chunk will have the most significant bit in each byte set iff original byte was 0x00.
209+
*/
210+
mask &= ~chunk;
211+
/*
212+
0x8080808080808080:
213+
10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000
214+
215+
mask:
216+
00000000 00000000 11111111 00000000 00000001 00000001 00000000 00000111
217+
218+
ANDing mask with 0x8080808080808080 isolates the most significant bit in each byte where
219+
the original byte was 0x00, thereby setting the most significant bit to 1 in each 0x00 original byte.
220+
221+
result:
222+
00000000 00000000 10000000 00000000 00000000 00000000 00000000 00000000
223+
^^^^^^^^
224+
The most significant bit is set in each 0x00 byte, and only there.
225+
*/
226+
mask &= 0x8080808080808080L;
227+
if (mask != 0) {
228+
/*
229+
The UTF-8 data is endian-independent and stored in left-to-right order in the buffer, with the first byte at the lowest index.
230+
After calling getLong() in little-endian mode, the first UTF-8 byte ends up in the least significant byte of the long (bits 0–7),
231+
and the last one in the most significant byte (bits 56–63).
232+
233+
numberOfTrailingZeros scans from the least significant bit, which aligns with the position of the first UTF-8 byte.
234+
We then use >>> 3, which means dividing without remainder by Long.BYTES because Long.BYTES is 2^3, computing the byte offset
235+
of the NULL terminator in the original UTF-8 data.
236+
*/
237+
int offset = Long.numberOfTrailingZeros(mask) >>> 3;
238+
// Find the NULL terminator at pos + offset
239+
return (pos - prevPos) + offset + 1;
240+
}
241+
}
242+
243+
// Process remaining bytes one by one.
190244
while (pos < limit) {
191245
if (buffer.get(pos++) == 0) {
192246
return (pos - prevPos);

0 commit comments

Comments
 (0)