Skip to content
Open
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
9 changes: 9 additions & 0 deletions hollow-perf/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ tasks.withType(JavaCompile).configureEach {

jmh {
duplicateClassesStrategy = DuplicatesStrategy.WARN
if (project.hasProperty('jmhInclude')) {
includes = [ project.property('jmhInclude') ]
}
if (project.hasProperty('jmhJvm')) {
jvm = project.property('jmhJvm')
}
if (project.hasProperty('jmhProf')) {
profilers = [ project.property('jmhProf') ]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.netflix.hollow.core.read.engine.list;

import com.netflix.hollow.core.memory.pool.ArraySegmentRecycler;
import com.netflix.hollow.core.memory.pool.RecyclingRecycler;
import com.netflix.hollow.core.memory.pool.WastefulRecycler;
import com.netflix.hollow.core.read.engine.HollowReadStateEngine;
import com.netflix.hollow.core.read.engine.HollowTypeReadState;
import com.netflix.hollow.core.read.iterator.HollowOrdinalIterator;
import com.netflix.hollow.core.util.StateEngineRoundTripper;
import com.netflix.hollow.core.write.HollowWriteStateEngine;
import com.netflix.hollow.core.write.objectmapper.HollowObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

/**
* Compares list iteration cost of the per-element read path (the generic {@code HollowListOrdinalIterator}
* pattern: {@code size()} + {@code getElementOrdinal(ordinal, i)} per element, i.e. 2N+1
* {@code Unsafe.loadFence()}s when arrays can be recycled)
* against the single-snapshot path ({@code ordinalIterator()} backed by a shard cursor). With recycling, the
* cursor validates once per element; without recycling, its captured shard is immutable and requires no trailing
* validation.
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 6, time = 1)
@Fork(1)
public class HollowListTypeReadStateIterationBenchmark {

static class ListHolder {
List<Integer> vals;
ListHolder(List<Integer> vals) { this.vals = vals; }
}

@Param({ "1", "4", "16", "64", "256" })
int listSize;

@Param({ "2000" })
int numLists;

@Param({ "recycling", "wasteful" })
String recycler;

HollowListTypeReadState listTypeState;
int[] ordinals;

@Setup
public void setUp() throws IOException {
HollowWriteStateEngine writeStateEngine = new HollowWriteStateEngine();
HollowObjectMapper objectMapper = new HollowObjectMapper(writeStateEngine);
objectMapper.initializeTypeState(ListHolder.class);

for (int i = 0; i < numLists; i++) {
List<Integer> vals = new ArrayList<>(listSize);
for (int j = 0; j < listSize; j++)
vals.add(i + j); // distinct lists so they aren't deduped to one ordinal
objectMapper.add(new ListHolder(vals));
}

ArraySegmentRecycler memoryRecycler = recycler.equals("recycling")
? new RecyclingRecycler()
: WastefulRecycler.DEFAULT_INSTANCE;
HollowReadStateEngine readStateEngine = new HollowReadStateEngine(memoryRecycler);
readStateEngine.setShardCursorIterators(true);
StateEngineRoundTripper.roundTripSnapshot(writeStateEngine, readStateEngine, null);

listTypeState = null;
for (HollowTypeReadState ts : readStateEngine.getTypeStates()) {
if (ts instanceof HollowListTypeReadState) {
listTypeState = (HollowListTypeReadState) ts;
break;
}
}
if (listTypeState == null)
throw new IllegalStateException("no list type state found");

int max = listTypeState.maxOrdinal();
List<Integer> populated = new ArrayList<>();
for (int o = 0; o <= max; o++)
populated.add(o);
ordinals = new int[populated.size()];
for (int i = 0; i < ordinals.length; i++)
ordinals[i] = populated.get(i);
}

/** New path: one shard snapshot per list, validated per element only when arrays can be recycled. */
@Benchmark
public void iterateSnapshot(Blackhole bh) {
for (int ordinal : ordinals) {
HollowOrdinalIterator iter = listTypeState.ordinalIterator(ordinal);
int o = iter.next();
while (o != HollowOrdinalIterator.NO_MORE_ORDINALS) {
bh.consume(o);
o = iter.next();
}
}
}

/** Old path: size() then getElementOrdinal(ordinal, i) per element. */
@Benchmark
public void iteratePerElement(Blackhole bh) {
for (int ordinal : ordinals) {
int size = listTypeState.size(ordinal);
for (int i = 0; i < size; i++) {
bh.consume(listTypeState.getElementOrdinal(ordinal, i));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.netflix.hollow.core.read.engine.map;

import com.netflix.hollow.core.memory.encoding.HashCodes;
import com.netflix.hollow.core.memory.pool.ArraySegmentRecycler;
import com.netflix.hollow.core.memory.pool.RecyclingRecycler;
import com.netflix.hollow.core.memory.pool.WastefulRecycler;
import com.netflix.hollow.core.read.engine.HollowReadStateEngine;
import com.netflix.hollow.core.read.engine.HollowTypeReadState;
import com.netflix.hollow.core.read.iterator.HollowMapEntryOrdinalIterator;
import com.netflix.hollow.core.util.StateEngineRoundTripper;
import com.netflix.hollow.core.write.HollowWriteStateEngine;
import com.netflix.hollow.core.write.objectmapper.HollowObjectMapper;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

/**
* Compares map entry iteration cost of the per-bucket read path (the generic
* {@code HollowMapEntryOrdinalIteratorImpl} pattern: {@code size()} + {@code relativeBucket(ordinal, bucket)}
* over every hash bucket, i.e. two fences per bucket when arrays can be recycled) against the single-snapshot
* entry iterator. With recycling, the cursor validates once per surfaced entry; without recycling, its captured
* shard is immutable and requires no trailing validation.
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 6, time = 1)
@Fork(1)
public class HollowMapTypeReadStateIterationBenchmark {

static class MapHolder {
Map<Integer, Integer> vals;
MapHolder(Map<Integer, Integer> vals) { this.vals = vals; }
}

@Param({ "1", "4", "16", "64", "256" })
int mapSize;

@Param({ "2000" })
int numMaps;

@Param({ "recycling", "wasteful" })
String recycler;

HollowMapTypeReadState mapTypeState;
int[] ordinals;

@Setup
public void setUp() throws IOException {
HollowWriteStateEngine writeStateEngine = new HollowWriteStateEngine();
HollowObjectMapper objectMapper = new HollowObjectMapper(writeStateEngine);
objectMapper.initializeTypeState(MapHolder.class);

for (int i = 0; i < numMaps; i++) {
Map<Integer, Integer> vals = new LinkedHashMap<>();
for (int j = 0; j < mapSize; j++)
vals.put(i + j, i + j + 1); // distinct maps so they aren't deduped to one ordinal
objectMapper.add(new MapHolder(vals));
}

ArraySegmentRecycler memoryRecycler = recycler.equals("recycling")
? new RecyclingRecycler()
: WastefulRecycler.DEFAULT_INSTANCE;
HollowReadStateEngine readStateEngine = new HollowReadStateEngine(memoryRecycler);
readStateEngine.setShardCursorIterators(true);
StateEngineRoundTripper.roundTripSnapshot(writeStateEngine, readStateEngine, null);

mapTypeState = null;
for (HollowTypeReadState ts : readStateEngine.getTypeStates()) {
if (ts instanceof HollowMapTypeReadState) {
mapTypeState = (HollowMapTypeReadState) ts;
break;
}
}
if (mapTypeState == null)
throw new IllegalStateException("no map type state found");

int max = mapTypeState.maxOrdinal();
ordinals = new int[max + 1];
for (int o = 0; o <= max; o++)
ordinals[o] = o;
}

/** New path: one shard snapshot per map, validated per entry only when arrays can be recycled. */
@Benchmark
public void iterateSnapshot(Blackhole bh) {
for (int ordinal : ordinals) {
HollowMapEntryOrdinalIterator iter = mapTypeState.ordinalIterator(ordinal);
while (iter.next()) {
bh.consume(iter.getKey());
bh.consume(iter.getValue());
}
}
}

/** Old path: size() then relativeBucket(ordinal, bucket) over every hash bucket. */
@Benchmark
public void iteratePerBucket(Blackhole bh) {
for (int ordinal : ordinals) {
int numBuckets = HashCodes.hashTableSize(mapTypeState.size(ordinal));
for (int b = 0; b < numBuckets; b++) {
long bucketVal = mapTypeState.relativeBucket(ordinal, b);
if (bucketVal != -1L) {
bh.consume((int) (bucketVal >>> 32));
bh.consume((int) bucketVal);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.netflix.hollow.core.read.engine.set;

import static com.netflix.hollow.core.HollowConstants.ORDINAL_NONE;

import com.netflix.hollow.core.memory.encoding.HashCodes;
import com.netflix.hollow.core.memory.pool.ArraySegmentRecycler;
import com.netflix.hollow.core.memory.pool.RecyclingRecycler;
import com.netflix.hollow.core.memory.pool.WastefulRecycler;
import com.netflix.hollow.core.read.engine.HollowReadStateEngine;
import com.netflix.hollow.core.read.engine.HollowTypeReadState;
import com.netflix.hollow.core.read.iterator.HollowOrdinalIterator;
import com.netflix.hollow.core.util.StateEngineRoundTripper;
import com.netflix.hollow.core.write.HollowWriteStateEngine;
import com.netflix.hollow.core.write.objectmapper.HollowObjectMapper;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

/**
* Compares set iteration cost of the per-bucket read path (the generic {@code HollowSetOrdinalIterator}
* pattern: {@code size()} + {@code relativeBucketValue(ordinal, bucket)} over every hash bucket, i.e. two fences
* per bucket when arrays can be recycled) against the single-snapshot iterator. With recycling, the cursor
* validates once per surfaced element; without recycling, its captured shard is immutable and requires no
* trailing validation.
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 6, time = 1)
@Fork(1)
public class HollowSetTypeReadStateIterationBenchmark {

static class SetHolder {
Set<Integer> vals;
SetHolder(Set<Integer> vals) { this.vals = vals; }
}

@Param({ "1", "4", "16", "64", "256" })
int setSize;

@Param({ "2000" })
int numSets;

@Param({ "recycling", "wasteful" })
String recycler;

HollowSetTypeReadState setTypeState;
int[] ordinals;

@Setup
public void setUp() throws IOException {
HollowWriteStateEngine writeStateEngine = new HollowWriteStateEngine();
HollowObjectMapper objectMapper = new HollowObjectMapper(writeStateEngine);
objectMapper.initializeTypeState(SetHolder.class);

for (int i = 0; i < numSets; i++) {
Set<Integer> vals = new LinkedHashSet<>();
for (int j = 0; j < setSize; j++)
vals.add(i + j); // distinct sets so they aren't deduped to one ordinal
objectMapper.add(new SetHolder(vals));
}

ArraySegmentRecycler memoryRecycler = recycler.equals("recycling")
? new RecyclingRecycler()
: WastefulRecycler.DEFAULT_INSTANCE;
HollowReadStateEngine readStateEngine = new HollowReadStateEngine(memoryRecycler);
readStateEngine.setShardCursorIterators(true);
StateEngineRoundTripper.roundTripSnapshot(writeStateEngine, readStateEngine, null);

setTypeState = null;
for (HollowTypeReadState ts : readStateEngine.getTypeStates()) {
if (ts instanceof HollowSetTypeReadState) {
setTypeState = (HollowSetTypeReadState) ts;
break;
}
}
if (setTypeState == null)
throw new IllegalStateException("no set type state found");

int max = setTypeState.maxOrdinal();
ordinals = new int[max + 1];
for (int o = 0; o <= max; o++)
ordinals[o] = o;
}

/** New path: one shard snapshot per set, validated per element only when arrays can be recycled. */
@Benchmark
public void iterateSnapshot(Blackhole bh) {
for (int ordinal : ordinals) {
HollowOrdinalIterator iter = setTypeState.ordinalIterator(ordinal);
int o = iter.next();
while (o != HollowOrdinalIterator.NO_MORE_ORDINALS) {
bh.consume(o);
o = iter.next();
}
}
}

/** Old path: size() then relativeBucketValue(ordinal, bucket) over every hash bucket. */
@Benchmark
public void iteratePerBucket(Blackhole bh) {
for (int ordinal : ordinals) {
int numBuckets = HashCodes.hashTableSize(setTypeState.size(ordinal));
for (int b = 0; b < numBuckets; b++) {
int v = setTypeState.relativeBucketValue(ordinal, b);
if (v != ORDINAL_NONE)
bh.consume(v);
}
}
}
}
Loading
Loading