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 final BigArrays bigArrays;
private LongArray bits; private LongArray bits;
/**
* Create the {@linkplain BitArray}.
* @param initialSize the initial size of underlying storage.
*/
public BitArray(int initialSize, BigArrays bigArrays) { public BitArray(int initialSize, BigArrays bigArrays) {
this.bigArrays = bigArrays; this.bigArrays = bigArrays;
this.bits = bigArrays.newLongArray(initialSize, true); this.bits = bigArrays.newLongArray(initialSize, true);
@ -41,21 +45,31 @@ public final class BitArray implements Releasable {
* Set the {@code index}th bit. * Set the {@code index}th bit.
*/ */
public void set(int index) { 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. * Clear the {@code index}th bit.
*/ */
public void clear(int index) { 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? * Is the {@code index}th bit set?
*/ */
public boolean get(int index) { public boolean get(int index) {
int wordNum = index >> 6; int wordNum = wordNum(index);
if (wordNum >= bits.size()) { if (wordNum >= bits.size()) {
/* /*
* If the word is bigger than the array then it could *never* have * 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; return (bits.get(wordNum) & bitmask) != 0;
} }
private void fill(int index, boolean bit) { private static int wordNum(int index) {
int wordNum = index >> 6; return index >> 6;
bits = bigArrays.grow(bits,wordNum+1); }
long bitmask = 1L << index;
long value = bit ? bits.get(wordNum) | bitmask : bits.get(wordNum) & ~bitmask; private static long bitmask(int index) {
bits.set(wordNum, value); return 1L << index;
} }
@Override @Override

View File

@ -19,12 +19,21 @@
package org.elasticsearch.common.util; 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 org.elasticsearch.test.ESTestCase;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BitArrayTests extends ESTestCase { public class BitArrayTests extends ESTestCase {
public void testRandom() { public void testRandom() {
try (BitArray bitArray = new BitArray(1, BigArrays.NON_RECYCLING_INSTANCE)) { 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);
}
}
} }