Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion benchmarks/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ threads="${THREADS:-$(getconf _NPROCESSORS_ONLN)}"
heap="${HEAP:-4g}"
history="${HISTORY:-64}"
runs="${RUNS:-3}"
cache="${CACHE_MB:-0}"
results="results/scale-$scale-$durability-history-$history"
if [ "$cache" != 0 ]; then
results="$results-cache-$cache"
fi

../mvnw -q -f ../pom.xml -Pbenchmarks -DskipTests install
classpath="target/classes:$(cat target/classpath.txt)"
Expand All @@ -22,7 +26,7 @@ rm -rf "$results" && mkdir -p "$results"
for run in $(seq 1 "$runs"); do
for store in hstore hypergraphdb; do
java "${jvm[@]}" io.hstore.bench.Comparison run --store "$store" --scale "$scale" --durability "$durability" \
--threads "$threads" --history "$history" --out "$results/$store-$run.json"
--threads "$threads" --history "$history" --cache-mb "$cache" --out "$results/$store-$run.json"
done
done
java "${jvm[@]}" io.hstore.bench.Comparison report "$results"/hstore-*.json "$results"/hypergraphdb-*.json | tee "$results/report.md"
7 changes: 4 additions & 3 deletions benchmarks/src/main/java/io/hstore/bench/Comparison.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static void main(String[] args) throws Exception {
case "report" -> IO.println(Report.markdown(args));
default -> {
IO.println("""
usage: Comparison run --store hstore|hypergraphdb [--scale N] [--durability async|sync] [--threads N] [--history N] --out FILE
usage: Comparison run --store hstore|hypergraphdb [--scale N] [--durability async|sync] [--threads N] [--history N] [--cache-mb N] --out FILE
Comparison report FILE... (runs of two stores; cells show the median and range)""");
System.exit(2);
}
Expand All @@ -97,13 +97,14 @@ private static void run(Map<String, String> options) throws Exception {
int threads = Integer.parseInt(options.getOrDefault("threads", String.valueOf(Runtime.getRuntime().availableProcessors())));
Path out = Path.of(options.getOrDefault("out", "results/" + storeName + ".json"));
Path directory = Files.createTempDirectory("hstore-bench-" + storeName);
long cacheBytes = Long.parseLong(options.getOrDefault("cache-mb", "0")) << 20;
Dataset dataset = Dataset.generate(scale, SEED);
List<Result> results = new ArrayList<>();
Map<String, Json> totals = new LinkedHashMap<>();
long baselineHeap = JvmUsage.retainedHeap();
Store store = switch (storeName) {
case "hstore" -> new HStoreStore(directory, sync, Integer.parseInt(options.getOrDefault("history", "64")));
case "hypergraphdb" -> new HyperGraphDbStore(directory, sync);
case "hstore" -> new HStoreStore(directory, sync, Integer.parseInt(options.getOrDefault("history", "64")), cacheBytes);
case "hypergraphdb" -> new HyperGraphDbStore(directory, sync, cacheBytes);
default -> throw new IllegalArgumentException("unknown store " + storeName);
};
IO.println("%s: %,d nodes, %,d hyperedges, %,d incidences, durability %s, %d threads"
Expand Down
15 changes: 11 additions & 4 deletions benchmarks/src/main/java/io/hstore/bench/HStoreStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ final class HStoreStore implements Store {

private final int history;

HStoreStore(Path directory, boolean sync, int history) {
private final int cachedNodes;
private final long cacheBytes;

HStoreStore(Path directory, boolean sync, int history, long cacheBytes) {
this.directory = directory;
this.history = history;
this.options = DatabaseOptions.defaults().withEngine(EngineOptions.defaults()
.withDurability(sync ? Durability.SYNC : Durability.ASYNC).withCachedNodes(CACHED_NODES).withHistoryLimit(history));
this.cacheBytes = cacheBytes;
EngineOptions engine = EngineOptions.defaults();
this.cachedNodes = cacheBytes > 0 ? Math.toIntExact(cacheBytes / engine.pageSize()) : CACHED_NODES;
this.options = DatabaseOptions.defaults().withEngine(engine
.withDurability(sync ? Durability.SYNC : Durability.ASYNC).withCachedNodes(cachedNodes).withHistoryLimit(history));
this.database = HypergraphDatabase.open(directory, options);
database.write(writer -> {
writer.defineNode(NODE, List.of(new PropertyDef("name", TypeTag.STRING, false, false)));
Expand All @@ -59,7 +65,8 @@ public String version() {

@Override
public String cache() {
return "node cache of %,d decoded nodes and retains %d generations of history".formatted(CACHED_NODES, history);
String budget = cacheBytes > 0 ? " (%,d MiB divided by the page size)".formatted(cacheBytes >> 20) : "";
return "node cache of %,d decoded nodes%s and retains %d generations of history".formatted(cachedNodes, budget, history);
}

@Override
Expand Down
16 changes: 12 additions & 4 deletions benchmarks/src/main/java/io/hstore/bench/HyperGraphDbStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sleepycat.je.Durability;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.StatsConfig;
import org.hypergraphdb.HGConfiguration;
Expand Down Expand Up @@ -29,12 +30,18 @@ final class HyperGraphDbStore implements Store {
private HGPersistentHandle large;
private long writtenBeforeReopen;

HyperGraphDbStore(Path directory, boolean sync) {
private final long cacheBytes;

HyperGraphDbStore(Path directory, boolean sync, long cacheBytes) {
this.directory = directory;
this.cacheBytes = cacheBytes;
configuration.setTransactional(true);
EnvironmentConfig environment = ((BJEStorageImplementation) configuration.getStoreImplementation()).getConfiguration().getEnvironmentConfig();
if (sync) {
((BJEStorageImplementation) configuration.getStoreImplementation()).getConfiguration()
.getEnvironmentConfig().setDurability(Durability.COMMIT_SYNC);
environment.setDurability(Durability.COMMIT_SYNC);
}
if (cacheBytes > 0) {
environment.setCacheSize(cacheBytes);
}
this.graph = HGEnvironment.get(directory.toString(), configuration);
}
Expand All @@ -51,7 +58,8 @@ public String version() {

@Override
public String cache() {
return "Berkeley DB JE cache at 30% of the heap (HyperGraphDB default) plus the HyperGraphDB atom cache";
String je = cacheBytes > 0 ? "Berkeley DB JE cache of %,d MiB".formatted(cacheBytes >> 20) : "Berkeley DB JE cache at 30% of the heap (HyperGraphDB default)";
return je + " plus the HyperGraphDB atom cache";
}

@Override
Expand Down
22 changes: 21 additions & 1 deletion docs/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ flowchart LR
|---|---|---|
| Version | 0.1.0 (this repository) | 1.4 at commit `99485a1`, storage `bdb-je` on Berkeley DB JE 5.0.73 |
| Data model used | node type `Entity` (indexed by key, property `name`); `SET_EDGE` type `Relation` | `String` atoms; `HGPlainLink` links |
| Cache | node cache of 1,048,576 decoded nodes | JE cache at 30% of the heap (HyperGraphDB default) plus the HyperGraphDB atom cache |
| Cache | node cache of 1,048,576 decoded nodes, or `CACHE_MB` / page size | JE cache at 30% of the heap (HyperGraphDB default), or `CACHE_MB`, plus the HyperGraphDB atom cache |
| `async` durability | `durability = async` | HyperGraphDB default: JE `WRITE_NO_SYNC` |
| `sync` durability | `durability = sync`, `wal_mode = references`, group commit | JE `COMMIT_SYNC` |
| History | `history_limit = 64` generations retained for time travel (default) | none |
Expand Down Expand Up @@ -210,3 +210,23 @@ SCALE=4 DURABILITY=async HISTORY=1 benchmarks/run.sh
run in brackets, and "Results agree" requires every run of both stores to return the same checksum. `THREADS`
and `HEAP` override the thread count and heap size. Close other
workloads first: the numbers are only meaningful on an otherwise idle machine.

**Equal cache budgets.** By default the two caches are not the same size. HStore caches about a million decoded
nodes, which at 16 KiB pages is enough to hold the whole dataset. HyperGraphDB gets JE's default of 30% of the
heap. `CACHE_MB` gives both the same budget:

| HStore | HyperGraphDB |
|---|---|
| node cache of `CACHE_MB` / page size nodes | JE cache of exactly `CACHE_MB` |

Decoded nodes can take more or less heap than their page, and HyperGraphDB's own atom and incidence caches can't
be capped in bytes (they shrink under memory pressure). So check the retained-heap line in the report to see what
each store actually kept. Results go to a directory ending in `-cache-<mb>`.

```
SCALE=4 CACHE_MB=64 benchmarks/run.sh # working set several times larger than either cache
```

With a small budget, reads that miss the cache go to the files, but the operating system's page cache will
usually still serve them from memory. Reads only really hit the disk when the data is larger than the machine's
free RAM.
Loading