Skip to content

Commit

Permalink
Replace usage of Trampoline by streaming the immutable list.
Browse files Browse the repository at this point in the history
This was neccessary for the performance of the TieTest which was running
at least 30 seconds without these changes. Mostly caused by looping over
head and tail, which would copy the list many times.

Co-authored-by: rdvdijk <roel@rustradio.org>
  • Loading branch information
mvanaken and rdvdijk committed Feb 5, 2024
1 parent c7aedb1 commit 29e828c
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 54 deletions.
26 changes: 19 additions & 7 deletions core/src/main/java/io/parsingdata/metal/data/ImmutableList.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class ImmutableList<T> extends LinkedList<T> {

private Integer hashCode;

public ImmutableList() {
super();
}
Expand All @@ -32,6 +35,10 @@ public ImmutableList(final LinkedList<T> ts) {
super(ts);
}

public ImmutableList(List<T> collect) {
this(new LinkedList<>(collect));
}

public static <T> ImmutableList<T> create(final T head) {
return new ImmutableList<T>().addHead(checkNotNull(head, "head"));
}
Expand All @@ -41,15 +48,15 @@ public static <T> ImmutableList<T> create(final T[] array) {
}

public ImmutableList<T> addHead(final T head) {
final LinkedList<T> ts = new LinkedList<>(this);
final ImmutableList<T> ts = new ImmutableList<>(this);
ts.addFirst(head);
return new ImmutableList<>(ts);
return ts;
}

public ImmutableList<T> addList(final ImmutableList<T> list) {
final LinkedList<T> ts = new LinkedList<>(list);
final ImmutableList<T> ts = new ImmutableList<>(list);
ts.addAll(this);
return new ImmutableList<>(ts);
return ts;
}

public T head() {
Expand All @@ -60,15 +67,20 @@ public T head() {
}

public ImmutableList<T> tail() {
final LinkedList<T> ts = new LinkedList<>(this.subList(1, size()));
return new ImmutableList<>(ts);
return new ImmutableList<>(this.subList(1, size()));
}

@Override
public String toString() {
return isEmpty() ? "" : ">" + head() + tail();
}

// TODO cache the hashcode like in ImmutableObject.
@Override
public int hashCode() {
if (hashCode == null) {
hashCode = super.hashCode();
}
return hashCode;
}

}
24 changes: 10 additions & 14 deletions core/src/main/java/io/parsingdata/metal/data/ParseValueCache.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package io.parsingdata.metal.data;

import static io.parsingdata.metal.Trampoline.complete;
import static io.parsingdata.metal.Trampoline.intermediate;
import static io.parsingdata.metal.data.Selection.NO_LIMIT;
import static io.parsingdata.metal.data.Selection.reverse;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.parsingdata.metal.Trampoline;
import io.parsingdata.metal.Util;
import io.parsingdata.metal.expression.value.Value;
import io.parsingdata.metal.token.Token;
Expand Down Expand Up @@ -39,19 +38,16 @@ public Optional<ImmutableList<Value>> find(final String scopeName, int limit) {
if (this == NO_CACHE) {
return Optional.empty();
}
final ImmutableList<Value> result = find(cache.getOrDefault(shortName(scopeName), new ImmutableList<>()), scopeName, limit, new ImmutableList<>()).computeResult();
return Optional.of(reverse(result));
final ImmutableList<Value> result = find(cache.getOrDefault(shortName(scopeName), new ImmutableList<>()), scopeName, limit);
return Optional.of(result);
}

private Trampoline<ImmutableList<Value>> find(final ImmutableList<ParseValue> searchList, final String scopeName, final int limit, final ImmutableList<Value> result) {
if (searchList.isEmpty() || (limit != NO_LIMIT && (long) result.size() == limit)) {
return complete(() -> result);
private ImmutableList<Value> find(final ImmutableList<ParseValue> searchList, final String scopeName, final int limit) {
Stream<ParseValue> collect = searchList.stream().filter(head -> head.matches(scopeName));
if (limit != NO_LIMIT) {
collect = collect.limit(limit);
}
final ParseValue head = searchList.head();
if (head.matches(scopeName)) {
return intermediate(() -> find(searchList.tail(), scopeName, limit, result.addHead(head)));
}
return intermediate(() -> find(searchList.tail(), scopeName, limit, result));
return new ImmutableList<>(new LinkedList<>(collect.collect(Collectors.toList())));
}

public ParseValueCache add(final ParseValue value) {
Expand Down
12 changes: 4 additions & 8 deletions core/src/main/java/io/parsingdata/metal/data/Selection.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static io.parsingdata.metal.Util.checkNotNull;

import java.math.BigInteger;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Predicate;

Expand Down Expand Up @@ -113,14 +114,9 @@ public static <T> ImmutableList<T> reverse(final ImmutableList<T> list) {
if (list.isEmpty()) {
return list;
}
return reverse(list.tail(), ImmutableList.create(list.head())).computeResult();
}

private static <T> Trampoline<ImmutableList<T>> reverse(final ImmutableList<T> oldList, final ImmutableList<T> newList) {
if (oldList.isEmpty()) {
return complete(() -> newList);
}
return intermediate(() -> reverse(oldList.tail(), newList.addHead(oldList.head())));
final ImmutableList<T> reversedList = new ImmutableList<>(list);
Collections.reverse(reversedList);
return reversedList;
}

public static ImmutableList<ParseItem> getAllRoots(final ParseGraph graph, final Token definition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@

package io.parsingdata.metal.expression.value.reference;

import static java.math.BigInteger.ONE;
import static java.math.BigInteger.ZERO;

import static io.parsingdata.metal.Trampoline.complete;
import static io.parsingdata.metal.Trampoline.intermediate;
import static io.parsingdata.metal.Util.checkNotNull;
import static io.parsingdata.metal.data.Selection.reverse;
import static io.parsingdata.metal.expression.value.NotAValue.NOT_A_VALUE;

import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import io.parsingdata.metal.ImmutableObject;
import io.parsingdata.metal.Trampoline;
import io.parsingdata.metal.Util;
import io.parsingdata.metal.data.ImmutableList;
import io.parsingdata.metal.data.ParseState;
Expand Down Expand Up @@ -61,26 +58,19 @@ public Nth(final ValueExpression values, final ValueExpression indices) {

@Override
public ImmutableList<Value> eval(final ParseState parseState, final Encoding encoding) {
return reverse(eval(values.eval(parseState, encoding), indices.eval(parseState, encoding), new ImmutableList<>()).computeResult());
return eval(values.eval(parseState, encoding), indices.eval(parseState, encoding));
}

private Trampoline<ImmutableList<Value>> eval(final ImmutableList<Value> values, final ImmutableList<Value> indices, final ImmutableList<Value> result) {
private ImmutableList<Value> eval(final ImmutableList<Value> values, final ImmutableList<Value> indices) {
if (indices.isEmpty()) {
return complete(() -> result);
return new ImmutableList<>();
}
final BigInteger valueCount = BigInteger.valueOf(values.size());
final Value index = indices.head();
final Value nextResult = !index.equals(NOT_A_VALUE) && index.asNumeric().compareTo(valueCount) < 0 && index.asNumeric().compareTo(ZERO) >= 0
? nth(values, valueCount.subtract(index.asNumeric()).subtract(ONE)).computeResult()
: NOT_A_VALUE;
return intermediate(() -> eval(values, indices.tail(), result.addHead(nextResult)));
}

private Trampoline<Value> nth(final ImmutableList<Value> values, final BigInteger index) {
if (index.equals(ZERO)) {
return complete(() -> values.head());
}
return intermediate(() -> nth(values.tail(), index.subtract(ONE)));
final List<Value> collect = indices.stream()
.map(index -> !index.equals(NOT_A_VALUE) && index.asNumeric().compareTo(BigInteger.valueOf(values.size())) < 0 && index.asNumeric().compareTo(ZERO) >= 0
? values.get(values.size() - index.asNumeric().intValue() - 1)
: NOT_A_VALUE)
.collect(Collectors.toList());
return new ImmutableList<>(collect);
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ public class AutoEqualityTest {
private static final List<Supplier<Object>> PARSE_STATES = List.of(() -> createFromByteStream(DUMMY_STREAM), () -> createFromByteStream(DUMMY_STREAM, ONE), () -> new ParseState(GRAPH_WITH_REFERENCE, NO_CACHE, DUMMY_BYTE_STREAM_SOURCE, TEN, new ImmutableList<>(), new ImmutableList<>()));
private static final List<Supplier<Object>> PARSE_VALUE_CACHES = List.of(() -> NO_CACHE, () -> new ParseValueCache(), () -> new ParseValueCache().add(PARSE_VALUE), () -> new ParseValueCache().add(PARSE_VALUE).add(PARSE_VALUE));
private static final List<Supplier<Object>> IMMUTABLE_LISTS = List.of(ImmutableList::new, () -> ImmutableList.create("TEST"), () -> ImmutableList.create(1), () -> ImmutableList.create(1).addHead(2));
private static final List<Supplier<Object>> LISTS = List.of(List::of, () -> List.of("TEST"), () -> List.of(1), () -> List.of(1, 2));
private static final List<Supplier<Object>> BOOLEANS = List.of(() -> true, () -> false);
private static final List<Supplier<Object>> BIPREDICATES = List.of(() -> (BiPredicate<Object, Object>) (o, o2) -> false);
private static final Map<Class<?>, List<Supplier<Object>>> mapping = buildMap();
Expand Down Expand Up @@ -253,6 +254,7 @@ private static Map<Class<?>, List<Supplier<Object>>> buildMap() {
result.put(ImmutableList.class, IMMUTABLE_LISTS);
result.put(boolean.class, BOOLEANS);
result.put(BiPredicate.class, BIPREDICATES);
result.put(List.class, LISTS);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ public class CallbackTest {
@Test
public void crc32Good() {
final ImmutableList<Value> result = crc32(con(0x01020304)).eval(stream(), enc());
assertEquals(1, result.size);
assertArrayEquals(new byte[] { -74, 60, -5, -51 }, result.head.value());
assertEquals(1, result.size());
assertArrayEquals(new byte[] { -74, 60, -5, -51 }, result.head().value());
}

@Test
public void inflateGood() {
final ImmutableList<Value> result = inflate(con(0xcb, 0x4d, 0x2d, 0x49, 0xcc, 0x01, 0x00)).eval(stream(), enc());
assertEquals(1, result.size);
assertEquals("metal", result.head.asString());
assertEquals(1, result.size());
assertEquals("metal", result.head().asString());
}

}

0 comments on commit 29e828c

Please sign in to comment.