Removes the following allocations:
- ``CharsetDecoder`` is reused between calls
- ``CharBuffer#wrap`` removed in favor of heap
  based char buffer that is reused
- Temporary ``char[]``, an intermediate copy inside ``StringCoding``
- Another ``char[]``, this is needed because ``StringCoding`` uses
  a ``CharBuffer`` internally but returns a ``char[]``.  Extra
  characters need to be trimmed so this means yet another allocation
- Yet another ``char[]`` directly from ``__string`` for non-heap
  based buffers

Removes the following copies
- No copy is performed to trim the allocation since a ``CharBuffer``
  is used directly
- For non-heap based byte buffers, removes the copy that was
  previously done in the __string function

This does need to get the TLS entry which implies at least some
contention on the thread object table and a fence.
This commit is contained in:
pjulien 2016-04-18 20:02:27 -04:00
parent 6f751d5d26
commit 286587d151
1 changed files with 38 additions and 12 deletions

50
java/com/google/flatbuffers/Table.java Normal file → Executable file
View File

@ -19,6 +19,11 @@ package com.google.flatbuffers;
import static com.google.flatbuffers.Constants.*; import static com.google.flatbuffers.Constants.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
/// @cond FLATBUFFERS_INTERNAL /// @cond FLATBUFFERS_INTERNAL
@ -26,6 +31,13 @@ import java.nio.ByteOrder;
* All tables in the generated code derive from this class, and add their own accessors. * All tables in the generated code derive from this class, and add their own accessors.
*/ */
public class Table { public class Table {
private final static ThreadLocal<CharsetDecoder> UTF8_DECODER = new ThreadLocal<CharsetDecoder>() {
@Override
protected CharsetDecoder initialValue() {
return Charset.forName("UTF-8").newDecoder();
}
};
private final static ThreadLocal<CharBuffer> CHAR_BUFFER = new ThreadLocal<CharBuffer>();
/** Used to hold the position of the `bb` buffer. */ /** Used to hold the position of the `bb` buffer. */
protected int bb_pos; protected int bb_pos;
/** The underlying ByteBuffer to hold the data of the Table. */ /** The underlying ByteBuffer to hold the data of the Table. */
@ -71,20 +83,34 @@ public class Table {
* @return Returns a `String` from the data stored inside the FlatBuffer at `offset`. * @return Returns a `String` from the data stored inside the FlatBuffer at `offset`.
*/ */
protected String __string(int offset) { protected String __string(int offset) {
CharsetDecoder decoder = UTF8_DECODER.get();
decoder.reset();
offset += bb.getInt(offset); offset += bb.getInt(offset);
if (bb.hasArray()) { ByteBuffer src = bb.duplicate().order(ByteOrder.LITTLE_ENDIAN);
return new String(bb.array(), bb.arrayOffset() + offset + SIZEOF_INT, bb.getInt(offset), int length = src.getInt(offset);
FlatBufferBuilder.utf8charset); src.position(offset + SIZEOF_INT);
} else { src.limit(offset + SIZEOF_INT + length);
// We can't access .array(), since the ByteBuffer is read-only,
// off-heap or a memory map int required = (int)((float)length * decoder.maxCharsPerByte());
ByteBuffer bb = this.bb.duplicate().order(ByteOrder.LITTLE_ENDIAN); CharBuffer dst = CHAR_BUFFER.get();
// We're forced to make an extra copy: if (dst == null || dst.capacity() < required) {
byte[] copy = new byte[bb.getInt(offset)]; dst = CharBuffer.allocate(Math.max(required, 128));
bb.position(offset + SIZEOF_INT); CHAR_BUFFER.set(dst);
bb.get(copy);
return new String(copy, 0, copy.length, FlatBufferBuilder.utf8charset);
} }
dst.clear();
try {
CoderResult cr = decoder.decode(src, dst, true);
if (!cr.isUnderflow()) {
cr.throwException();
}
} catch (CharacterCodingException x) {
throw new Error(x);
}
return dst.flip().toString();
} }
/** /**