Skip to content

Commit 7b282af

Browse files
storozhukBMRobWin
authored andcommitted
Issue ReactiveX#7 RingBitSet optimisation
* test coverage added + jacoco excludes for benchmark classes * Issue ReactiveX#7 RingBitSet optimisation + benchmark results * Issue ReactiveX#7 cleanup
1 parent 17b2197 commit 7b282af

File tree

9 files changed

+223
-224
lines changed

9 files changed

+223
-224
lines changed

README.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ static <T> Supplier<T> decorateSupplier(Supplier<T> supplier, CircuitBreaker cir
240240
The state of the CircuitBreaker changes from `CLOSED` to `OPEN` when the failure rate is above a (configurable) threshold.
241241
Then, all access to the backend is blocked for a (configurable) time duration. `CircuitBreaker::isCallPermitted()` throws a `CircuitBreakerOpenException`, if the CircuitBreaker is `OPEN`.
242242

243-
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.
243+
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.
244244

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

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

280280
== License
281281

282-
Copyright 2016 Robert Winkler
282+
Copyright 2016 Robert Winkler and Bohdan Storozhuk
283283

284284
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
285285

build.gradle

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,6 @@ artifacts {
105105

106106
cobertura {
107107
coverageFormats = ['html', 'xml']
108-
// afterEvaluate {
109-
// classDirectories = files(classDirectories.files.collect {
110-
// fileTree(dir: it,
111-
// exclude: ['**/**Benchmark**'])
112-
// })
113-
// }
114108
}
115109

116110
tasks.coveralls {

src/docs/asciidoc/usage_guide.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ static <T> Supplier<T> decorateSupplier(Supplier<T> supplier, CircuitBreaker cir
342342
The state of the CircuitBreaker changes from `CLOSED` to `OPEN` when the failure rate is above a (configurable) threshold.
343343
Then, all access to the backend is blocked for a (configurable) time duration. `CircuitBreaker::isCallPermitted()` throws a `CircuitBreakerOpenException`, if the CircuitBreaker is `OPEN`.
344344

345-
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.
345+
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.
346346

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

src/jmh/java/io/github/robwin/circuitbreaker/CircularBufferBenchmark.java

Lines changed: 0 additions & 103 deletions
This file was deleted.
Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
*
3-
* Copyright 2016 Robert Winkler
3+
* Copyright 2016 Robert Winkler and Bohdan Storozhuk
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -19,24 +19,38 @@
1919
package io.github.robwin.circuitbreaker;
2020

2121
import io.github.robwin.circuitbreaker.internal.RingBitSet;
22-
import org.openjdk.jmh.annotations.*;
22+
import org.openjdk.jmh.annotations.Benchmark;
23+
import org.openjdk.jmh.annotations.BenchmarkMode;
24+
import org.openjdk.jmh.annotations.Fork;
25+
import org.openjdk.jmh.annotations.Group;
26+
import org.openjdk.jmh.annotations.GroupThreads;
27+
import org.openjdk.jmh.annotations.Measurement;
28+
import org.openjdk.jmh.annotations.Mode;
29+
import org.openjdk.jmh.annotations.OutputTimeUnit;
30+
import org.openjdk.jmh.annotations.Scope;
31+
import org.openjdk.jmh.annotations.Setup;
32+
import org.openjdk.jmh.annotations.State;
33+
import org.openjdk.jmh.annotations.Warmup;
34+
import org.openjdk.jmh.infra.Blackhole;
2335

2436
import java.util.concurrent.TimeUnit;
2537

2638
@State(Scope.Benchmark)
27-
@OutputTimeUnit(TimeUnit.MILLISECONDS)
28-
@BenchmarkMode(Mode.Throughput)
39+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
40+
@BenchmarkMode(Mode.All)
2941
public class RingBitSetBenchmark {
3042

31-
private RingBitSet ringBitSet;
43+
private static final int CAPACITY = 1000;
3244
private static final int ITERATION_COUNT = 10;
3345
private static final int WARMUP_COUNT = 10;
34-
private static final int THREAD_COUNT = 10;
35-
private static final int FORK_COUNT = 1;
46+
private static final int THREAD_COUNT = 2;
47+
private static final int FORK_COUNT = 2;
48+
49+
private RingBitSet ringBitSet;
3650

3751
@Setup
3852
public void setUp() {
39-
ringBitSet = new RingBitSet(1000);
53+
ringBitSet = new RingBitSet(CAPACITY);
4054
}
4155

4256
@Benchmark
@@ -45,18 +59,21 @@ public void setUp() {
4559
@GroupThreads(THREAD_COUNT)
4660
@Warmup(iterations = WARMUP_COUNT)
4761
@Measurement(iterations = ITERATION_COUNT)
48-
public void setBits(){
49-
ringBitSet.setNextBit(true);
50-
ringBitSet.setNextBit(false);
62+
public void concurrentSetBits(Blackhole bh) {
63+
int firstCardinality = ringBitSet.setNextBit(true);
64+
bh.consume(firstCardinality);
65+
int secondCardinality = ringBitSet.setNextBit(false);
66+
bh.consume(secondCardinality);
5167
}
5268

5369
@Benchmark
5470
@Fork(value = FORK_COUNT)
5571
@Group("ringBitSet")
56-
@GroupThreads(THREAD_COUNT)
72+
@GroupThreads(1)
5773
@Warmup(iterations = WARMUP_COUNT)
5874
@Measurement(iterations = ITERATION_COUNT)
59-
public int cardinality(){
60-
return ringBitSet.cardinality();
75+
public void concurrentCardinality(Blackhole bh) {
76+
int cardinality = ringBitSet.cardinality();
77+
bh.consume(cardinality);
6178
}
6279
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
*
3+
* Copyright 2016 Robert Winkler and Bohdan Storozhuk
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.robwin.circuitbreaker.internal;
20+
21+
/**
22+
* {@link BitSetMod} is simplified version of {@link java.util.BitSet}.
23+
* It has no dynamic allocation, expanding logic, boundary checks
24+
* and it's set method returns previous bit state.
25+
*/
26+
class BitSetMod {
27+
28+
private final static int ADDRESS_BITS_PER_WORD = 6;
29+
private final int size;
30+
private final long[] words;
31+
32+
33+
BitSetMod(final int capacity) {
34+
int countOfWordsRequired = wordIndex(capacity - 1) + 1;
35+
size = countOfWordsRequired << ADDRESS_BITS_PER_WORD;
36+
words = new long[countOfWordsRequired];
37+
}
38+
39+
/**
40+
* Given a bit index, return word index containing it.
41+
*/
42+
private static int wordIndex(int bitIndex) {
43+
return bitIndex >> ADDRESS_BITS_PER_WORD;
44+
}
45+
46+
/**
47+
* Sets the bit at the specified index to value.
48+
*
49+
* @param bitIndex a bit index
50+
* @return previous state of bitIndex that can be {@code 1} or {@code 0}
51+
* @throws IndexOutOfBoundsException if the specified index is negative
52+
*/
53+
int set(int bitIndex, boolean value) {
54+
int wordIndex = wordIndex(bitIndex);
55+
long bitMask = 1L << bitIndex;
56+
int previous = (words[wordIndex] & bitMask) != 0 ? 1 : 0;
57+
if (value) {
58+
words[wordIndex] |= bitMask;
59+
} else {
60+
words[wordIndex] &= ~bitMask;
61+
}
62+
return previous;
63+
}
64+
65+
int size() {
66+
return size;
67+
}
68+
}

0 commit comments

Comments
 (0)