Skip to content

Commit

Permalink
avoid lookups for cached meters in global registry (#401)
Browse files Browse the repository at this point in the history
Fixes #393. CompositeRegistry has been updated to use
a swappable counter and only update the copies when the
set of registries is changed (rare).

For cached meters, this means the only additional
overhead is the cost of the wrapper and access via a
volatile member variable. How much benefit this provides
depends a lot of the lookup cost which depends on the
overall number of meters, hashing cost of typical ids,
and concurrency. For the simple case benchmared it is
about a 3x improvement when using a composite with a
single DefaultRegistry added in:

```
Benchmark      Mode  Cnt         Score         Error  Units
before        thrpt   10  19433058.126 ± 4927554.937  ops/s
after         thrpt   10  61043422.331 ± 3085269.565  ops/s
```
  • Loading branch information
brharrington committed May 1, 2017
1 parent 6ec448c commit 288fcb5
Show file tree
Hide file tree
Showing 23 changed files with 576 additions and 277 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ subprojects {
warmupIterations = 10
iterations = 10
fork = 1
threads = 1
profilers = ['stack']
include '.*Counters.*'
}
Expand Down
227 changes: 66 additions & 161 deletions spectator-api/src/jmh/java/com/netflix/spectator/perf/Counters.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2015 Netflix, Inc.
/*
* Copyright 2014-2017 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,16 +22,65 @@
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.concurrent.ThreadLocalRandom;

/**
* Summary of results on m4.16xlarge:
*
* <pre>
* ### T1 Composite(Empty)
*
* ```
* Benchmark Mode Cnt Score Error Units
* Counters.cached thrpt 10 222995027.222 ± 5812215.685 ops/s
* Counters.lookup thrpt 10 34596370.526 ± 7975715.214 ops/s
* Counters.random thrpt 10 5699426.669 ± 604639.108 ops/s
* ```
*
* ### T1 Composite(Noop)
*
* ```
* Benchmark Mode Cnt Score Error Units
* Counters.cached thrpt 10 221034201.857 ± 9204618.077 ops/s
* Counters.lookup thrpt 10 33410400.013 ± 7828970.416 ops/s
* Counters.random thrpt 10 5977032.777 ± 679753.009 ops/s
* ```
*
* ### T1 Composite(Default)
*
* ```
* Benchmark Mode Cnt Score Error Units
* Counters.cached thrpt 10 61043422.331 ± 3085269.565 ops/s
* Counters.lookup thrpt 10 25989379.563 ± 4981909.126 ops/s
* Counters.random thrpt 10 4299422.647 ± 394069.294 ops/s
* ```
*
* ### T1 Composite(Noop, Noop)
*
* ```
* Benchmark Mode Cnt Score Error Units
* Counters.cached thrpt 10 65781502.616 ± 3124211.952 ops/s
* Counters.lookup thrpt 10 23914193.535 ± 6256980.210 ops/s
* Counters.random thrpt 10 3907696.564 ± 383335.366 ops/s
* ```
*
* ### T1 Composite(Default, Default)
*
* ```
* Benchmark Mode Cnt Score Error Units
* Counters.cached thrpt 10 37594426.749 ± 1302829.135 ops/s
* Counters.lookup thrpt 10 17151030.656 ± 3776435.406 ops/s
* Counters.random thrpt 10 2228890.157 ± 186029.279 ops/s
* ```
* </pre>
*/
@State(Scope.Thread)
public class Counters {

Expand All @@ -43,21 +92,19 @@ public static class Data {
String[] newNames;

public Data() {
names = new String[100000];
newNames = new String[100000];
for (int i = 0; i < 100000; ++i) {
registry.counter(UUID.randomUUID().toString()).increment();
names[i] = UUID.randomUUID().toString();
registry.counter(names[i]).increment();
newNames[i] = UUID.randomUUID().toString();
}
names = registry.counters()
.map(c -> c.id().name())
.collect(Collectors.toList())
.toArray(new String[]{});
}
}

@State(Scope.Thread)
public static class Metrics {
private Random random = new Random();
private Random random = ThreadLocalRandom.current();

Counter get(Data data) {
// Assumes about 5% of lookups will be for a new or expired counter. This is
Expand All @@ -70,166 +117,24 @@ Counter get(Data data) {
}
}

@Threads(1)
@Benchmark
public void cached_T001(Data data) {
data.cached.increment();
}

@Threads(1)
@Benchmark
public void lookup_T001(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(1)
@Benchmark
public void random_T001(Data data, Metrics metrics) {
metrics.get(data).increment();
}

@Threads(2)
@Benchmark
public void cached_T002(Data data) {
data.cached.increment();
}

@Threads(2)
@Benchmark
public void lookup_T002(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(2)
@Benchmark
public void random_T002(Data data, Metrics metrics) {
metrics.get(data).increment();
}

@Threads(4)
@Benchmark
public void cached_T004(Data data) {
data.cached.increment();
}

@Threads(4)
@Benchmark
public void lookup_T004(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(4)
@Benchmark
public void random_T004(Data data, Metrics metrics) {
metrics.get(data).increment();
}

@Threads(8)
@Benchmark
public void cached_T008(Data data) {
data.cached.increment();
}

@Threads(8)
@Benchmark
public void lookup_T008(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(8)
@Benchmark
public void random_T008(Data data, Metrics metrics) {
metrics.get(data).increment();
}

@Threads(16)
@Benchmark
public void cached_T016(Data data) {
data.cached.increment();
}

@Threads(16)
@Benchmark
public void lookup_T016(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(16)
@Benchmark
public void random_T016(Data data, Metrics metrics) {
metrics.get(data).increment();
}

@Threads(32)
@Benchmark
public void cached_T032(Data data) {
data.cached.increment();
}

@Threads(32)
@Benchmark
public void lookup_T032(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(32)
@Benchmark
public void random_T032(Data data, Metrics metrics) {
metrics.get(data).increment();
}

@Threads(64)
@Benchmark
public void cached_T064(Data data) {
data.cached.increment();
}

@Threads(64)
@Benchmark
public void lookup_T064(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(64)
@Benchmark
public void random_T064(Data data, Metrics metrics) {
metrics.get(data).increment();
}

@Threads(128)
@Benchmark
public void cached_T128(Data data) {
data.cached.increment();
}

@Threads(128)
@Benchmark
public void lookup_T128(Data data) {
data.registry.counter("lookup").increment();
}

@Threads(128)
@Benchmark
public void random_T128(Data data, Metrics metrics) {
metrics.get(data).increment();
private long incrementAndGet(Counter c) {
c.increment();
return c.count();
}

@Threads(256)
@Benchmark
public void cached_T256(Data data) {
data.cached.increment();
public long cached(Data data) {
return incrementAndGet(data.cached);
}

@Threads(256)
@Benchmark
public void lookup_T256(Data data) {
data.registry.counter("lookup").increment();
public long lookup(Data data) {
return incrementAndGet(data.registry.counter("lookup"));
}

@Threads(256)
@Benchmark
public void random_T256(Data data, Metrics metrics) {
metrics.get(data).increment();
public long random(Data data, Metrics metrics) {
return incrementAndGet(metrics.get(data));
}

@TearDown
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2015 Netflix, Inc.
/*
* Copyright 2014-2017 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,27 +19,27 @@
import java.util.Iterator;

/** Counter implementation for the composite registry. */
final class CompositeCounter extends CompositeMeter implements Counter {
final class CompositeCounter extends CompositeMeter<Counter> implements Counter {

/** Create a new instance. */
CompositeCounter(Id id, Collection<Registry> registries) {
super(id, registries);
CompositeCounter(Id id, Collection<Counter> counters) {
super(id, counters);
}

@Override public void increment() {
for (Registry r : registries) {
r.counter(id).increment();
for (Counter c : meters) {
c.increment();
}
}

@Override public void increment(long amount) {
for (Registry r : registries) {
r.counter(id).increment(amount);
for (Counter c : meters) {
c.increment(amount);
}
}

@Override public long count() {
Iterator<Registry> it = registries.iterator();
return it.hasNext() ? it.next().counter(id).count() : 0L;
Iterator<Counter> it = meters.iterator();
return it.hasNext() ? it.next().count() : 0L;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2015 Netflix, Inc.
/*
* Copyright 2014-2017 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,26 +19,26 @@
import java.util.Iterator;

/** Distribution summary implementation for the composite registry. */
final class CompositeDistributionSummary extends CompositeMeter implements DistributionSummary {
final class CompositeDistributionSummary extends CompositeMeter<DistributionSummary> implements DistributionSummary {

/** Create a new instance. */
CompositeDistributionSummary(Id id, Collection<Registry> registries) {
super(id, registries);
CompositeDistributionSummary(Id id, Collection<DistributionSummary> summaries) {
super(id, summaries);
}

@Override public void record(long amount) {
for (Registry r : registries) {
r.distributionSummary(id).record(amount);
for (DistributionSummary d : meters) {
d.record(amount);
}
}

@Override public long count() {
Iterator<Registry> it = registries.iterator();
return it.hasNext() ? it.next().distributionSummary(id).count() : 0L;
Iterator<DistributionSummary> it = meters.iterator();
return it.hasNext() ? it.next().count() : 0L;
}

@Override public long totalAmount() {
Iterator<Registry> it = registries.iterator();
return it.hasNext() ? it.next().distributionSummary(id).totalAmount() : 0L;
Iterator<DistributionSummary> it = meters.iterator();
return it.hasNext() ? it.next().totalAmount() : 0L;
}
}
Loading

0 comments on commit 288fcb5

Please sign in to comment.