Skip to content

Commit

Permalink
Issue ReactiveX#7 RingBitSet optimisation
Browse files Browse the repository at this point in the history
* test coverage added + jacoco excludes for benchmark classes

* Issue ReactiveX#7 RingBitSet optimisation + benchmark results

* Issue ReactiveX#7 cleanup
  • Loading branch information
storozhukBM authored and RobWin committed Dec 13, 2016
1 parent 17b2197 commit 7b282af
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 224 deletions.
4 changes: 2 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ static <T> Supplier<T> decorateSupplier(Supplier<T> supplier, CircuitBreaker cir
The state of the CircuitBreaker changes from `CLOSED` to `OPEN` when the failure rate is above a (configurable) threshold.
Then, all access to the backend is blocked for a (configurable) time duration. `CircuitBreaker::isCallPermitted()` throws a `CircuitBreakerOpenException`, if the CircuitBreaker is `OPEN`.

The CircuitBreaker uses a Ring Bit Buffer in the `CLOSED` state to store the success or failure statuses of the calls. A successful call is stored as a `0` bit and a failed call is stored as a `1` bit. The Ring Bit Buffer has a (configurable) fixed-size. The Ring Bit Buffer uses internally a https://docs.oracle.com/javase/8/docs/api/java/util/BitSet.html[BitSet] to store the bits which is saving memory compared to a boolean array. The BitSet uses a long[] array to store the bits. That means the BitSet only needs an array of 16 long (64-bit) values to store the status of 1024 calls.
The CircuitBreaker uses a Ring Bit Buffer in the `CLOSED` state to store the success or failure statuses of the calls. A successful call is stored as a `0` bit and a failed call is stored as a `1` bit. The Ring Bit Buffer has a (configurable) fixed-size. The Ring Bit Buffer uses internally a https://docs.oracle.com/javase/8/docs/api/java/util/BitSet.html[BitSet] like data structure to store the bits which is saving memory compared to a boolean array. The BitSet uses a long[] array to store the bits. That means the BitSet only needs an array of 16 long (64-bit) values to store the status of 1024 calls.

image::src/docs/asciidoc/images/ring_buffer.jpg[Ring Bit Buffer]

Expand Down Expand Up @@ -279,7 +279,7 @@ So you can easily restrict not only network calls but your local in-memory opera

== License

Copyright 2016 Robert Winkler
Copyright 2016 Robert Winkler and Bohdan Storozhuk

Licensed 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

Expand Down
6 changes: 0 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,6 @@ artifacts {

cobertura {
coverageFormats = ['html', 'xml']
// afterEvaluate {
// classDirectories = files(classDirectories.files.collect {
// fileTree(dir: it,
// exclude: ['**/**Benchmark**'])
// })
// }
}

tasks.coveralls {
Expand Down
2 changes: 1 addition & 1 deletion src/docs/asciidoc/usage_guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ static <T> Supplier<T> decorateSupplier(Supplier<T> supplier, CircuitBreaker cir
The state of the CircuitBreaker changes from `CLOSED` to `OPEN` when the failure rate is above a (configurable) threshold.
Then, all access to the backend is blocked for a (configurable) time duration. `CircuitBreaker::isCallPermitted()` throws a `CircuitBreakerOpenException`, if the CircuitBreaker is `OPEN`.

The CircuitBreaker uses a Ring Bit Buffer in the `CLOSED` state to store the success or failure statuses of the calls. A successful call is stored as a `0` bit and a failed call is stored as a `1` bit. The Ring Bit Buffer has a (configurable) fixed-size. The Ring Bit Buffer uses internally a https://docs.oracle.com/javase/8/docs/api/java/util/BitSet.html[BitSet] to store the bits which is saving memory compared to a boolean array. The BitSet uses a long[] array to store the bits. That means the BitSet only needs an array of 16 long (64-bit) values to store the status of 1024 calls.
The CircuitBreaker uses a Ring Bit Buffer in the `CLOSED` state to store the success or failure statuses of the calls. A successful call is stored as a `0` bit and a failed call is stored as a `1` bit. The Ring Bit Buffer has a (configurable) fixed-size. The Ring Bit Buffer uses internally a https://docs.oracle.com/javase/8/docs/api/java/util/BitSet.html[BitSet] like data structure to store the bits which is saving memory compared to a boolean array. The BitSet uses a long[] array to store the bits. That means the BitSet only needs an array of 16 long (64-bit) values to store the status of 1024 calls.

image::images/ring_buffer.jpg[Ring Bit Buffer]

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright 2016 Robert Winkler
* Copyright 2016 Robert Winkler and Bohdan Storozhuk
*
* 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,24 +19,38 @@
package io.github.robwin.circuitbreaker;

import io.github.robwin.circuitbreaker.internal.RingBitSet;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Group;
import org.openjdk.jmh.annotations.GroupThreads;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
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;

import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.All)
public class RingBitSetBenchmark {

private RingBitSet ringBitSet;
private static final int CAPACITY = 1000;
private static final int ITERATION_COUNT = 10;
private static final int WARMUP_COUNT = 10;
private static final int THREAD_COUNT = 10;
private static final int FORK_COUNT = 1;
private static final int THREAD_COUNT = 2;
private static final int FORK_COUNT = 2;

private RingBitSet ringBitSet;

@Setup
public void setUp() {
ringBitSet = new RingBitSet(1000);
ringBitSet = new RingBitSet(CAPACITY);
}

@Benchmark
Expand All @@ -45,18 +59,21 @@ public void setUp() {
@GroupThreads(THREAD_COUNT)
@Warmup(iterations = WARMUP_COUNT)
@Measurement(iterations = ITERATION_COUNT)
public void setBits(){
ringBitSet.setNextBit(true);
ringBitSet.setNextBit(false);
public void concurrentSetBits(Blackhole bh) {
int firstCardinality = ringBitSet.setNextBit(true);
bh.consume(firstCardinality);
int secondCardinality = ringBitSet.setNextBit(false);
bh.consume(secondCardinality);
}

@Benchmark
@Fork(value = FORK_COUNT)
@Group("ringBitSet")
@GroupThreads(THREAD_COUNT)
@GroupThreads(1)
@Warmup(iterations = WARMUP_COUNT)
@Measurement(iterations = ITERATION_COUNT)
public int cardinality(){
return ringBitSet.cardinality();
public void concurrentCardinality(Blackhole bh) {
int cardinality = ringBitSet.cardinality();
bh.consume(cardinality);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
*
* Copyright 2016 Robert Winkler and Bohdan Storozhuk
*
* Licensed 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 io.github.robwin.circuitbreaker.internal;

/**
* {@link BitSetMod} is simplified version of {@link java.util.BitSet}.
* It has no dynamic allocation, expanding logic, boundary checks
* and it's set method returns previous bit state.
*/
class BitSetMod {

private final static int ADDRESS_BITS_PER_WORD = 6;
private final int size;
private final long[] words;


BitSetMod(final int capacity) {
int countOfWordsRequired = wordIndex(capacity - 1) + 1;
size = countOfWordsRequired << ADDRESS_BITS_PER_WORD;
words = new long[countOfWordsRequired];
}

/**
* Given a bit index, return word index containing it.
*/
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}

/**
* Sets the bit at the specified index to value.
*
* @param bitIndex a bit index
* @return previous state of bitIndex that can be {@code 1} or {@code 0}
* @throws IndexOutOfBoundsException if the specified index is negative
*/
int set(int bitIndex, boolean value) {
int wordIndex = wordIndex(bitIndex);
long bitMask = 1L << bitIndex;
int previous = (words[wordIndex] & bitMask) != 0 ? 1 : 0;
if (value) {
words[wordIndex] |= bitMask;
} else {
words[wordIndex] &= ~bitMask;
}
return previous;
}

int size() {
return size;
}
}
Loading

0 comments on commit 7b282af

Please sign in to comment.