Skip to content

Commit 6ba8cc9

Browse files
htmldougrashtao
authored andcommitted
Improve performance 20%-110% (#14)
* Add jmh. * Add first benchmark. * Optimization: 5.735 => 5.489 * VPackBuilder optimizations: 2830 => 1358 ns/op * Fast path for ASCII 1358 => 946 ns/op * 985 => 896 ns/op * VPackSlice 1000 => 745 ns/op * VPackSlice 745 =>> 720 ns/op * Revert ASCII-specific optimizations 852 => 1386 ns/op * Remove unnecessary "throws VPackBuilderException" * PR comments: avoid unchecked cast for generic array. * PR comments: Remove spf4j-jmh. Resolve licensing concerns. * PR comments: Fix javadoc things.
1 parent eb5715b commit 6ba8cc9

File tree

10 files changed

+8209
-652
lines changed

10 files changed

+8209
-652
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/target
55
/.idea
66
/*.iml
7+
*.jfr

pom.xml

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<logback-classic.version>1.1.3</logback-classic.version>
2727
<hamcrest-all.version>1.3</hamcrest-all.version>
2828
<junit.version>4.12</junit.version>
29+
<jmh.version>1.21</jmh.version>
2930
</properties>
3031

3132
<developers>
@@ -104,8 +105,11 @@
104105
<artifactId>maven-compiler-plugin</artifactId>
105106
<version>3.2</version>
106107
<configuration>
107-
<source>1.6</source>
108-
<target>1.6</target>
108+
<source>1.7</source>
109+
<target>1.7</target>
110+
<compilerArgs>
111+
<arg>-Werror</arg>
112+
</compilerArgs>
109113
<compilerArgument></compilerArgument>
110114
</configuration>
111115
</plugin>
@@ -213,6 +217,16 @@
213217
<artifactId>hamcrest-all</artifactId>
214218
<scope>test</scope>
215219
</dependency>
220+
<dependency>
221+
<groupId>org.openjdk.jmh</groupId>
222+
<artifactId>jmh-core</artifactId>
223+
<scope>test</scope>
224+
</dependency>
225+
<dependency>
226+
<groupId>org.openjdk.jmh</groupId>
227+
<artifactId>jmh-generator-annprocess</artifactId>
228+
<scope>test</scope>
229+
</dependency>
216230
</dependencies>
217231

218232
<dependencyManagement>
@@ -244,6 +258,18 @@
244258
<artifactId>hamcrest-all</artifactId>
245259
<version>${hamcrest-all.version}</version>
246260
</dependency>
261+
<dependency>
262+
<groupId>org.openjdk.jmh</groupId>
263+
<artifactId>jmh-core</artifactId>
264+
<version>${jmh.version}</version>
265+
<scope>test</scope>
266+
</dependency>
267+
<dependency>
268+
<groupId>org.openjdk.jmh</groupId>
269+
<artifactId>jmh-generator-annprocess</artifactId>
270+
<version>${jmh.version}</version>
271+
<scope>test</scope>
272+
</dependency>
247273
</dependencies>
248274
</dependencyManagement>
249275

src/main/java/com/arangodb/velocypack/VPackBuilder.java

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.io.UnsupportedEncodingException;
2424
import java.math.BigDecimal;
2525
import java.math.BigInteger;
26+
import java.nio.charset.StandardCharsets;
2627
import java.sql.Timestamp;
2728
import java.util.ArrayList;
2829
import java.util.Arrays;
@@ -46,6 +47,7 @@
4647
import com.arangodb.velocypack.internal.DefaultVPackBuilderOptions;
4748
import com.arangodb.velocypack.internal.Value;
4849
import com.arangodb.velocypack.internal.util.NumberUtil;
50+
import com.fasterxml.jackson.core.io.CharTypes;
4951

5052
/**
5153
* @author Mark Vollmary
@@ -216,7 +218,7 @@ public void append(final VPackBuilder builder, final VPackSlice value) throws VP
216218
private int size;
217219
private final List<Integer> stack; // Start positions of open
218220
// objects/arrays
219-
private final Map<Integer, List<Integer>> index; // Indices for starts
221+
private List<List<Integer>> index; // Indices for starts
220222
// of
221223
// subindex
222224
private boolean keyWritten; // indicates that in the current object the key
@@ -233,7 +235,7 @@ public VPackBuilder(final BuilderOptions options) {
233235
size = 0;
234236
buffer = new byte[10];
235237
stack = new ArrayList<Integer>();
236-
index = new HashMap<Integer, List<Integer>>();
238+
index = new ArrayList<List<Integer>>(4);
237239
}
238240

239241
public BuilderOptions getOptions() {
@@ -546,7 +548,7 @@ private void set(final Value item) throws VPackBuilderException {
546548
throw new VPackBuilderUnexpectedValueException(ValueType.UINT, Long.class, Integer.class,
547549
BigInteger.class);
548550
}
549-
if (-1 == vUInt.compareTo(BigInteger.ZERO)) {
551+
if (vUInt.compareTo(BigInteger.ZERO) < 0) {
550552
throw new VPackBuilderUnexpectedValueException(ValueType.UINT, "non-negative", Long.class,
551553
Integer.class, BigInteger.class);
552554
}
@@ -620,22 +622,18 @@ private void appendSQLTimestamp(final Timestamp value) {
620622
append(value.getTime(), LONG_BYTES);
621623
}
622624

623-
private void appendString(final String value) throws VPackBuilderException {
624-
try {
625-
final byte[] bytes = value.getBytes("UTF-8");
626-
final int length = bytes.length;
627-
if (length <= 126) {
628-
// short string
629-
add((byte) (0x40 + length));
630-
} else {
631-
// long string
632-
add((byte) 0xbf);
633-
appendLength(length);
634-
}
635-
appendString(bytes);
636-
} catch (final UnsupportedEncodingException e) {
637-
throw new VPackBuilderException(e);
625+
private void appendString(final String value) {
626+
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
627+
final int length = bytes.length;
628+
if (length <= 126) {
629+
// short string
630+
add((byte) (0x40 + length));
631+
} else {
632+
// long string
633+
add((byte) 0xbf);
634+
appendLength(length);
638635
}
636+
appendString(bytes);
639637
}
640638

641639
private void appendString(final byte[] bytes) {
@@ -670,7 +668,7 @@ private void addObject(final boolean unindexed) {
670668
private void addCompoundValue(final byte head) {
671669
// an Array or Object is started:
672670
stack.add(size);
673-
index.put(stack.size() - 1, new ArrayList<Integer>());
671+
index.add(stack.size() - 1, new ArrayList<Integer>());
674672
add(head);
675673
// Will be filled later with bytelength and nr subs
676674
size += 8;
@@ -990,10 +988,10 @@ private VPackBuilder closeArray(final int tos, final List<Integer> in) {
990988
}
991989

992990
private static class SortEntry {
993-
private final VPackSlice slice;
991+
private final VPackStringSlice slice;
994992
private final int offset;
995993

996-
public SortEntry(final VPackSlice slice, final int offset) {
994+
public SortEntry(final VPackStringSlice slice, final int offset) {
997995
super();
998996
this.slice = slice;
999997
this.offset = offset;
@@ -1002,17 +1000,18 @@ public SortEntry(final VPackSlice slice, final int offset) {
10021000

10031001
private void sortObjectIndex(final int start, final List<Integer> offsets)
10041002
throws VPackKeyTypeException, VPackNeedAttributeTranslatorException {
1005-
final List<VPackBuilder.SortEntry> attributes = new ArrayList<VPackBuilder.SortEntry>();
1006-
for (final Integer offset : offsets) {
1007-
attributes.add(new SortEntry(new VPackSlice(buffer, start + offset).makeKey(), offset));
1003+
VPackBuilder.SortEntry[] attributes = new VPackBuilder.SortEntry[offsets.size()];
1004+
for (int i = 0; i < offsets.size(); i++) {
1005+
Integer offset = offsets.get(i);
1006+
attributes[i] = new SortEntry(new VPackSlice(buffer, start + offset).makeKey().getAsStringSlice(), offset);
10081007
}
10091008
final Comparator<SortEntry> comparator = new Comparator<SortEntry>() {
10101009
@Override
10111010
public int compare(final SortEntry o1, final SortEntry o2) {
1012-
return o1.slice.getAsString().compareTo(o2.slice.getAsString());
1011+
return o1.slice.compareTo(o2.slice);
10131012
}
10141013
};
1015-
Collections.sort(attributes, comparator);
1014+
Arrays.sort(attributes, comparator);
10161015
offsets.clear();
10171016
for (final SortEntry sortEntry : attributes) {
10181017
offsets.add(sortEntry.offset);

src/main/java/com/arangodb/velocypack/VPackSlice.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
import java.math.BigDecimal;
2525
import java.math.BigInteger;
2626
import java.nio.charset.Charset;
27+
import java.nio.charset.StandardCharsets;
2728
import java.util.Arrays;
2829
import java.util.Date;
2930
import java.util.Iterator;
3031
import java.util.Map.Entry;
32+
import java.lang.Comparable;
3133

3234
import com.arangodb.velocypack.exception.VPackException;
3335
import com.arangodb.velocypack.exception.VPackKeyTypeException;
@@ -294,6 +296,10 @@ public java.sql.Timestamp getAsSQLTimestamp() {
294296
}
295297

296298
public String getAsString() {
299+
return getAsStringSlice().toString();
300+
}
301+
302+
public VPackStringSlice getAsStringSlice() {
297303
if (!isString()) {
298304
throw new VPackValueTypeException(ValueType.STRING);
299305
}
@@ -308,12 +314,12 @@ private boolean isLongString() {
308314
return head() == (byte) 0xbf;
309315
}
310316

311-
private String getShortString() {
312-
return new String(vpack, start + 1, length(), Charset.forName("UTF-8"));
317+
private VPackStringSlice getShortString() {
318+
return new VPackStringSlice(vpack, start + 1, length());
313319
}
314320

315-
private String getLongString() {
316-
return new String(vpack, start + 9, getLongStringLength(), Charset.forName("UTF-8"));
321+
private VPackStringSlice getLongString() {
322+
return new VPackStringSlice(vpack, start + 9, getLongStringLength());
317323
}
318324

319325
private int getLongStringLength() {
@@ -574,6 +580,7 @@ private VPackSlice searchObjectKeyBinary(
574580
long l = 0;
575581
long r = n - 1;
576582

583+
byte[] attributeBytes = attribute.getBytes(StandardCharsets.UTF_8);
577584
for (;;) {
578585
// midpoint
579586
final long index = l + ((r - l) / 2);
@@ -582,14 +589,14 @@ private VPackSlice searchObjectKeyBinary(
582589
final VPackSlice key = new VPackSlice(vpack, (int) (start + keyIndex));
583590
int res = 0;
584591
if (key.isString()) {
585-
res = key.compareString(attribute);
592+
res = key.getAsStringSlice().compareToBytes(attributeBytes);
586593
} else if (key.isInteger()) {
587594
// translate key
588595
if (!useTranslator) {
589596
// no attribute translator
590597
throw new VPackNeedAttributeTranslatorException();
591598
}
592-
res = key.translateUnchecked().compareString(attribute);
599+
res = key.translateUnchecked().getAsStringSlice().compareToBytes(attributeBytes);
593600
} else {
594601
// invalid key
595602
result = new VPackSlice();
@@ -816,4 +823,5 @@ public boolean equals(final Object obj) {
816823
return true;
817824
}
818825

826+
819827
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.arangodb.velocypack;
2+
3+
import java.nio.charset.StandardCharsets;
4+
5+
/**
6+
* Wrapper around a {@link ValueType#STRING} supporting fast bytewise comparison.
7+
*
8+
* @see <a href="https://github.com/arangodb/velocypack/blob/master/VelocyPack.md#objects">VelocyPack Objects</a>
9+
*/
10+
public class VPackStringSlice implements Comparable<VPackStringSlice> {
11+
private byte[] vpack;
12+
/**
13+
* Index of the string bytes within {@link vpack},
14+
* i.e. tag byte and length are somewhere before this index.
15+
*/
16+
private int start;
17+
private int length;
18+
19+
public VPackStringSlice(byte[] vpack, int start, int length) {
20+
this.vpack = vpack;
21+
this.start = start;
22+
this.length = length;
23+
}
24+
25+
@Override
26+
public int compareTo(VPackStringSlice o) {
27+
return compareToBytes(o.vpack, o.start, o.length);
28+
}
29+
30+
public int compareToBytes(byte[] other) {
31+
return compareToBytes(other, 0, other.length);
32+
}
33+
34+
public int compareToBytes(byte[] other, int off, int oLen) {
35+
for (int i = 0; i < length && i < oLen; i++) {
36+
int c = (vpack[start + i] & 0xff) - (other[off + i] & 0xff);
37+
if (c != 0) return c;
38+
}
39+
return length - oLen;
40+
}
41+
42+
@Override
43+
public String toString() {
44+
return new String(vpack, start, length, StandardCharsets.UTF_8);
45+
}
46+
}

0 commit comments

Comments
 (0)