Save a little space on empty BitArrays (#53243) (#53316)

It doesn't make a whole lot of sense for `BitArray#clear` to grow the
underlying storage array just to clear the bit. We *already* treat
indices outside of the storage array as unset. This turns such
operations into a noop.
This commit is contained in:
Nik Everett 2020-03-10 09:22:19 -04:00 committed by GitHub
parent 856d9bfbc1
commit e23c3f915f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 9 deletions

View File

@ -32,6 +32,10 @@ public final class BitArray implements Releasable {
private final BigArrays bigArrays;
private LongArray bits;
/**
* Create the {@linkplain BitArray}.
* @param initialSize the initial size of underlying storage.
*/
public BitArray(int initialSize, BigArrays bigArrays) {
this.bigArrays = bigArrays;
this.bits = bigArrays.newLongArray(initialSize, true);
@ -41,21 +45,31 @@ public final class BitArray implements Releasable {
* Set the {@code index}th bit.
*/
public void set(int index) {
fill(index, true);
int wordNum = wordNum(index);
bits = bigArrays.grow(bits, wordNum + 1);
bits.set(wordNum, bits.get(wordNum) | bitmask(index));
}
/**
* Clear the {@code index}th bit.
*/
public void clear(int index) {
fill(index, false);
int wordNum = wordNum(index);
if (wordNum >= bits.size()) {
/*
* No need to resize the array just to clear the bit because we'll
* initialize them to false when we grow the array anyway.
*/
return;
}
bits.set(wordNum, bits.get(wordNum) & ~bitmask(index));
}
/**
* Is the {@code index}th bit set?
*/
public boolean get(int index) {
int wordNum = index >> 6;
int wordNum = wordNum(index);
if (wordNum >= bits.size()) {
/*
* If the word is bigger than the array then it could *never* have
@ -67,12 +81,12 @@ public final class BitArray implements Releasable {
return (bits.get(wordNum) & bitmask) != 0;
}
private void fill(int index, boolean bit) {
int wordNum = index >> 6;
bits = bigArrays.grow(bits,wordNum+1);
long bitmask = 1L << index;
long value = bit ? bits.get(wordNum) | bitmask : bits.get(wordNum) & ~bitmask;
bits.set(wordNum, value);
private static int wordNum(int index) {
return index >> 6;
}
private static long bitmask(int index) {
return 1L << index;
}
@Override

View File

@ -19,12 +19,21 @@
package org.elasticsearch.common.util;
import org.elasticsearch.common.breaker.CircuitBreaker;
import org.elasticsearch.common.breaker.CircuitBreakingException;
import org.elasticsearch.common.breaker.NoopCircuitBreaker;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.test.ESTestCase;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BitArrayTests extends ESTestCase {
public void testRandom() {
try (BitArray bitArray = new BitArray(1, BigArrays.NON_RECYCLING_INSTANCE)) {
@ -63,4 +72,31 @@ public class BitArrayTests extends ESTestCase {
}
}
}
public void testClearingDoesntAllocate() {
CircuitBreakerService breaker = mock(CircuitBreakerService.class);
ByteSizeValue max = new ByteSizeValue(1, ByteSizeUnit.KB);
when(breaker.getBreaker(CircuitBreaker.REQUEST)).thenReturn(new NoopCircuitBreaker(CircuitBreaker.REQUEST) {
private long total = 0;
@Override
public double addEstimateBytesAndMaybeBreak(long bytes, String label) throws CircuitBreakingException {
total += bytes;
if (total > max.getBytes()) {
throw new CircuitBreakingException("test error", bytes, max.getBytes(), Durability.TRANSIENT);
}
return total;
}
@Override
public long addWithoutBreaking(long bytes) {
total += bytes;
return total;
}
});
BigArrays bigArrays = new BigArrays(null, breaker, CircuitBreaker.REQUEST, true);
try (BitArray bitArray = new BitArray(1, bigArrays)) {
bitArray.clear(100000000);
}
}
}