summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java2
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java26
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java308
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java2
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/Tensor.java23
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java129
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/TensorType.java5
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java83
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/functions/Reduce.java10
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/functions/Rename.java4
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/serialization/SparseBinaryFormat.java5
-rw-r--r--vespajlib/src/test/java/com/yahoo/tensor/IndexedTensorTestCase.java12
-rw-r--r--vespajlib/src/test/java/com/yahoo/tensor/JoinTestCase.java36
-rw-r--r--vespajlib/src/test/java/com/yahoo/tensor/TensorFunctionBenchmark.java45
-rw-r--r--vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java28
15 files changed, 540 insertions, 178 deletions
diff --git a/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java b/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
index e1444c5145f..8c6ee898c0e 100644
--- a/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
+++ b/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
@@ -99,7 +99,7 @@ public class JsonSerializationHelper {
private static void serializeTensorAddress(JsonGenerator generator, TensorAddress address, TensorType type) throws IOException {
generator.writeObjectFieldStart(JsonReader.TENSOR_ADDRESS);
for (int i = 0; i < type.dimensions().size(); i++)
- generator.writeStringField(type.dimensions().get(i).name(), address.labels().get(i));
+ generator.writeStringField(type.dimensions().get(i).name(), address.label(i));
generator.writeEndObject();
}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java
index 6b67fca791d..6d038113691 100644
--- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java
@@ -23,16 +23,9 @@ public class TensorValue extends Value {
/** The tensor value of this */
private final Tensor value;
- private final Optional<TensorType> type;
-
+
public TensorValue(Tensor value) {
this.value = value;
- this.type = Optional.empty();
- }
-
- public TensorValue(Tensor value, TensorType type) {
- this.value = value;
- this.type = Optional.of(type);
}
@Override
@@ -97,10 +90,6 @@ public class TensorValue extends Value {
public Tensor asTensor() { return value; }
- public Optional<TensorType> getType() {
- return type;
- }
-
@Override
public Value compare(TruthOperator operator, Value argument) {
return new TensorValue(compareTensor(operator, asTensor(argument, operator.toString())));
@@ -150,18 +139,13 @@ public class TensorValue extends Value {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- TensorValue that = (TensorValue) o;
-
- if (!type.equals(that.type)) return false;
- if (!value.equals(that.value)) return false;
-
- return true;
+ TensorValue other = (TensorValue) o;
+ return value.equals(other.value);
}
@Override
public int hashCode() {
- int result = value.hashCode();
- result = 31 * result + type.hashCode();
- return result;
+ return value.hashCode();
}
+
}
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java
index 4091fd97a16..1ebd6c4179d 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java
@@ -12,6 +12,7 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
+import java.util.Set;
/**
* An indexed (dense) tensor backed by a double array.
@@ -60,6 +61,20 @@ public class IndexedTensor implements Tensor {
return new ValueIterator();
}
+ /**
+ * Returns an iterator over value iterators where the outer iterator is over each unique value of the dimensions
+ * given and the inner iterator is over each unique value of the rest of the dimensions, in the same order as
+ * other iterator.
+ *
+ * @param dimensions the names of the dimensions of the superspace
+ * @param dimensionSizes the size of each dimension in the space we are returning values for, containing
+ * one value per dimension of this tensor (in order). Each size may be the same or smaller
+ * than the corresponding size of this tensor
+ */
+ public Iterator<SubspaceIterator> subspaceIterator(Set<String> dimensions, int[] dimensionSizes) {
+ return new SuperspaceIterator(dimensions, dimensionSizes);
+ }
+
/**
* Returns the value at the given indexes
*
@@ -82,7 +97,12 @@ public class IndexedTensor implements Tensor {
return Double.NaN;
}
}
-
+
+ /** Returns the value at these indexes */
+ private double get(Indexes indexes) {
+ return values[toValueIndex(indexes.indexesForReading(), dimensionSizes)];
+ }
+
private static int toValueIndex(int[] indexes, int[] dimensionSizes) {
if (indexes.length == 1) return indexes[0]; // for speed
if (indexes.length == 0) return 0; // for speed
@@ -92,13 +112,13 @@ public class IndexedTensor implements Tensor {
valueIndex += productOfDimensionsAfter(i, dimensionSizes) * indexes[i];
return valueIndex;
}
-
+
private static int toValueIndex(TensorAddress address, int[] dimensionSizes) {
- if (address.labels().isEmpty()) return 0;
+ if (address.isEmpty()) return 0;
int valueIndex = 0;
- for (int i = 0; i < address.labels().size(); i++)
- valueIndex += productOfDimensionsAfter(i, dimensionSizes) * Integer.parseInt(address.labels().get(i));
+ for (int i = 0; i < address.size(); i++)
+ valueIndex += productOfDimensionsAfter(i, dimensionSizes) * address.intLabel(i);
return valueIndex;
}
@@ -113,11 +133,11 @@ public class IndexedTensor implements Tensor {
public TensorType type() { return type; }
/**
- * Returns the lenght of this in the nth dimension
+ * Returns the length of this in the nth dimension
*
* @throws IndexOutOfBoundsException if the index is larger than the number of dimensions in this tensor minus one
*/
- public int length(int dimension) {
+ public int size(int dimension) {
return dimensionSizes[dimension];
}
@@ -127,30 +147,15 @@ public class IndexedTensor implements Tensor {
return values.length == 0 ? Collections.emptyMap() : Collections.singletonMap(TensorAddress.empty, values[0]);
ImmutableMap.Builder<TensorAddress, Double> builder = new ImmutableMap.Builder<>();
- int[] tensorIndexes = new int[dimensionSizes.length];
+ Indexes indexes = new Indexes(dimensionSizes, values.length);
for (int i = 0; i < values.length; i++) {
- builder.put(new TensorAddress(tensorIndexes), values[i]);
+ builder.put(indexes.toAddress(), values[i]);
if (i < values.length -1)
- next(tensorIndexes);
+ indexes.next();
}
return builder.build();
}
- private void next(int[] tensorIndexes) {
- nextRecursively(tensorIndexes.length - 1, tensorIndexes);
- }
-
- // TODO: Tail recursion -> loop
- private void nextRecursively(int dimension, int[] tensorIndexes) {
- if (tensorIndexes[dimension] + 1 == dimensionSizes[dimension]) {
- tensorIndexes[dimension] = 0;
- nextRecursively(dimension - 1, tensorIndexes);
- }
- else {
- tensorIndexes[dimension]++;
- }
- }
-
@Override
public int hashCode() { return Arrays.hashCode(values); }
@@ -190,9 +195,9 @@ public class IndexedTensor implements Tensor {
" for " + type);
for (int i = 0; i < dimensionSizes.length; i++ ) {
Optional<Integer> size = type.dimensions().get(i).size();
- if (size.isPresent() && size.get() != dimensionSizes[i])
- throw new IllegalArgumentException("Size of " + type.dimensions() + " must be " + size.get() +
- ", not " + dimensionSizes[i]);
+ if (size.isPresent() && size.get() < dimensionSizes[i])
+ throw new IllegalArgumentException("Size of " + type.dimensions() + " is " + dimensionSizes[i] +
+ " but cannot be larger than " + size.get());
}
return new BoundBuilder(type, dimensionSizes);
@@ -342,14 +347,9 @@ public class IndexedTensor implements Tensor {
@Override
public Builder cell(TensorAddress address, double value) {
- int[] indexes = new int[address.labels().size()];
- for (int i = 0; i < address.labels().size(); i++) {
- try {
- indexes[i] = Integer.parseInt(address.labels().get(i));
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("Labels in an indexed tensor must be integers, not '" +
- address.labels().get(i) + "'");
- }
+ int[] indexes = new int[address.size()];
+ for (int i = 0; i < address.size(); i++) {
+ indexes[i] = address.intLabel(i);
}
cell(value, indexes);
return this;
@@ -397,71 +397,251 @@ public class IndexedTensor implements Tensor {
}
+ // TODO: Generalize to vector cell iterator?
private final class CellIterator implements Iterator<Map.Entry<TensorAddress, Double>> {
- private int cursor = 0;
- private final int[] tensorIndexes = new int[dimensionSizes.length];
+ private int count = 0;
+ private final Indexes indexes = new Indexes(dimensionSizes, values.length);
@Override
public boolean hasNext() {
- return cursor < values.length;
+ return count < indexes.size();
}
@Override
public Map.Entry<TensorAddress, Double> next() {
- if ( ! hasNext()) throw new NoSuchElementException();
+ if ( ! hasNext()) throw new NoSuchElementException("No cell at " + indexes);
- Map.Entry<TensorAddress, Double> current = new Cell(new TensorAddress(tensorIndexes), values[cursor]);
+ Map.Entry<TensorAddress, Double> current = new Cell(indexes.toAddress(), get(indexes));
- cursor++;
+ count++;
if (hasNext())
- IndexedTensor.this.next(tensorIndexes);
+ indexes.next();
return current;
}
- private class Cell implements Map.Entry<TensorAddress, Double> {
+ }
- private final TensorAddress address;
- private final Double value;
-
- private Cell(TensorAddress address, Double value) {
- this.address = address;
- this.value = value;
- }
-
- @Override
- public TensorAddress getKey() { return address; }
+ private class Cell implements Map.Entry<TensorAddress, Double> {
- @Override
- public Double getValue() { return value; }
+ private final TensorAddress address;
+ private final Double value;
- @Override
- public Double setValue(Double value) {
- throw new UnsupportedOperationException("A tensor cannot be modified");
- }
+ private Cell(TensorAddress address, Double value) {
+ this.address = address;
+ this.value = value;
+ }
+
+ @Override
+ public TensorAddress getKey() { return address; }
+
+ @Override
+ public Double getValue() { return value; }
+ @Override
+ public Double setValue(Double value) {
+ throw new UnsupportedOperationException("A tensor cannot be modified");
}
}
private final class ValueIterator implements Iterator<Double> {
- private int cursor = 0;
+ private int count = 0;
@Override
public boolean hasNext() {
- return cursor < values.length;
+ return count < values.length;
}
@Override
public Double next() {
try {
- return values[cursor++];
+ return values[count++];
}
catch (IndexOutOfBoundsException e) {
- throw new NoSuchElementException("No element at position " + cursor);
+ throw new NoSuchElementException("No element at position " + count);
+ }
+ }
+
+ }
+
+ private final class SuperspaceIterator implements Iterator<SubspaceIterator> {
+
+ private final Indexes superindexes;
+
+ /** true at indexes whose dimension subspaces iterate over */
+ private final boolean[] subdimensionIndexes;
+
+ /**
+ * The sizes of the space we'll return values of, one value for each dimension of this tensor,
+ * which may be equal to or smaller than the sizes of this tensor
+ */
+ private final int[] dimensionSizes;
+
+ private int count = 0;
+
+ private SuperspaceIterator(Set<String> superdimensionNames, int[] dimensionSizes) {
+ this.dimensionSizes = dimensionSizes;
+
+ boolean[] superdimensionIndexes = new boolean[dimensionSizes.length]; // for outer iterator
+ subdimensionIndexes = new boolean [dimensionSizes.length]; // for inner iterator
+ for (int i = 0; i < type.dimensions().size(); i++ ) {
+ boolean superDimension = superdimensionNames.contains(type.dimensions().get(i).name());
+ superdimensionIndexes[i] = superDimension;
+ subdimensionIndexes[i] = ! superDimension;
}
+
+ superindexes = new Indexes(dimensionSizes, superdimensionIndexes);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return count < superindexes.size();
+ }
+
+ @Override
+ public SubspaceIterator next() {
+ if ( ! hasNext()) throw new NoSuchElementException("No cell at " + superindexes);
+ SubspaceIterator subspace = new SubspaceIterator(subdimensionIndexes, superindexes.indexesCopy(), dimensionSizes);
+ count++;
+ if (hasNext())
+ superindexes.next();
+ return subspace;
+ }
+
+ }
+
+ /**
+ * An iterator over a subspace of this tensor. This is exposed to allow clients to query the size.
+ */
+ public final class SubspaceIterator implements Iterator<Map.Entry<TensorAddress, Double>> {
+
+ private final Indexes indexes;
+ private int count = 0;
+
+ /**
+ * Creates a new subspace iterator
+ *
+ * @param dimensionIndexes a boolean array with a true entry for dimensions we should iterate over and false
+ * entries for all other dimensions
+ * @param address the address of the first cell of this subspace.
+ */
+ private SubspaceIterator(boolean[] dimensionIndexes, int[] address, int[] dimensionSizes) {
+ this.indexes = new Indexes(dimensionSizes, dimensionIndexes, address);
+ }
+
+ /** Returns the total number of cells in this subspace */
+ public int size() {
+ return indexes.size();
+ }
+
+ @Override
+ public boolean hasNext() { return count < indexes.size(); }
+
+ @Override
+ public Map.Entry<TensorAddress, Double> next() {
+ if ( ! hasNext()) throw new NoSuchElementException("No cell at " + indexes);
+
+ Map.Entry<TensorAddress, Double> current = new Cell(indexes.toAddress(), get(indexes));
+
+ count++;
+ if (hasNext())
+ indexes.next();
+
+ return current;
+ }
+
+ }
+
+ /** An array of indexes into this tensor which are able to find the next index in the value order */
+ private static class Indexes {
+
+ private final int size;
+ private final int[] indexes;
+
+ private final int[] dimensionSizes;
+
+ /** Only mutate (take next in) the dimension indexes which are true */
+ private final boolean[] iteratingDimensions;
+
+ private Indexes(int[] dimensionSizes, int size) {
+ this(dimensionSizes, trueArray(dimensionSizes.length), size);
+ }
+
+ private Indexes(int[] dimensionSizes, boolean[] iteratingDimensions) {
+ this(dimensionSizes, iteratingDimensions, computeSize(dimensionSizes, iteratingDimensions));
+ }
+
+ private Indexes(int[] dimensionSizes, boolean[] iteratingDimensionIndexes, int size) {
+ this(dimensionSizes, iteratingDimensionIndexes, new int[dimensionSizes.length], size);
+ }
+
+ private Indexes(int[] dimensionSizes, boolean[] iteratingDimensions, int[] initialIndexes) {
+ this(dimensionSizes, iteratingDimensions, initialIndexes, computeSize(dimensionSizes, iteratingDimensions));
+ }
+
+ private Indexes(int[] dimensionSizes, boolean[] iteratingDimensions, int[] initialIndexes, int size) {
+ this.dimensionSizes = dimensionSizes;
+ this.iteratingDimensions = iteratingDimensions;
+ this.indexes = initialIndexes;
+ this.size = size;
+ }
+
+ private static boolean[] trueArray(int size) {
+ boolean[] array = new boolean[size];
+ Arrays.fill(array, true);
+ return array;
+ }
+
+ private static int computeSize(int[] dimensionSizes, boolean[] iteratingDimensions) {
+ int size = 1;
+ for (int dimensionIndex = 0; dimensionIndex < dimensionSizes.length; dimensionIndex++)
+ if (iteratingDimensions[dimensionIndex])
+ size *= dimensionSizes[dimensionIndex];
+ return size;
+ }
+
+ private static boolean anyTrue(boolean[] values) {
+ for (boolean value : values)
+ if (value) return true;
+ return false;
+ }
+
+ /** Returns the number of values this will iterate over - i.e the product if the iterating dimension sizes */
+ public int size() {
+ return size;
+ }
+
+ private void next() {
+ int currentDimension = indexes.length - 1;
+ while ( ! iteratingDimensions[currentDimension] ||
+ indexes[currentDimension] + 1 == dimensionSizes[currentDimension]) {
+ if ( iteratingDimensions[currentDimension])
+ indexes[currentDimension--] = 0; // carry over
+ else // leave this dimension as-is
+ currentDimension--;
+ }
+ indexes[currentDimension]++;
+ }
+
+ /** Returns the address of the current position of these indexes */
+ private TensorAddress toAddress() {
+ // TODO: We may avoid the array copy by issuing a one-time-use address?
+ return TensorAddress.of(indexes);
+ }
+
+ private int[] indexesCopy() {
+ return Arrays.copyOf(indexes, indexes.length);
+ }
+
+ /** Returns a copy of the indexes of this which must not be modified */
+ private int[] indexesForReading() { return indexes; }
+
+ @Override
+ public String toString() {
+ return "indexes " + Arrays.toString(indexes);
}
}
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java
index f87a97cb44e..6e169b8347f 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java
@@ -81,7 +81,7 @@ public class MappedTensor implements Tensor {
@Override
public Builder cell(double value, int... labels) {
- cells.put(new TensorAddress(labels), value);
+ cells.put(TensorAddress.of(labels), value);
return this;
}
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java
index 260c48ace7f..6f655fd5860 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java
@@ -73,8 +73,6 @@ public interface Tensor {
if (type().dimensions().size() > 0)
throw new IllegalStateException("This tensor is not dimensionless. Dimensions: " + type().dimensions().size());
if (size() == 0) return Double.NaN;
- if (size() > 1)
- throw new IllegalStateException("This tensor does not have a single value, it has " + size());
return valueIterator().next();
}
@@ -213,9 +211,7 @@ public interface Tensor {
/** Returns true if the two given tensors are mathematically equivalent, that is whether both have the same content */
static boolean equals(Tensor a, Tensor b) {
- if (a == b) return true;
- if ( ! a.cells().equals(b.cells())) return false;
- return true;
+ return a == b || a.cells().equals(b.cells());
}
// ----------------- Factories
@@ -250,9 +246,8 @@ public interface Tensor {
}
interface Builder {
-
+
/** Creates a suitable builder for the given type */
- // TODO: Create version of this which takes size info and use it when possible
static Builder of(TensorType type) {
boolean containsIndexed = type.dimensions().stream().anyMatch(d -> d.isIndexed());
boolean containsMapped = type.dimensions().stream().anyMatch( d -> ! d.isIndexed());
@@ -263,7 +258,19 @@ public interface Tensor {
else // indexed or empty
return IndexedTensor.Builder.of(type);
}
-
+
+ /** Creates a suitable builder for the given type */
+ static Builder of(TensorType type, int[] dimensionSizes) {
+ boolean containsIndexed = type.dimensions().stream().anyMatch(d -> d.isIndexed());
+ boolean containsMapped = type.dimensions().stream().anyMatch( d -> ! d.isIndexed());
+ if (containsIndexed && containsMapped)
+ throw new IllegalArgumentException("Combining indexed and mapped dimensions is not supported yet");
+ if (containsMapped)
+ return MappedTensor.Builder.of(type);
+ else // indexed or empty
+ return IndexedTensor.Builder.of(type, dimensionSizes);
+ }
+
/** Returns the type this is building */
TensorType type();
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java b/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java
index abcaee2dfa5..5224d9632ec 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/TensorAddress.java
@@ -20,55 +20,73 @@ import java.util.Set;
* @author bratseth
*/
@Beta
-public final class TensorAddress implements Comparable<TensorAddress> {
+public abstract class TensorAddress implements Comparable<TensorAddress> {
public static final TensorAddress empty = new TensorAddress.Builder(TensorType.empty).build();
- private final ImmutableList<String> labels;
-
- public TensorAddress(String ... labels) {
- this.labels = ImmutableList.copyOf(labels);
+ public static TensorAddress of(String[] labels) {
+ return new StringTensorAddress(labels);
}
- public TensorAddress(int ... labels) {
- ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
- for (int label : labels)
- builder.add(String.valueOf(label));
- this.labels = builder.build();
+ public static TensorAddress of(int ... labels) {
+ return new IntTensorAddress(labels);
}
- /** Returns the labels of this as an immutable list in the order of the dimensions of the tensor type of this */
- public List<String> labels() { return labels; }
+ /** Returns the number of labels in this */
+ public abstract int size();
+
+ /**
+ * Returns the i'th label in this
+ *
+ * @throws IllegalArgumentException if there is no label at this index
+ */
+ public abstract String label(int i);
+
+ /**
+ * Returns the i'th label in this as an int.
+ * Prefer this if you know that this is an integer address, but not otherwise.
+ *
+ * @throws IllegalArgumentException if there is no label at this index
+ */
+ public abstract int intLabel(int i);
+
+ public final boolean isEmpty() { return size() == 0; }
@Override
public int compareTo(TensorAddress other) {
- for (int i = 0; i < labels.size(); i++) {
- int elementComparison = this.labels.get(i).compareTo(other.labels.get(i));
+ // TODO: Formal issue (only): Ordering with different address sizes
+ for (int i = 0; i < size(); i++) {
+ int elementComparison = this.label(i).compareTo(other.label(i));
if (elementComparison != 0) return elementComparison;
}
return 0;
}
@Override
- public int hashCode() { return labels.hashCode(); }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) return true;
- if ( ! (other instanceof TensorAddress)) return false;
- return ((TensorAddress)other).labels.equals(this.labels);
+ public int hashCode() {
+ int result = 1;
+ for (int i = 0; i < size(); i++)
+ result = 31 * result + label(i).hashCode();
+ return result;
}
@Override
- public String toString() {
- return labels.toString();
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if ( ! (o instanceof TensorAddress)) return false;
+ TensorAddress other = (TensorAddress)o;
+ if (other.size() != this.size()) return false;
+ for (int i = 0; i < this.size(); i++)
+ if ( ! this.label(i).equals(other.label(i)))
+ return false;
+ return true;
}
- /** Returns this on the appropriate form given the type */
- public String toString(TensorType type) {
+ /** Returns this as a string on the appropriate form given the type */
+ public final String toString(TensorType type) {
StringBuilder b = new StringBuilder("{");
- for (int i = 0; i < labels.size(); i++) {
- b.append(type.dimensions().get(i).name()).append(":").append(labels.get(i));
+ for (int i = 0; i < size(); i++) {
+ b.append(type.dimensions().get(i).name()).append(":").append(label(i));
b.append(",");
}
if (b.length() > 1)
@@ -77,6 +95,61 @@ public final class TensorAddress implements Comparable<TensorAddress> {
return b.toString();
}
+ private static final class StringTensorAddress extends TensorAddress {
+
+ private final String[] labels;
+
+ private StringTensorAddress(String ... labels) {
+ this.labels = Arrays.copyOf(labels, labels.length);
+ }
+
+ @Override
+ public int size() { return labels.length; }
+
+ @Override
+ public String label(int i) { return labels[i]; }
+
+ @Override
+ public int intLabel(int i) {
+ try {
+ return Integer.parseInt(labels[i]);
+ }
+ catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Expected an int label in " + this + " at position " + i);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(labels);
+ }
+
+ }
+
+ private static final class IntTensorAddress extends TensorAddress {
+
+ private final int[] labels;
+
+ private IntTensorAddress(int ... labels) {
+ this.labels = Arrays.copyOf(labels, labels.length);
+ }
+
+ @Override
+ public int size() { return labels.length; }
+
+ @Override
+ public String label(int i) { return String.valueOf(labels[i]); }
+
+ @Override
+ public int intLabel(int i) { return labels[i]; }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(labels);
+ }
+
+ }
+
/** Supports building of a tensor address */
public static class Builder {
@@ -117,7 +190,7 @@ public final class TensorAddress implements Comparable<TensorAddress> {
if (labels[i] == null)
throw new IllegalArgumentException("Missing a value for dimension " +
type.dimensions().get(i).name() + " for " + type);
- return new TensorAddress(labels);
+ return TensorAddress.of(labels);
}
}
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java b/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java
index 0c726efc047..e829f4c909b 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java
@@ -53,6 +53,9 @@ public class TensorType {
return TensorTypeParser.fromSpec(specString);
}
+ /** Returns true if all dimensions of this are indexed */
+ public boolean isIndexed() { return dimensions().stream().allMatch(d -> d.isIndexed()); }
+
private static final boolean supportsMixedTypes = false;
/**
@@ -176,7 +179,7 @@ public class TensorType {
// both are indexed bound
IndexedBoundDimension thisIb = (IndexedBoundDimension)this;
IndexedBoundDimension otherIb = (IndexedBoundDimension)other.get();
- return thisIb.size().get() > otherIb.size().get() ? thisIb : otherIb;
+ return thisIb.size().get() < otherIb.size().get() ? thisIb : otherIb;
}
@Override
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java
index 19b4ad39af3..6128611302f 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java
@@ -2,18 +2,20 @@ package com.yahoo.tensor.functions;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.yahoo.tensor.IndexedTensor;
-import com.yahoo.tensor.MappedTensor;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorAddress;
import com.yahoo.tensor.TensorType;
import com.yahoo.tensor.evaluation.EvaluationContext;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
import java.util.function.DoubleBinaryOperator;
/**
@@ -69,7 +71,7 @@ public class Join extends PrimitiveTensorFunction {
TensorType joinedType = a.type().combineWith(b.type());
// Choose join algorithm
- if (a.type().equals(b.type()) && a.type().dimensions().size() == 1 && a.type().dimensions().get(0).isIndexed())
+ if (hasSingleIndexedDimension(a) && hasSingleIndexedDimension(b) && a.type().dimensions().get(0).name().equals(b.type().dimensions().get(0).name()))
return indexedVectorJoin((IndexedTensor)a, (IndexedTensor)b, joinedType);
else if (joinedType.dimensions().size() == a.type().dimensions().size() && joinedType.dimensions().size() == b.type().dimensions().size())
return singleSpaceJoin(a, b, joinedType);
@@ -81,8 +83,12 @@ public class Join extends PrimitiveTensorFunction {
return generalJoin(a, b, joinedType);
}
+ private boolean hasSingleIndexedDimension(Tensor tensor) {
+ return tensor.type().dimensions().size() == 1 && tensor.type().dimensions().get(0).isIndexed();
+ }
+
private Tensor indexedVectorJoin(IndexedTensor a, IndexedTensor b, TensorType type) {
- int joinedLength = Math.min(a.length(0), b.length(0));
+ int joinedLength = Math.min(a.size(0), b.size(0));
Iterator<Double> aIterator = a.valueIterator();
Iterator<Double> bIterator = b.valueIterator();
IndexedTensor.Builder builder = IndexedTensor.Builder.of(type, new int[] { joinedLength});
@@ -105,6 +111,42 @@ public class Join extends PrimitiveTensorFunction {
/** Join a tensor into a superspace */
private Tensor subspaceJoin(Tensor subspace, Tensor superspace, TensorType joinedType, boolean reversedArgumentOrder) {
+ if (subspace.type().isIndexed() && superspace.type().isIndexed())
+ return indexedSubspaceJoin((IndexedTensor) subspace, (IndexedTensor) superspace, joinedType, reversedArgumentOrder);
+ else
+ return generalSubspaceJoin(subspace, superspace, joinedType, reversedArgumentOrder);
+ }
+
+ private Tensor indexedSubspaceJoin(IndexedTensor subspace, IndexedTensor superspace, TensorType joinedType, boolean reversedArgumentOrder) {
+ if (subspace.size() == 0 || superspace.size() == 0) // special case empty here to avoid doing it when finding sizes
+ return Tensor.Builder.of(joinedType, new int[joinedType.dimensions().size()]).build();
+
+ // Find size of joined tensor
+ int[] joinedSizes = new int[joinedType.dimensions().size()];
+ for (int i = 0; i < joinedSizes.length; i++) {
+ Optional<Integer> subspaceIndex = subspace.type().indexOfDimension(joinedType.dimensions().get(i).name());
+ if (subspaceIndex.isPresent())
+ joinedSizes[i] = Math.min(superspace.size(i), subspace.size(subspaceIndex.get()));
+ else
+ joinedSizes[i] = superspace.size(i);
+ }
+
+ Tensor.Builder builder = Tensor.Builder.of(joinedType, joinedSizes);
+
+ // Find dimensions which are only in the supertype
+ Set<String> superDimensionNames = new HashSet<>(superspace.type().dimensionNames());
+ superDimensionNames.removeAll(subspace.type().dimensionNames());
+
+ for (Iterator<IndexedTensor.SubspaceIterator> i = superspace.subspaceIterator(superDimensionNames, joinedSizes); i.hasNext(); ) {
+ IndexedTensor.SubspaceIterator subspaceInSuper = i.next();
+ joinSubspaces(subspace.valueIterator(), subspace.size(),
+ subspaceInSuper, subspaceInSuper.size(),
+ reversedArgumentOrder, builder);
+ }
+ return builder.build();
+ }
+
+ private Tensor generalSubspaceJoin(Tensor subspace, Tensor superspace, TensorType joinedType, boolean reversedArgumentOrder) {
int[] subspaceIndexes = subspaceIndexes(superspace.type(), subspace.type());
Tensor.Builder builder = Tensor.Builder.of(joinedType);
for (Iterator<Map.Entry<TensorAddress, Double>> i = superspace.cellIterator(); i.hasNext(); ) {
@@ -112,13 +154,26 @@ public class Join extends PrimitiveTensorFunction {
TensorAddress subaddress = mapAddressToSubspace(supercell.getKey(), subspaceIndexes);
double subspaceValue = subspace.get(subaddress);
if ( ! Double.isNaN(subspaceValue))
- builder.cell(supercell.getKey(),
+ builder.cell(supercell.getKey(),
reversedArgumentOrder ? combinator.applyAsDouble(supercell.getValue(), subspaceValue)
: combinator.applyAsDouble(subspaceValue, supercell.getValue()));
}
return builder.build();
}
-
+
+ private void joinSubspaces(Iterator<Double> subspace, int subspaceSize,
+ Iterator<Map.Entry<TensorAddress, Double>> superspace, int superspaceSize,
+ boolean reversedArgumentOrder, Tensor.Builder builder) {
+ int joinedLength = Math.min(subspaceSize, superspaceSize);
+ for (int i = 0; i < joinedLength; i++) {
+ Double subvalue = subspace.next();
+ Map.Entry<TensorAddress, Double> supercell = superspace.next();
+ builder.cell(supercell.getKey(),
+ reversedArgumentOrder ? combinator.applyAsDouble(supercell.getValue(), subvalue)
+ : combinator.applyAsDouble(subvalue, supercell.getValue()));
+ }
+ }
+
/** Returns the indexes in the superspace type which should be retained to create the subspace type */
private int[] subspaceIndexes(TensorType supertype, TensorType subtype) {
int[] subspaceIndexes = new int[subtype.dimensions().size()];
@@ -130,8 +185,8 @@ public class Join extends PrimitiveTensorFunction {
private TensorAddress mapAddressToSubspace(TensorAddress superAddress, int[] subspaceIndexes) {
String[] subspaceLabels = new String[subspaceIndexes.length];
for (int i = 0; i < subspaceIndexes.length; i++)
- subspaceLabels[i] = superAddress.labels().get(subspaceIndexes[i]);
- return new TensorAddress(subspaceLabels);
+ subspaceLabels[i] = superAddress.label(subspaceIndexes[i]);
+ return TensorAddress.of(subspaceLabels);
}
/** Slow join which works for any two tensors */
@@ -169,10 +224,10 @@ public class Join extends PrimitiveTensorFunction {
private TensorAddress combineAddresses(TensorAddress a, int[] aToIndexes, TensorAddress b, int[] bToIndexes,
TensorType joinedType) {
String[] joinedLabels = new String[joinedType.dimensions().size()];
- mapContent(a.labels(), joinedLabels, aToIndexes);
- boolean compatible = mapContent(b.labels(), joinedLabels, bToIndexes);
+ mapContent(a, joinedLabels, aToIndexes);
+ boolean compatible = mapContent(b, joinedLabels, bToIndexes);
if ( ! compatible) return null;
- return new TensorAddress(joinedLabels);
+ return TensorAddress.of(joinedLabels);
}
/**
@@ -181,11 +236,11 @@ public class Join extends PrimitiveTensorFunction {
* @return true if the mapping was successful, false if one of the destination positions was
* occupied by a different value
*/
- private boolean mapContent(List<String> from, String[] to, int[] indexMap) {
+ private boolean mapContent(TensorAddress from, String[] to, int[] indexMap) {
for (int i = 0; i < from.size(); i++) {
int toIndex = indexMap[i];
- if (to[toIndex] != null && ! to[toIndex].equals(from.get(i))) return false;
- to[toIndex] = from.get(i);
+ if (to[toIndex] != null && ! to[toIndex].equals(from.label(i))) return false;
+ to[toIndex] = from.label(i);
}
return true;
}
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/Reduce.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/Reduce.java
index 7595d18fb18..d528b4d3dc5 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/functions/Reduce.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/Reduce.java
@@ -2,9 +2,7 @@ package com.yahoo.tensor.functions;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.yahoo.tensor.IndexedTensor;
-import com.yahoo.tensor.MappedTensor;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorAddress;
import com.yahoo.tensor.TensorType;
@@ -134,10 +132,10 @@ public class Reduce extends PrimitiveTensorFunction {
String[] reducedLabels = new String[reducedType.dimensions().size()];
int reducedLabelIndex = 0;
- for (int i = 0; i < address.labels().size(); i++)
+ for (int i = 0; i < address.size(); i++)
if ( ! indexesToRemove.contains(i))
- reducedLabels[reducedLabelIndex++] = address.labels().get(i);
- return new TensorAddress(reducedLabels);
+ reducedLabels[reducedLabelIndex++] = address.label(i);
+ return TensorAddress.of(reducedLabels);
}
private Tensor reduceAllGeneral(Tensor argument) {
@@ -149,7 +147,7 @@ public class Reduce extends PrimitiveTensorFunction {
private Tensor reduceIndexedVector(IndexedTensor argument) {
ValueAggregator valueAggregator = ValueAggregator.ofType(aggregator);
- for (int i = 0; i < argument.length(0); i++)
+ for (int i = 0; i < argument.size(0); i++)
valueAggregator.aggregate(argument.get(i));
return Tensor.Builder.of(TensorType.empty).cell((valueAggregator.aggregatedValue())).build();
}
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/Rename.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/Rename.java
index aabe38d1824..29dcb218843 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/functions/Rename.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/Rename.java
@@ -89,8 +89,8 @@ public class Rename extends PrimitiveTensorFunction {
private TensorAddress rename(TensorAddress address, int[] toIndexes) {
String[] reorderedLabels = new String[toIndexes.length];
for (int i = 0; i < toIndexes.length; i++)
- reorderedLabels[toIndexes[i]] = address.labels().get(i);
- return new TensorAddress(reorderedLabels);
+ reorderedLabels[toIndexes[i]] = address.label(i);
+ return TensorAddress.of(reorderedLabels);
}
@Override
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/serialization/SparseBinaryFormat.java b/vespajlib/src/main/java/com/yahoo/tensor/serialization/SparseBinaryFormat.java
index cb9a93fd233..8e15a994ed9 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/serialization/SparseBinaryFormat.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/serialization/SparseBinaryFormat.java
@@ -48,9 +48,8 @@ class SparseBinaryFormat implements BinaryFormat {
}
private static void encodeAddress(GrowableByteBuffer buffer, TensorAddress address) {
- for (String label : address.labels()) {
- encodeString(buffer, label);
- }
+ for (int i = 0; i < address.size(); i++)
+ encodeString(buffer, address.label(i));
}
private static void encodeString(GrowableByteBuffer buffer, String value) {
diff --git a/vespajlib/src/test/java/com/yahoo/tensor/IndexedTensorTestCase.java b/vespajlib/src/test/java/com/yahoo/tensor/IndexedTensorTestCase.java
index 5e91679c412..9183ad68956 100644
--- a/vespajlib/src/test/java/com/yahoo/tensor/IndexedTensorTestCase.java
+++ b/vespajlib/src/test/java/com/yahoo/tensor/IndexedTensorTestCase.java
@@ -56,10 +56,10 @@ public class IndexedTensorTestCase {
assertEquals(emptyWithDimensions, emptyWithDimensionsFromString);
IndexedTensor emptyWithDimensionsIndexed = (IndexedTensor)emptyWithDimensions;
- assertEquals(0, emptyWithDimensionsIndexed.length(0));
- assertEquals(0, emptyWithDimensionsIndexed.length(1));
+ assertEquals(0, emptyWithDimensionsIndexed.size(0));
+ assertEquals(0, emptyWithDimensionsIndexed.size(1));
}
-
+
@Test
public void testBoundBuilding() {
TensorType type = new TensorType.Builder().indexed("v", vSize)
@@ -108,7 +108,7 @@ public class IndexedTensorTestCase {
for (int y = 0; y < ySize; y++)
for (int x = xSize - 1; x >= 0; x--)
for (int z = 0; z < zSize; z++)
- assertEquals(value(v, w, x, y, z), (int) tensor.get(new TensorAddress(v, w, x, y, z)));
+ assertEquals(value(v, w, x, y, z), (int) tensor.get(TensorAddress.of(v, w, x, y, z)));
// Lookup from cells
Map<TensorAddress, Double> cells = tensor.cells();
@@ -118,7 +118,7 @@ public class IndexedTensorTestCase {
for (int y = 0; y < ySize; y++)
for (int x = xSize - 1; x >= 0; x--)
for (int z = 0; z < zSize; z++)
- assertEquals(value(v, w, x, y, z), cells.get(new TensorAddress(v, w, x, y, z)).intValue());
+ assertEquals(value(v, w, x, y, z), cells.get(TensorAddress.of(v, w, x, y, z)).intValue());
// Lookup from iterator
Map<TensorAddress, Double> cellsOfIterator = new HashMap<>();
@@ -132,7 +132,7 @@ public class IndexedTensorTestCase {
for (int y = 0; y < ySize; y++)
for (int x = xSize - 1; x >= 0; x--)
for (int z = 0; z < zSize; z++)
- assertEquals(value(v, w, x, y, z), cellsOfIterator.get(new TensorAddress(v, w, x, y, z)).intValue());
+ assertEquals(value(v, w, x, y, z), cellsOfIterator.get(TensorAddress.of(v, w, x, y, z)).intValue());
}
diff --git a/vespajlib/src/test/java/com/yahoo/tensor/JoinTestCase.java b/vespajlib/src/test/java/com/yahoo/tensor/JoinTestCase.java
new file mode 100644
index 00000000000..63dd4a4a644
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/tensor/JoinTestCase.java
@@ -0,0 +1,36 @@
+package com.yahoo.tensor;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author bratseth
+ */
+public class JoinTestCase {
+
+ /** Test the indexed subspace join optimization */
+ @Test
+ public void testJoinIndexedSubspace() {
+ Tensor t1, t2;
+
+ t1 = Tensor.from("tensor(x[]):{{x:0}:1.0,{x:1}:2.0}");
+ t2 = Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:10,{x:1,y:1,z:0}:0.0}");
+ assertEquals(Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:20.0,{x:1,y:1,z:0}:0.0}"),
+ t1.multiply(t2));
+ t1 = Tensor.from("tensor(x[]):{{x:0}:1.0,{x:1}:2.0}");
+ t2 = Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:10,{x:1,y:1,z:0}:0.0}");
+ assertEquals(Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:5.0,{x:1,y:1,z:0}:0.0}"),
+ t2.divide(t1));
+
+ t1 = Tensor.from("tensor(y[]):{{y:0}:1.0,{y:1}:2.0}");
+ t2 = Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:10,{x:1,y:1,z:0}:0.0}");
+ assertEquals(Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:10.0,{x:1,y:1,z:0}:0.0}"),
+ t1.multiply(t2));
+ t1 = Tensor.from("tensor(y[]):{{y:0}:1.0,{y:1}:2.0}");
+ t2 = Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:10,{x:1,y:1,z:0}:0.0}");
+ assertEquals(Tensor.from("tensor(x[],y[],z[]):{{x:0,y:0,z:0}:6,{x:0,y:1,z:0}:0.0,{x:1,y:0,z:0}:10.0,{x:1,y:1,z:0}:0.0}"),
+ t2.divide(t1));
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/tensor/TensorFunctionBenchmark.java b/vespajlib/src/test/java/com/yahoo/tensor/TensorFunctionBenchmark.java
index bbab92fc16d..9b34780a6fc 100644
--- a/vespajlib/src/test/java/com/yahoo/tensor/TensorFunctionBenchmark.java
+++ b/vespajlib/src/test/java/com/yahoo/tensor/TensorFunctionBenchmark.java
@@ -106,11 +106,13 @@ public class TensorFunctionBenchmark {
// ---------------- Mapped with extra space (sidesteps current special-case optimizations):
// Initial: 450 ms
- //time = new TensorFunctionBenchmark().benchmark(20, vectors(100, 300, TensorType.Dimension.Type.mapped), TensorType.Dimension.Type.mapped, true);
- //System.out.printf("Mapped vectors, x space time per join: %1$8.3f ms\n", time);
+ // - Now: 350 ms
+ time = new TensorFunctionBenchmark().benchmark(20, vectors(100, 300, TensorType.Dimension.Type.mapped), TensorType.Dimension.Type.mapped, true);
+ System.out.printf("Mapped vectors, x space time per join: %1$8.3f ms\n", time);
// Initial: 900 ms
- //time = new TensorFunctionBenchmark().benchmark(20, matrix(100, 300, TensorType.Dimension.Type.mapped), TensorType.Dimension.Type.mapped, true);
- //System.out.printf("Mapped matrix, x space time per join: %1$8.3f ms\n", time);
+ // - Now: 700 ms
+ time = new TensorFunctionBenchmark().benchmark(20, matrix(100, 300, TensorType.Dimension.Type.mapped), TensorType.Dimension.Type.mapped, true);
+ System.out.printf("Mapped matrix, x space time per join: %1$8.3f ms\n", time);
// ---------------- Mapped:
// Initial: 150 ms
@@ -121,40 +123,51 @@ public class TensorFunctionBenchmark {
System.out.printf("Mapped vectors, time per join: %1$8.3f ms\n", time);
// Initial: 760 ms
// - After special-casing subspace: 13 ms
+ // - Now: 7 ms
time = new TensorFunctionBenchmark().benchmark(500, matrix(100, 300, TensorType.Dimension.Type.mapped), TensorType.Dimension.Type.mapped, false);
System.out.printf("Mapped matrix, time per join: %1$8.3f ms\n", time);
// ---------------- Indexed (unbound) with extra space (sidesteps current special-case optimizations):
// Initial: 1900 ms
// - After moving to cell iterators: 1100
- //time = new TensorFunctionBenchmark().benchmark(20, vectors(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, true);
- //System.out.printf("Indexed vectors, x space time per join: %1$8.3f ms\n", time);
+ // - Now: 1300
+ time = new TensorFunctionBenchmark().benchmark(20, vectors(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, true);
+ System.out.printf("Indexed vectors, x space time per join: %1$8.3f ms\n", time);
// Initial: 2200 ms
// - After moving to cell iterators: 1300
- //time = new TensorFunctionBenchmark().benchmark(20, matrix(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, true);
- //System.out.printf("Indexed matrix, x space time per join: %1$8.3f ms\n", time);
+ // - Now: 1550
+ time = new TensorFunctionBenchmark().benchmark(20, matrix(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, true);
+ System.out.printf("Indexed matrix, x space time per join: %1$8.3f ms\n", time);
// ---------------- Indexed unbound:
// Initial: 718 ms
// - After special casing join: 3.6 ms
// - After special-casing reduce: 0.80 ms
// - After create IndexedTensor without builder: 0.4 ms
- // - After double-array backing: 0.1 ms
- time = new TensorFunctionBenchmark().benchmark(10000, vectors(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, false);
+ // - After double-array backing: 0.09 ms
+ time = new TensorFunctionBenchmark().benchmark(50000, vectors(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, false);
System.out.printf("Indexed unbound vectors, time per join: %1$8.3f ms\n", time);
// Initial: 3500 ms
// - After special-casing subspace: 25 ms
- // - After moving to iterators: 10 ms
- time = new TensorFunctionBenchmark().benchmark(500, matrix(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, false);
+ // - After moving to iterators: 7.7 ms
+ // - After indexed subspace join algorithm: 6
+ // - After passing sized: 3.7 ms
+ // - After creating int tensor address: 2.5 ms
+ // - After using int address to get ints directly: 0.93 ms
+ time = new TensorFunctionBenchmark().benchmark(5000, matrix(100, 300, TensorType.Dimension.Type.indexedUnbound), TensorType.Dimension.Type.indexedUnbound, false);
System.out.printf("Indexed unbound matrix, time per join: %1$8.3f ms\n", time);
// ---------------- Indexed bound:
- // Initial: 0.1 ms
- time = new TensorFunctionBenchmark().benchmark(10000, vectors(100, 300, TensorType.Dimension.Type.indexedBound), TensorType.Dimension.Type.indexedBound, false);
+ // Initial: 0.09 ms
+ time = new TensorFunctionBenchmark().benchmark(50000, vectors(100, 300, TensorType.Dimension.Type.indexedBound), TensorType.Dimension.Type.indexedBound, false);
System.out.printf("Indexed bound vectors, time per join: %1$8.3f ms\n", time);
// Initial: 25 ms
- // - After moving to iterators: 10 ms
- time = new TensorFunctionBenchmark().benchmark(500, matrix(100, 300, TensorType.Dimension.Type.indexedBound), TensorType.Dimension.Type.indexedBound, false);
+ // - After moving to iterators: 7.7 ms
+ // - After indexed subspace join algorithm: 6
+ // - After passing sized: 3.7 ms
+ // - After creating int tensor address: 2.8 ms
+ // - After using int address to get ints directly: 0.93 ms
+ time = new TensorFunctionBenchmark().benchmark(5000, matrix(100, 300, TensorType.Dimension.Type.indexedBound), TensorType.Dimension.Type.indexedBound, false);
System.out.printf("Indexed bound matrix, time per join: %1$8.3f ms\n", time);
}
diff --git a/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java b/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java
index aa220e93258..eaf26fd82cd 100644
--- a/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java
+++ b/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java
@@ -79,11 +79,15 @@ public class TensorTestCase {
@Test
public void testOptimizedComputation() {
assertEquals("Mapped vector", 42, (int)dotProduct(vector(Type.mapped), vectors(Type.mapped, 2)));
- assertEquals("Indexed unbound vector", 42, (int)dotProduct(vector(Type.indexedUnbound), vectors(Type.indexedUnbound, 2)));
- assertEquals("Indexed bound vector", 42, (int)dotProduct(vector(Type.indexedBound), vectors(Type.indexedBound, 2)));
+ assertEquals("Indexed unbound vector", 42, (int)dotProduct(vector(3, Type.indexedUnbound), vectors(5, Type.indexedUnbound, 2)));
+ assertEquals("Indexed unbound vector", 42, (int)dotProduct(vector(5, Type.indexedUnbound), vectors(3, Type.indexedUnbound, 2)));
+ assertEquals("Indexed bound vector", 42, (int)dotProduct(vector(3, Type.indexedBound), vectors(5, Type.indexedBound, 2)));
+ assertEquals("Indexed bound vector", 42, (int)dotProduct(vector(5, Type.indexedBound), vectors(3, Type.indexedBound, 2)));
assertEquals("Mapped matrix", 42, (int)dotProduct(vector(Type.mapped), matrix(Type.mapped, 2)));
- assertEquals("Indexed unbound matrix", 42, (int)dotProduct(vector(Type.indexedUnbound), matrix(Type.indexedUnbound, 2)));
- assertEquals("Indexed bound matrix", 42, (int)dotProduct(vector(Type.indexedBound), matrix(Type.indexedBound, 2)));
+ assertEquals("Indexed unbound matrix", 42, (int)dotProduct(vector(3, Type.indexedUnbound), matrix(5, Type.indexedUnbound, 2)));
+ assertEquals("Indexed unbound matrix", 42, (int)dotProduct(vector(5, Type.indexedUnbound), matrix(3, Type.indexedUnbound, 2)));
+ assertEquals("Indexed bound matrix", 42, (int)dotProduct(vector(3, Type.indexedBound), matrix(5, Type.indexedBound, 2)));
+ assertEquals("Indexed bound matrix", 42, (int)dotProduct(vector(5, Type.indexedBound), matrix(3, Type.indexedBound, 2)));
assertEquals("Mixed vector", 42, (int)dotProduct(vector(Type.mapped), vectors(Type.indexedUnbound, 2)));
assertEquals("Mixed vector", 42, (int)dotProduct(vector(Type.mapped), vectors(Type.indexedUnbound, 2)));
assertEquals("Mixed matrix", 42, (int)dotProduct(vector(Type.mapped), matrix(Type.indexedUnbound, 2)));
@@ -100,7 +104,7 @@ public class TensorTestCase {
Tensor matrixInKSpace = matrix(Type.mapped, 2).get(0).multiply(unitK);
assertEquals("Generic computation implementation", 42, (int)dotProduct(vectorInJSpace, Collections.singletonList(matrixInKSpace)));
}
-
+
private double dotProduct(Tensor tensor, List<Tensor> tensors) {
double sum = 0;
TensorFunction dotProductFunction = new Reduce(new Join(new ConstantTensor(tensor),
@@ -119,10 +123,17 @@ public class TensorTestCase {
private Tensor vector(TensorType.Dimension.Type dimensionType) {
return vectors(dimensionType, 1).get(0);
}
+
+ private Tensor vector(int vectorSize, TensorType.Dimension.Type dimensionType) {
+ return vectors(vectorSize, dimensionType, 1).get(0);
+ }
/** Create a list of vectors having a single dimension x */
private List<Tensor> vectors(TensorType.Dimension.Type dimensionType, int vectorCount) {
- int vectorSize = 3;
+ return vectors(3, dimensionType, vectorCount);
+ }
+
+ private List<Tensor> vectors(int vectorSize, TensorType.Dimension.Type dimensionType, int vectorCount) {
List<Tensor> tensors = new ArrayList<>();
TensorType type = vectorType(new TensorType.Builder(), "x", dimensionType, vectorSize);
for (int i = 0; i < vectorCount; i++) {
@@ -140,7 +151,10 @@ public class TensorTestCase {
* This matrix contains the same vectors as returned by createVectors, in a single list element for convenience.
*/
private List<Tensor> matrix(TensorType.Dimension.Type dimensionType, int vectorCount) {
- int vectorSize = 3;
+ return matrix(3, dimensionType, vectorCount);
+ }
+
+ private List<Tensor> matrix(int vectorSize, TensorType.Dimension.Type dimensionType, int vectorCount) {
TensorType.Builder typeBuilder = new TensorType.Builder();
typeBuilder.dimension("i", dimensionType == Type.indexedBound ? Type.indexedUnbound : dimensionType);
vectorType(typeBuilder, "x", dimensionType, vectorSize);