Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load encoded values from disk #2542

Merged
merged 13 commits into from Mar 22, 2022
Expand Up @@ -17,6 +17,8 @@
*/
package com.graphhopper.routing.ev;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.storage.IntsRef;

/**
Expand Down Expand Up @@ -68,6 +70,22 @@ public DecimalEncodedValueImpl(String name, int bits, double minValue, double fa
throw new IllegalArgumentException("defaultIsInfinity cannot be true when minValue is negative");
}

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public DecimalEncodedValueImpl(@JsonProperty("name") String name,
@JsonProperty("bits") int bits,
@JsonProperty("minValue") int minValue,
@JsonProperty("maxValue") int maxValue,
@JsonProperty("negateReverseDirection") boolean negateReverseDirection,
@JsonProperty("storeTwoDirections") boolean storeTwoDirections,
@JsonProperty("factor") double factor,
@JsonProperty("defaultIsInfinity") boolean defaultIsInfinity,
@JsonProperty("useMaximumAsInfinity") boolean useMaximumAsInfinity) {
super(name, bits, minValue, maxValue, negateReverseDirection, storeTwoDirections);
this.factor = factor;
this.defaultIsInfinity = defaultIsInfinity;
this.useMaximumAsInfinity = useMaximumAsInfinity;
}

@Override
public void setDecimal(boolean reverse, IntsRef ref, double value) {
if (!isInitialized())
Expand Down
Expand Up @@ -17,11 +17,14 @@
*/
package com.graphhopper.routing.ev;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

/**
* This interface defines how to store and read values from a list of integers
*
* @see com.graphhopper.storage.IntsRef
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
public interface EncodedValue {

/**
Expand Down
@@ -0,0 +1,60 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.graphhopper.routing.ev;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class EncodedValueSerializer {
private final static ObjectMapper MAPPER = new ObjectMapper();

static {
MAPPER.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for consistency shall we add
MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
here?

}

public static String serializeEncodedValue(EncodedValue encodedValue) {
try {
JsonNode tree = MAPPER.valueToTree(encodedValue);
((ObjectNode) tree).put("version", encodedValue.getVersion());
return MAPPER.writeValueAsString(tree);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Could not serialize encoded value: " + encodedValue + ", error: " + e.getMessage());
}
}

public static EncodedValue deserializeEncodedValue(String serializedEncodedValue) {
try {
JsonNode jsonNode = MAPPER.readTree(serializedEncodedValue);
int storedVersion = jsonNode.get("version").asInt();
((ObjectNode) jsonNode).remove("version");
EncodedValue encodedValue = MAPPER.treeToValue(jsonNode, EncodedValue.class);
if (storedVersion != encodedValue.getVersion())
throw new IllegalStateException("Version does not match. Cannot properly read encoded value: " + encodedValue.getName() + ". " +
"You need to use the same version of GraphHopper you used to import the data");
Comment on lines +51 to +56
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit self-made and probably can also be done using Jackson Annotations, but since we aren't really planning to de/serialize encoded values with something other than these two methods I think this is fine. I'm not so experienced with Jackson and open for improvements of course.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also no expert. I tried to get it working and embedded your work into a common Deserializer (and using @JsonProperty(access = JsonProperty.Access.READ_ONLY) for getVersion to avoid a custom Serializer).

But the problem was that we don't want to manually handle the className and I wasn't able to find a way to use both:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
and
@JsonDeserialize(using = EncodedValueDeserializer.class).

The only solution was with some wrapper class but this here looks simpler IMO.

return encodedValue;
} catch (JsonProcessingException e) {
throw new IllegalStateException("Could not deserialize encoded value: " + serializedEncodedValue + ", error: " + e.getMessage());
}
}
}
Expand Up @@ -17,6 +17,9 @@
*/
package com.graphhopper.routing.ev;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.storage.IntsRef;

import java.util.Arrays;
Expand All @@ -25,14 +28,30 @@
* This class allows to store distinct values via an enum. I.e. it stores just the indices
*/
public final class EnumEncodedValue<E extends Enum> extends IntEncodedValueImpl {
private final E[] arr;
public final Class<E> enumType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be reverted back to private ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, thanks I forgot.

@JsonIgnore
public E[] arr;

public EnumEncodedValue(String name, Class<E> enumType) {
this(name, enumType, false);
}

public EnumEncodedValue(String name, Class<E> enumType, boolean storeTwoDirections) {
super(name, 32 - Integer.numberOfLeadingZeros(enumType.getEnumConstants().length - 1), storeTwoDirections);
this.enumType = enumType;
arr = enumType.getEnumConstants();
}

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public EnumEncodedValue(@JsonProperty("name") String name,
@JsonProperty("bits") int bits,
@JsonProperty("minValue") int minValue,
@JsonProperty("maxValue") int maxValue,
@JsonProperty("negateReverseDirection") boolean negateReverseDirection,
@JsonProperty("storeTwoDirections") boolean storeTwoDirections,
@JsonProperty("enumType") Class<E> enumType) {
super(name, bits, minValue, maxValue, negateReverseDirection, storeTwoDirections);
this.enumType = enumType;
arr = enumType.getEnumConstants();
}

Expand Down
Expand Up @@ -17,6 +17,9 @@
*/
package com.graphhopper.routing.ev;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.storage.IntsRef;
import com.graphhopper.util.Helper;
Expand All @@ -35,22 +38,29 @@
* and is used to save storage space.
*/
public class IntEncodedValueImpl implements IntEncodedValue {

private final String name;
private final boolean storeTwoDirections;
final int bits;
final boolean negateReverseDirection;
final int minValue;
final int maxValue;

// the following fields will be set by the init() method and we do not store them on disk, because they will be
// set again when we create the EncodingManager
/**
* There are multiple int values possible per edge. Here we specify the index into this integer array.
*/
@JsonIgnore
private int fwdDataIndex;
@JsonIgnore
private int bwdDataIndex;
private final boolean storeTwoDirections;
final int bits;
final boolean negateReverseDirection;
final int minValue;
final int maxValue;
@JsonIgnore
int fwdShift = -1;
@JsonIgnore
int bwdShift = -1;
@JsonIgnore
int fwdMask;
@JsonIgnore
int bwdMask;

/**
Expand Down Expand Up @@ -94,6 +104,21 @@ public IntEncodedValueImpl(String name, int bits, int minValue, boolean negateRe
this.negateReverseDirection = negateReverseDirection;
}

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public IntEncodedValueImpl(@JsonProperty("name") String name,
@JsonProperty("bits") int bits,
@JsonProperty("minValue") int minValue,
@JsonProperty("maxValue") int maxValue,
@JsonProperty("negateReverseDirection") boolean negateReverseDirection,
@JsonProperty("storeTwoDirections") boolean storeTwoDirections) {
this.name = name;
this.storeTwoDirections = storeTwoDirections;
this.bits = bits;
this.negateReverseDirection = negateReverseDirection;
this.minValue = minValue;
this.maxValue = maxValue;
}

@Override
public final int init(EncodedValue.InitializerConfig init) {
if (isInitialized())
Expand Down
Expand Up @@ -17,13 +17,14 @@
*/
package com.graphhopper.routing.ev;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.storage.IntsRef;

/**
* This class implements a simple boolean storage via an UnsignedIntEncodedValue with 1 bit.
*/
public final class SimpleBooleanEncodedValue extends IntEncodedValueImpl implements BooleanEncodedValue {

public SimpleBooleanEncodedValue(String name) {
this(name, false);
}
Expand All @@ -32,6 +33,18 @@ public SimpleBooleanEncodedValue(String name, boolean storeBothDirections) {
super(name, 1, storeBothDirections);
}

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public SimpleBooleanEncodedValue(
@JsonProperty("name") String name,
@JsonProperty("bits") int bits,
@JsonProperty("minValue") int minValue,
@JsonProperty("maxValue") int maxValue,
@JsonProperty("negateReverseDirection") boolean negateReverseDirection,
@JsonProperty("storeTwoDirections") boolean storeTwoDirections
) {
super(name, bits, minValue, maxValue, negateReverseDirection, storeTwoDirections);
}

@Override
public final void setBool(boolean reverse, IntsRef ref, boolean value) {
setInt(reverse, ref, value ? 1 : 0);
Expand Down
@@ -1,13 +1,10 @@
package com.graphhopper.routing.ev;

import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.storage.IntsRef;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.*;

/**
* This class holds a List of up to {@link #maxValues} encountered Strings and stores
Expand All @@ -19,7 +16,7 @@
public final class StringEncodedValue extends IntEncodedValueImpl {
private final int maxValues;
private final List<String> values;
private final ObjectIntMap<String> indexMap;
private final Map<String, Integer> indexMap;

public StringEncodedValue(String name, int expectedValueCount) {
this(name, expectedValueCount, false);
Expand All @@ -30,7 +27,7 @@ public StringEncodedValue(String name, int expectedValueCount, boolean storeTwoD

this.maxValues = roundUp(expectedValueCount);
this.values = new ArrayList<>(maxValues);
this.indexMap = new ObjectIntHashMap<>(maxValues);
this.indexMap = new HashMap<>(maxValues);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid this with a dedicated getter instead of using field access? I'm not sure if we want to deal with auto(un)boxing in a potentially hot code path.

Copy link
Member Author

@easbar easbar Mar 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really not work because we use field access? I thought we need a custom serializer for hppc, but I might be wrong. I thought about the auto unboxing for a moment but then thought it's probably not as bad bc the maps will be very small. But you are more than welcome to make this work with the hppc map again. I just did not feel like spending a lot more time on this, also because currently the default GraphHopper server does not use StringEncodedValue currently.

Copy link
Member Author

@easbar easbar Mar 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried changing indexMap to the concrete type ObjectIntHashMap and got another error which is due to the fact that HashOrderMixingStrategy is an abstract type and Jackson does not know which type to use upon de-serialization.

Copy link
Member Author

@easbar easbar Mar 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works when we add jackson-datatype-hppc: https://github.com/FasterXML/jackson-datatypes-collections
I tried this here including a little speed test: fa80be9
I could not measure a notable difference, though so I would go with java.util for now. You are invited to prove the opposite, of course.

}

public StringEncodedValue(String name, int bits, List<String> values, boolean storeTwoDirections) {
Expand All @@ -42,13 +39,33 @@ public StringEncodedValue(String name, int bits, List<String> values, boolean st
+ values.size() + " > " + maxValues);

this.values = new ArrayList<>(values);
this.indexMap = new ObjectIntHashMap<>(values.size());
this.indexMap = new HashMap<>(values.size());
int index = 1;
for (String value : values) {
indexMap.put(value, index++);
}
}

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public StringEncodedValue(
@JsonProperty("name") String name,
@JsonProperty("bits") int bits,
@JsonProperty("minValue") int minValue,
@JsonProperty("maxValue") int maxValue,
@JsonProperty("negateReverseDirection") boolean negateReverseDirection,
@JsonProperty("storeTwoDirections") boolean storeTwoDirections,
@JsonProperty("maxValues") int maxValues,
@JsonProperty("values") List<String> values,
@JsonProperty("indexMap") HashMap<String, Integer> indexMap) {
super(name, bits, minValue, maxValue, negateReverseDirection, storeTwoDirections);
if (values.size() > maxValues)
throw new IllegalArgumentException("Number of values is higher than the maximum value count: "
+ values.size() + " > " + maxValues);
this.maxValues = maxValues;
this.values = values;
this.indexMap = indexMap;
}

public final void setString(boolean reverse, IntsRef ref, String value) {
if (value == null) {
super.setInt(reverse, ref, 0);
Expand Down