Skip to content

Commit 87f3461

Browse files
committed
JBytes +UUID; benchmark BAOS ~ JDK ByteBuffer (pre allocate close to resulting bytes size)
1 parent 6d85784 commit 87f3461

File tree

5 files changed

+153
-33
lines changed

5 files changed

+153
-33
lines changed

src/main/java/com/trivago/fastutilconcurrentwrapper/io/BAIS.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,9 @@ public int readMedium () {
205205
public UUID readUUID () {
206206
if (count < pos + 16) throw new ArrayIndexOutOfBoundsException("readUUID, but "+available());// EOF
207207
//val bb = ByteBuffer.wrap(bytes); long mostSigBits = bb.getLong(); long leastSigBits = bb.getLong(); быстрее за счёт VarHandle
208-
long mostSigBits = readLong();// 0..7
209-
long leastSigBits = readLong();// 8..15
210-
return new UUID(mostSigBits, leastSigBits);
208+
val uuid = JBytes.DirectByteArrayAccess.getUUID(buf, pos);
209+
pos += 16;
210+
return uuid;
211211
}
212212

213213
@Override
@@ -290,6 +290,7 @@ public String readLatin1String (@PositiveOrZero int strLen) {
290290
}
291291

292292
/// @see java.nio.charset.StandardCharsets#UTF_16BE
293+
/// @see BAOS#writeChars
293294
public String readUTF16String (@PositiveOrZero int strLen) {
294295
int byteLen = Math.min(strLen << 1, available());//*2
295296
String s = new String(buf, pos, byteLen, UTF_16BE);

src/main/java/com/trivago/fastutilconcurrentwrapper/io/BAOS.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -282,16 +282,15 @@ public void writeBytes (String s) {
282282
int len = s.length();
283283
grow(len);
284284
s.getBytes(0, len, buf, position);
285-
position +=len;
285+
position += len;
286286
if (count < position) count = position;
287287
}
288288

289+
/// @see com.trivago.fastutilconcurrentwrapper.io.BAIS#readUTF16String
290+
/// @see #append
289291
@Override
290292
public void writeChars (String s) {
291-
for (int i = 0, len = s.length(); i < len; i++){
292-
int v = s.charAt(i);
293-
writeChar(v);
294-
}
293+
append(s, 0, s.length());
295294
}
296295

297296
@Override
@@ -393,9 +392,8 @@ public boolean equals (Object obj) {
393392
@Override
394393
public int hashCode () {
395394
int result = 1;
396-
for (int i = 0; i < count; i++){
397-
result = 31 * result + buf[i];
398-
}
395+
for (int i = 0; i < count; i++)
396+
result = 31 * result + buf[i];
399397
return result;
400398
}
401399
}

src/main/java/com/trivago/fastutilconcurrentwrapper/util/JBytes.java

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.lang.invoke.VarHandle;
1111
import java.nio.ByteOrder;
1212
import java.util.Arrays;
13+
import java.util.UUID;
1314
import java.util.function.IntBinaryOperator;
1415
import java.util.function.IntUnaryOperator;
1516

@@ -383,13 +384,12 @@ public static long getLong(byte[] array, int offset) {
383384
*
384385
* @param array to get a value from.
385386
* @param offset where extraction in the array should begin
386-
* @throws IndexOutOfBoundsException if the provided {@code offset} is outside
387-
* the range [0, array.length - 8]
387+
* @throws IndexOutOfBoundsException if the provided {@code offset} is outside the range [0, array.length - 8]
388388
* @see #setDouble(byte[], int, double)
389+
* @see #getDoubleRaw(byte[], int)
389390
*/
390391
public static double getDouble(byte[] array, int offset) {
391-
// Using Double.longBitsToDouble collapses NaN values to a single
392-
// "canonical" NaN value
392+
// Using Double.longBitsToDouble collapses NaN values to a single "canonical" NaN value
393393
return Double.longBitsToDouble((long) LONG.get(array, offset));
394394
}
395395

@@ -404,9 +404,9 @@ public static double getDouble(byte[] array, int offset) {
404404
*
405405
* @param array to get a value from.
406406
* @param offset where extraction in the array should begin
407-
* @throws IndexOutOfBoundsException if the provided {@code offset} is outside
408-
* the range [0, array.length - 8]
407+
* @throws IndexOutOfBoundsException if the provided {@code offset} is outside the range [0, array.length - 8]
409408
* @see #setDoubleRaw(byte[], int, double)
409+
* @see #getDouble(byte[], int)
410410
*/
411411
public static double getDoubleRaw(byte[] array, int offset) {
412412
// Just gets the bits as they are
@@ -594,7 +594,7 @@ public static void setDouble(byte[] array, int offset, double value) {
594594
* the range [0, array.length - 2]
595595
* @see #getDoubleRaw(byte[], int)
596596
*/
597-
public static void setDoubleRaw(byte[] array, int offset, double value) {
597+
public static void setDoubleRaw (byte[] array, int offset, double value) {
598598
// Just sets the bits as they are
599599
DOUBLE.set(array, offset, value);
600600
}
@@ -603,6 +603,17 @@ private static VarHandle create(Class<?> viewArrayClass) {
603603
return MethodHandles.byteArrayViewVarHandle(viewArrayClass, ByteOrder.BIG_ENDIAN);
604604
}
605605

606+
public static UUID getUUID (byte[] array, int offset) {
607+
long mostSigBits = getLong(array, offset);
608+
long leastSigBits = getLong(array, offset + 8);
609+
return new UUID(mostSigBits, leastSigBits);
610+
}
611+
612+
public static void setUUID (byte[] array, int offset, UUID uuid) {
613+
setLong(array, offset, uuid.getMostSignificantBits());
614+
setLong(array, offset + 8, uuid.getLeastSignificantBits());
615+
}
616+
606617
/**
607618
* Reads a {@code long} value from a {@link CharSequence} with given offset.
608619
*
@@ -630,14 +641,16 @@ public static int getInt (CharSequence cs, int off) {
630641
}
631642

632643
/**
633-
* Copies a given number of characters from a {@link CharSequence} into a byte array.
634-
*
635-
* @param cs a char sequence
636-
* @param offsetCharSequence an offset for the char sequence
637-
* @param toByteArray a byte array
638-
* @param offsetByteArray an offset for the byte array
639-
* @param numChars the number of characters to copy
640-
*/
644+
Copies a given number of characters from a {@link CharSequence} into a byte array.
645+
646+
😳 CharSequence vs String cs → speed is same
647+
648+
@param cs a char sequence
649+
@param offsetCharSequence an offset for the char sequence
650+
@param toByteArray a byte array
651+
@param offsetByteArray an offset for the byte array
652+
@param numChars the number of characters to copy
653+
*/
641654
public static void copyCharsToByteArray (
642655
CharSequence cs,
643656
int offsetCharSequence,
@@ -665,5 +678,5 @@ public static void copyCharsToByteArray (
665678
cs.charAt(offsetCharSequence + charIdx));
666679
}
667680
}
668-
}
681+
}//DirectByteArrayAccess
669682
}

src/test/java/com/trivago/fastutilconcurrentwrapper/io/BAOSTest.java

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.trivago.fastutilconcurrentwrapper.util.JBytes;
44
import it.unimi.dsi.fastutil.bytes.ByteArrays;
5+
import lombok.SneakyThrows;
56
import lombok.val;
67
import org.junit.jupiter.api.BeforeEach;
78
import org.junit.jupiter.api.DisplayName;
@@ -14,20 +15,22 @@
1415
import java.io.IOException;
1516
import java.io.Serializable;
1617
import java.io.UTFDataFormatException;
18+
import java.nio.ByteBuffer;
1719
import java.nio.charset.StandardCharsets;
1820
import java.util.ArrayList;
1921
import java.util.Arrays;
2022
import java.util.HashMap;
2123
import java.util.HexFormat;
2224
import java.util.Locale;
2325
import java.util.UUID;
26+
import java.util.concurrent.Callable;
2427
import java.util.stream.IntStream;
2528

2629
import static java.nio.charset.StandardCharsets.*;
2730
import static org.junit.jupiter.api.Assertions.*;
2831

2932
///@see BAOS
30-
class BAOSTest {
33+
public class BAOSTest {
3134
private BAOS stream;
3235

3336
static String toHex (byte[] b){
@@ -1004,10 +1007,10 @@ void _oos () throws IOException, ClassNotFoundException {
10041007
/// all: 2_281, op/s=219_195
10051008
@Test
10061009
void _benchmark () {
1007-
IntStream.range(0, 2).forEach(__->{
1010+
IntStream.range(0, 2).forEach(iteration->{
10081011
long t = System.nanoTime();
10091012
for (int loop = 1; loop < 500_000; loop++){
1010-
val os = new BAOS();
1013+
val os = new BAOS(iteration==0?160:9222);
10111014
val is = new BAIS(os.array());
10121015

10131016
for (int i = 0; i < 133; i++){
@@ -1046,10 +1049,10 @@ void _benchmark () {
10461049

10471050
@Test
10481051
void _benchmarkJDK () {
1049-
IntStream.range(0, 2).forEach(__->{
1052+
IntStream.range(0, 2).forEach(iteration->{
10501053
long t = System.nanoTime();
10511054
for (int loop = 1; loop < 500_000; loop++){
1052-
val baos = new BAOS();
1055+
val baos = new BAOS((iteration==0?160:9222));// ByteArrayOutputStream is very slow 😱
10531056
var os = new DataOutputStream(baos);
10541057
val is = new BAIS(baos.array());
10551058

@@ -1079,7 +1082,48 @@ void _benchmarkJDK () {
10791082
assertEquals(uuid, is.readUUID());
10801083
assertEquals("Answer42!", is.readLatin1String(9));
10811084
}
1082-
assertEquals(9222, is.array().length);
1085+
//assertEquals(9222, is.array().length);
1086+
assertEquals(6384, is.length());
1087+
assertEquals(6384, is.limit());
1088+
assertEquals(6384, is.position());
1089+
assertEquals(6384, is.readerIndex());
1090+
}//f
1091+
System.out.println(benchToStr(t, System.nanoTime(), 500_000));
1092+
});
1093+
}
1094+
1095+
@Test
1096+
void _benchmarkJDKBuffer () {
1097+
IntStream.range(0, 2).forEach(__->{
1098+
long t = System.nanoTime();
1099+
for (int loop = 1; loop < 500_000; loop++){
1100+
var bytes = new byte[9222];
1101+
var os = ByteBuffer.wrap(bytes);
1102+
val is = new BAIS(bytes);
1103+
1104+
for (int i = 0; i < 133; i++){
1105+
os.put((byte) 0xAB);
1106+
os.putShort((short) 0xCD_EF);
1107+
os.putInt(0x1234_5678);
1108+
os.putLong(0x91929394_95969798L);
1109+
os.putDouble(Math.PI);
1110+
os.putLong(uuid.getMostSignificantBits());
1111+
os.putLong(uuid.getLeastSignificantBits());
1112+
os.put("Answer42!".getBytes(ISO_8859_1));
1113+
}
1114+
assertEquals(6384, os.position());
1115+
1116+
is.array(bytes, 0, os.position());
1117+
1118+
for (int i = 0; i < 133; i++){
1119+
assertEquals(0xAB_CD_EF, is.readMedium());
1120+
assertEquals(0x1234_5678, is.readInt());
1121+
assertEquals(0x91929394_95969798L, is.readLong());
1122+
assertEquals(Math.PI, is.readDouble());
1123+
assertEquals(uuid, is.readUUID());
1124+
assertEquals("Answer42!", is.readLatin1String(9));
1125+
}
1126+
//assertEquals(9222, is.array().length);
10831127
assertEquals(6384, is.length());
10841128
assertEquals(6384, is.limit());
10851129
assertEquals(6384, is.position());
@@ -1095,6 +1139,28 @@ public static String benchToStr (long start, long end, long totalOperations) {
10951139
return String.format(Locale.ENGLISH, "%.3f, op/s=%.2f", elapsed/1000/1000.0, totalOperations * 1_000_000_000.0 / elapsed);
10961140
}
10971141

1142+
@SneakyThrows
1143+
public static void loop (long numberOfRepetitions, Callable<?> body){
1144+
while (numberOfRepetitions-- > 0)
1145+
body.call();// black hole - prevents JIT to cut out dead code
1146+
}
1147+
1148+
/** Similar to {@link #loop}, but with metrics and warm-up */
1149+
public static void loopMeasuredWarm (long numberOfRepetitions, Callable<?> body){
1150+
System.out.println("===== benchmark: "+ new Exception().getStackTrace()[1].toString());
1151+
long warmCnt = Math.min(50_000, numberOfRepetitions/10+10);
1152+
1153+
loop(warmCnt, body);
1154+
1155+
Runtime.getRuntime().gc();
1156+
1157+
long t = System.nanoTime();
1158+
loop(numberOfRepetitions, body);
1159+
System.out.println(benchToStr(t, System.nanoTime(), numberOfRepetitions));
1160+
}
1161+
1162+
1163+
10981164
@Test
10991165
void _ascii () {
11001166
val baos = new BAOS();
@@ -1184,4 +1250,16 @@ void _baos_as_StringBuilder () {
11841250
assertArrayEquals(w.toByteArray(), b.toString().getBytes(UTF_16BE));
11851251
assertEquals(w.length()/2, b.length());
11861252
}
1253+
1254+
@Test
1255+
void _writeChars () {
1256+
var w = new BAOS();
1257+
val s = "💡➕🤣⏩🚀".repeat(50);
1258+
w.writeChars(s);
1259+
assertEquals(s, w.toString(UTF_16BE));
1260+
var r = new BAIS(w);
1261+
assertEquals(s, r.readUTF16String(s.length()));
1262+
1263+
assertEquals(s.length()*2, w.size());// char = 2 bytes
1264+
}
11871265
}

src/test/java/com/trivago/fastutilconcurrentwrapper/util/JBytesTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package com.trivago.fastutilconcurrentwrapper.util;
22

3+
import com.trivago.fastutilconcurrentwrapper.io.BAIS;
4+
import com.trivago.fastutilconcurrentwrapper.io.BAOS;
5+
import com.trivago.fastutilconcurrentwrapper.io.BAOSTest;
36
import lombok.val;
47
import org.junit.jupiter.api.Test;
58

9+
import java.io.ByteArrayInputStream;
10+
import java.io.DataInputStream;
611
import java.util.HexFormat;
712
import java.util.concurrent.ThreadLocalRandom;
813
import java.util.function.IntBinaryOperator;
@@ -215,4 +220,29 @@ void _randomAccess () {
215220
JBytes.DirectByteArrayAccess.setInt(bytes, 7, 0xA1B2C3D4);//11-4==7
216221
assertEquals("007fffffffffffa1b2c3d4", HexFormat.of().formatHex(bytes));
217222
}
223+
224+
225+
@Test
226+
void doubleAndFloat () {
227+
var w = new BAOS();
228+
var x = new BAIS(w.array(), 0, 160);
229+
var y = new DataInputStream(new ByteArrayInputStream(w.array(), 0, 160));
230+
var r = ThreadLocalRandom.current();
231+
BAOSTest.loop(10_000, ()->{
232+
w.reset();
233+
double d = r.nextDouble();
234+
w.writeDouble(d);
235+
float f = r.nextFloat();
236+
w.writeFloat(f);
237+
238+
x.reset();
239+
y.reset();
240+
241+
assertEquals(d, x.readDouble());
242+
assertEquals(d, y.readDouble());
243+
assertEquals(f, x.readFloat());
244+
assertEquals(f, y.readFloat());
245+
return null;
246+
});
247+
}
218248
}

0 commit comments

Comments
 (0)