diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2023-03-17 18:34:02 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-17 18:34:02 +0100 |
commit | accbdfde039341d9dbd815398d1bd8f91bc5939c (patch) | |
tree | 6389f4156480bf2abef447566e8d77c6a4004eb1 /vespajlib/src | |
parent | edcdc5e2869033f8219ad21204adb2a1bc12c388 (diff) | |
parent | 102b25342eb6f4bf4b7e20218d476cfce4bb9be8 (diff) |
Merge pull request #26472 from vespa-engine/havardpe/auto-detect-native-arrays
auto-detect arrays of long or double
Diffstat (limited to 'vespajlib/src')
-rw-r--r-- | vespajlib/src/main/java/com/yahoo/slime/ArrayValue.java | 108 | ||||
-rw-r--r-- | vespajlib/src/test/java/com/yahoo/slime/ArrayValueTestCase.java | 188 |
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))); + } + } + } +} |