summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2023-03-17 18:34:02 +0100
committerGitHub <noreply@github.com>2023-03-17 18:34:02 +0100
commitaccbdfde039341d9dbd815398d1bd8f91bc5939c (patch)
tree6389f4156480bf2abef447566e8d77c6a4004eb1
parentedcdc5e2869033f8219ad21204adb2a1bc12c388 (diff)
parent102b25342eb6f4bf4b7e20218d476cfce4bb9be8 (diff)
Merge pull request #26472 from vespa-engine/havardpe/auto-detect-native-arrays
auto-detect arrays of long or double
-rw-r--r--vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java108
-rw-r--r--vespajlib/src/test/java/com/yahoo/slime/ArrayValueTestCase.java188
2 files changed, 279 insertions, 17 deletions
diff --git a/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java b/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java
index dbd9771afe9..9f455a5b7d4 100644
--- a/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java
+++ b/vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java
@@ -6,9 +6,94 @@ package com.yahoo.slime;
*/
final class ArrayValue extends Value {
- private int capacity = 16;
+ static final int initial_capacity = 16;
+ static final Impl initial_impl = new EmptyImpl();
+
+ private interface Impl {
+ public void prepareFor(ArrayValue self, Type type);
+ public Value add(Value value, int used);
+ public Value get(int index);
+ }
+
+ private static final class EmptyImpl implements Impl {
+ public void prepareFor(ArrayValue self, Type type) {
+ if (type == Type.LONG) {
+ self.impl = new LongImpl();
+ } else if (type == Type.DOUBLE) {
+ self.impl = new DoubleImpl();
+ } else {
+ self.impl = new GenericImpl(this, 0);
+ }
+ }
+ public Value add(Value value, int used) { return NixValue.invalid(); }
+ public Value get(int index) { return NixValue.invalid(); }
+ }
+
+ private static final class LongImpl implements Impl {
+ private long[] values = new long[initial_capacity];
+ public void prepareFor(ArrayValue self, Type type) {
+ if (type != Type.LONG) {
+ self.impl = new GenericImpl(this, self.used);
+ }
+ }
+ public Value add(Value value, int used) {
+ if (used == values.length) {
+ long[] v = values;
+ values = new long[v.length << 1];
+ System.arraycopy(v, 0, values, 0, used);
+ }
+ values[used] = value.asLong();
+ return get(used);
+ }
+ public Value get(int index) { return new LongValue(values[index]); }
+ }
+
+ private static final class DoubleImpl implements Impl {
+ private double[] values = new double[initial_capacity];
+ public void prepareFor(ArrayValue self, Type type) {
+ if (type != Type.DOUBLE) {
+ self.impl = new GenericImpl(this, self.used);
+ }
+ }
+ public Value add(Value value, int used) {
+ if (used == values.length) {
+ double[] v = values;
+ values = new double[v.length << 1];
+ System.arraycopy(v, 0, values, 0, used);
+ }
+ values[used] = value.asDouble();
+ return get(used);
+ }
+ public Value get(int index) { return new DoubleValue(values[index]); }
+ }
+
+ private static final class GenericImpl implements Impl {
+ private Value[] values;
+ GenericImpl(Impl src, int len) {
+ int capacity = initial_capacity;
+ while (capacity < (len + 1)) {
+ capacity = capacity << 1;
+ }
+ values = new Value[capacity];
+ for (int i = 0; i < len; i++) {
+ values[i] = src.get(i);
+ }
+ }
+ public void prepareFor(ArrayValue self, Type type) {}
+ public Value add(Value value, int used) {
+ if (used == values.length) {
+ Value[] v = values;
+ values = new Value[v.length << 1];
+ System.arraycopy(v, 0, values, 0, used);
+ }
+ values[used] = value;
+ return get(used);
+ }
+ public Value get(int index) { return values[index]; }
+ }
+
+ private Impl impl = initial_impl;
private int used = 0;
- private Value[] values = new Value[capacity];
private final SymbolTable names;
public ArrayValue(SymbolTable names) { this.names = names; }
@@ -16,33 +101,22 @@ final class ArrayValue extends Value {
public int children() { return used; }
public int entries() { return used; }
public Value entry(int index) {
- return (index < used) ? values[index] : NixValue.invalid();
+ return (index >= 0 && index < used) ? impl.get(index) : NixValue.invalid();
}
public void accept(Visitor v) { v.visitArray(this); }
public void traverse(ArrayTraverser at) {
for (int i = 0; i < used; i++) {
- at.entry(i, values[i]);
+ at.entry(i, impl.get(i));
}
}
- private void grow() {
- Value[] v = values;
- capacity = (capacity << 1);
- values = new Value[capacity];
- System.arraycopy(v, 0, values, 0, used);
- }
-
protected Value addLeaf(Value value) {
- if (used == capacity) {
- grow();
- }
- values[used++] = value;
- return value;
+ impl.prepareFor(this, value.type());
+ return impl.add(value, used++);
}
public Value addArray() { return addLeaf(new ArrayValue(names)); }
public Value addObject() { return addLeaf(new ObjectValue(names)); }
-
}
diff --git a/vespajlib/src/test/java/com/yahoo/slime/ArrayValueTestCase.java b/vespajlib/src/test/java/com/yahoo/slime/ArrayValueTestCase.java
new file mode 100644
index 00000000000..c9ff86e7c2e
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/slime/ArrayValueTestCase.java
@@ -0,0 +1,188 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.slime;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class ArrayValueTestCase {
+
+ static ArrayValue makeArray() {
+ return new ArrayValue(new SymbolTable());
+ }
+
+ @Test
+ public void testSymbolTableForwarding() {
+ SymbolTable names = new SymbolTable();
+ assertThat(names.symbols(), is(0));
+ new ArrayValue(names).addArray().addObject().setLong("foo", 3);
+ assertThat(names.symbols(), is(1));
+ }
+
+ @Test
+ public void testOutOfBoundsAccess() {
+ var array = makeArray();
+ array.addBool(true);
+ assertThat(array.entry(-1).valid(), is(false));
+ assertThat(array.entry(1).valid(), is(false));
+ }
+
+ @Test
+ public void testGenericArray() {
+ var array = makeArray();
+ var added = new ArrayList<Cursor>();
+ for (int i = 0; i < 128; ++i) {
+ added.add(array.addString("foo" + i));
+ }
+ for (int i = 0; i < 128; i++) {
+ var e1 = array.entry(i);
+ var e2 = array.entry(i);
+ var e3 = added.get(i);
+ assertThat(e1, sameInstance(e2));
+ assertThat(e1, sameInstance(e3));
+ }
+ }
+
+ @Test
+ public void testNativeLongArray() {
+ var array = makeArray();
+ var added = new ArrayList<Cursor>();
+ for (int i = 0; i < 128; ++i) {
+ added.add(array.addLong(i));
+ }
+ for (int i = 0; i < 128; ++i) {
+ long expect = i;
+ var e1 = array.entry(i);
+ var e2 = array.entry(i);
+ var e3 = added.get(i);
+ assertThat(e1, not(sameInstance(e2)));
+ assertThat(e1, not(sameInstance(e3)));
+ assertThat(e1.equalTo(e2), is(true));
+ assertThat(e1.equalTo(e3), is(true));
+ assertThat(e1.type(), is(Type.LONG));
+ assertThat(e1.asLong(), is(expect));
+ }
+ }
+
+ @Test
+ public void testNativeDoubleArray() {
+ var array = makeArray();
+ var added = new ArrayList<Cursor>();
+ for (int i = 0; i < 128; ++i) {
+ added.add(array.addDouble((double)i));
+ }
+ for (int i = 0; i < 128; ++i) {
+ double expect = i;
+ var e1 = array.entry(i);
+ var e2 = array.entry(i);
+ var e3 = added.get(i);
+ assertThat(e1, not(sameInstance(e2)));
+ assertThat(e1, not(sameInstance(e3)));
+ assertThat(e1.equalTo(e2), is(true));
+ assertThat(e1.equalTo(e3), is(true));
+ assertThat(e1.type(), is(Type.DOUBLE));
+ assertThat(e1.asDouble(), is(expect));
+ }
+ }
+
+ @Test
+ public void testLongToGenericConversion() {
+ for (Type type: Type.values()) {
+ if (type != Type.LONG) {
+ var array = makeArray();
+ var added = new ArrayList<Cursor>();
+ for (int i = 0; i < 64; ++i) {
+ added.add(array.addLong(i));
+ }
+ switch (type) {
+ case NIX: added.add(array.addNix()); break;
+ case BOOL: added.add(array.addBool(true)); break;
+ case DOUBLE: added.add(array.addDouble(42.0)); break;
+ case STRING: added.add(array.addString("foo")); break;
+ case DATA: added.add(array.addData(new byte[1])); break;
+ case ARRAY: added.add(array.addArray()); break;
+ case OBJECT: added.add(array.addObject()); break;
+ }
+ assertThat(array.entries(), is(65));
+ assertThat(array.entry(64).type(), is(type));
+ assertThat(added.get(64), sameInstance(array.entry(64)));
+ for (int i = 0; i < 64; ++i) {
+ var e1 = array.entry(i);
+ var e2 = array.entry(i);
+ var e3 = added.get(i);
+ long expect = i;
+ assertThat(e1, sameInstance(e2));
+ assertThat(e1, not(sameInstance(e3)));
+ assertThat(e1.equalTo(e2), is(true));
+ assertThat(e1.equalTo(e3), is(true));
+ assertThat(e1.type(), is(Type.LONG));
+ assertThat(e1.asLong(), is(expect));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testDoubleToGenericConversion() {
+ for (Type type: Type.values()) {
+ if (type != Type.DOUBLE) {
+ var array = makeArray();
+ var added = new ArrayList<Cursor>();
+ for (int i = 0; i < 64; ++i) {
+ added.add(array.addDouble(i));
+ }
+ switch (type) {
+ case NIX: added.add(array.addNix()); break;
+ case BOOL: added.add(array.addBool(true)); break;
+ case LONG: added.add(array.addLong(42)); break;
+ case STRING: added.add(array.addString("foo")); break;
+ case DATA: added.add(array.addData(new byte[1])); break;
+ case ARRAY: added.add(array.addArray()); break;
+ case OBJECT: added.add(array.addObject()); break;
+ }
+ assertThat(array.entries(), is(65));
+ assertThat(array.entry(64).type(), is(type));
+ assertThat(added.get(64), sameInstance(array.entry(64)));
+ for (int i = 0; i < 64; ++i) {
+ var e1 = array.entry(i);
+ var e2 = array.entry(i);
+ var e3 = added.get(i);
+ double expect = i;
+ assertThat(e1, sameInstance(e2));
+ assertThat(e1, not(sameInstance(e3)));
+ assertThat(e1.equalTo(e2), is(true));
+ assertThat(e1.equalTo(e3), is(true));
+ assertThat(e1.type(), is(Type.DOUBLE));
+ assertThat(e1.asDouble(), is(expect));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testGenericArrayStart() {
+ for (Type type: Type.values()) {
+ if (type != Type.LONG && type != Type.DOUBLE) {
+ var array = makeArray();
+ Cursor added = null;
+ switch (type) {
+ case NIX: added = array.addNix(); break;
+ case BOOL: added = array.addBool(true); break;
+ case STRING: added = array.addString("foo"); break;
+ case DATA: added = array.addData(new byte[1]); break;
+ case ARRAY: added = array.addArray(); break;
+ case OBJECT: added = array.addObject(); break;
+ }
+ assertThat(array.entries(), is(1));
+ assertThat(array.entry(0).type(), is(type));
+ assertThat(added, sameInstance(array.entry(0)));
+ }
+ }
+ }
+}