/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.common.buffer.impl;
import java.util.BitSet;
import java.util.concurrent.atomic.AtomicInteger;
import org.teiid.core.util.Assertion;
/**
* A segmented {@link BitSet} that supports greater concurrency
* and faster finding of clear bits.
*/
public class ConcurrentBitSet {
private static final int CONCURRENT_MODIFICATION = -2;
private static final int ADDRESS_BITS_PER_TOP_VALUE = 18;
private static final int MAX_TOP_VALUE = 1 << ADDRESS_BITS_PER_TOP_VALUE;
private static class Segment {
int offset;
int maxBits;
int startSearch;
int highestBitSet = -1;
int bitsSet;
int[] topVals;
final BitSet bitSet;
public Segment(int bitCount) {
bitSet = new BitSet();
maxBits = bitCount;
this.topVals = new int[Math.max(1, maxBits >> ADDRESS_BITS_PER_TOP_VALUE)];
}
}
private int bitsPerSegment;
private int totalBits;
private AtomicInteger counter = new AtomicInteger();
private AtomicInteger bitsSet = new AtomicInteger();
private Segment[] segments;
private boolean compact;
/**
* @param maxBits
* @param concurrencyLevel - should be a power of 2
*/
public ConcurrentBitSet(int maxBits, int concurrencyLevel) {
Assertion.assertTrue(maxBits > 0);
while ((bitsPerSegment = maxBits/concurrencyLevel) < concurrencyLevel) {
concurrencyLevel >>= 1;
}
segments = new Segment[concurrencyLevel];
int modBits = maxBits%concurrencyLevel;
if (modBits > 0) {
bitsPerSegment++;
}
for (int i = 0; i < concurrencyLevel; i++) {
segments[i] = new Segment(bitsPerSegment);
segments[i].offset = i*bitsPerSegment;
if (i == concurrencyLevel - 1) {
segments[i].maxBits -= (bitsPerSegment * concurrencyLevel)-maxBits;
}
}
this.totalBits = maxBits;
}
public void clear(int bitIndex) {
checkIndex(bitIndex);
Segment s = segments[bitIndex/bitsPerSegment];
int segmentBitIndex = bitIndex%bitsPerSegment;
synchronized (s) {
if (!s.bitSet.get(segmentBitIndex)) {
throw new AssertionError(bitIndex + " not set"); //$NON-NLS-1$
}
if (compact) {
s.startSearch = Math.min(s.startSearch, segmentBitIndex);
}
s.bitSet.clear(segmentBitIndex);
s.bitsSet--;
s.topVals[segmentBitIndex>>ADDRESS_BITS_PER_TOP_VALUE]--;
}
bitsSet.decrementAndGet();
}
/**
* Makes a best effort to atomically find the next clear bit and set it
* @return the next bit index or -1 if no clear bits are found
*/
public int getAndSetNextClearBit() {
return getAndSetNextClearBit(counter.getAndIncrement());
}
public int getNextSegment() {
return counter.getAndIncrement();
}
/**
* return an estimate of the number of bits set
* @param segment
* @return
*/
public int getBitsSet(int segment) {
Segment s = segments[segment&(segments.length-1)];
return s.bitsSet;
}
/**
* return an estimate of the highest bit (relative index) that has been set
* @param segment
* @return
*/
public int getHighestBitSet(int segment) {
Segment s = segments[segment&(segments.length-1)];
return s.highestBitSet;
}
/**
* @param segment
* @return the next clear bit index as an absolute index - not relative to a segment
*/
public int getAndSetNextClearBit(int segment) {
int nextBit = -1;
for (int i = 0; i < segments.length; i++) {
Segment s = segments[(segment+i)&(segments.length-1)];
synchronized (s) {
if (s.bitsSet == s.maxBits) {
continue;
}
int indexSearchStart = s.startSearch >> ADDRESS_BITS_PER_TOP_VALUE;
for (int j = indexSearchStart; j < s.topVals.length; j++) {
if (s.topVals[j] == MAX_TOP_VALUE) {
continue;
}
if (s.topVals[j] == 0) {
if (j == segment) {
nextBit = s.startSearch;
break;
}
nextBit = j * MAX_TOP_VALUE;
break;
}
int index = j * MAX_TOP_VALUE;
if (j == indexSearchStart) {
index = s.startSearch;
}
nextBit = s.bitSet.nextClearBit(index);
if (s.startSearch > 0 && nextBit >= s.maxBits - 1) {
s.startSearch = 0;
//fallback full scan
nextBit = s.bitSet.nextClearBit(s.startSearch);
}
break;
}
if (nextBit >= s.maxBits) {
throw new AssertionError("could not find clear bit"); //$NON-NLS-1$
}
s.topVals[nextBit>>ADDRESS_BITS_PER_TOP_VALUE]++;
s.bitsSet++;
s.bitSet.set(nextBit);
s.startSearch = nextBit + 1;
s.highestBitSet = Math.max(s.highestBitSet, nextBit);
if (s.startSearch == s.maxBits) {
s.startSearch = 0;
}
nextBit += s.offset;
break;
}
}
if (nextBit != -1) {
bitsSet.getAndIncrement();
}
return nextBit;
}
private void checkIndex(int bitIndex) {
if (bitIndex >= totalBits) {
throw new ArrayIndexOutOfBoundsException(bitIndex);
}
}
public int getTotalBits() {
return totalBits;
}
public int getBitsSet() {
return bitsSet.get();
}
public int getBitsPerSegment() {
return bitsPerSegment;
}
/**
* Set to always allocate against the first available block in a segment.
* @param compact
*/
public void setCompact(boolean compact) {
this.compact = compact;
}
public int compactHighestBitSet(int segment) {
Segment s = segments[segment&(segments.length-1)];
//first do an unlocked compact
for (int i = 0; i < 3; i++) {
int result = tryCompactHighestBitSet(s);
if (result != CONCURRENT_MODIFICATION) {
return result;
}
}
synchronized (s) {
return tryCompactHighestBitSet(s);
}
}
private int tryCompactHighestBitSet(Segment s) {
int highestBitSet = 0;
synchronized (s) {
highestBitSet = s.highestBitSet;
if (highestBitSet < 0) {
return -1;
}
if (s.bitSet.get(highestBitSet)) {
return highestBitSet;
}
}
int indexSearchStart = highestBitSet >> ADDRESS_BITS_PER_TOP_VALUE;
for (int j = indexSearchStart; j >= 0; j--) {
if (s.topVals[j] == 0) {
if (j==0) {
synchronized (s) {
if (s.highestBitSet != highestBitSet) {
return CONCURRENT_MODIFICATION;
}
s.highestBitSet = -1;
}
}
continue;
}
if (s.topVals[j] == MAX_TOP_VALUE) {
synchronized (s) {
if (s.highestBitSet != highestBitSet) {
return CONCURRENT_MODIFICATION;
}
s.highestBitSet = ((j + 1) * MAX_TOP_VALUE) -1;
}
break;
}
int index = j * MAX_TOP_VALUE;
int end = index + s.maxBits;
if (j == indexSearchStart) {
end = highestBitSet;
}
BitSet bs = s.bitSet;
int offset = 0;
if (j == indexSearchStart) {
bs = s.bitSet.get(index, end); //ensures that we look only at a subset of the words
offset = index;
}
index = index - offset;
end = end - offset - 1;
while (index < end) {
int next = bs.nextSetBit(index);
if (next == -1) {
index--;
break;
}
index = next + 1;
}
synchronized (s) {
if (s.highestBitSet != highestBitSet) {
return CONCURRENT_MODIFICATION;
}
s.highestBitSet = index + offset;
return s.highestBitSet;
}
}
return -1;
}
}