我在寻找最有效的方法来提取任意长度(0<=length<=16)的(无符号)位序列。skeleton类显示了我当前的实现如何处理这个问题:
public abstract class BitArray {
byte[] bytes = new byte[2048];
int bitGet;
public BitArray() {
}
public void readNextBlock(int initialBitGet, int count) {
// substitute for reading from an input stream
for (int i=(initialBitGet>>3); i<=count; ++i) {
bytes[i] = (byte) i;
}
prepareBitGet(initialBitGet, count);
}
public abstract void prepareBitGet(int initialBitGet, int count);
public abstract int getBits(int count);
static class Version0 extends BitArray {
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
}
public int getBits(int len) {
// intentionally gives meaningless result
bitGet += len;
return 0;
}
}
static class Version1 extends BitArray {
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet - 1;
}
public int getBits(int len) {
int byteIndex = bitGet;
bitGet = byteIndex + len;
int shift = 23 - (byteIndex & 7) - len;
int mask = (1 << len) - 1;
byteIndex >>= 3;
return (((bytes[byteIndex] << 16) |
((bytes[++byteIndex] & 0xFF) << 8) |
(bytes[++byteIndex] & 0xFF)) >> shift) & mask;
}
}
static class Version2 extends BitArray {
static final int[] mask = { 0x0, 0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF,
0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
}
public int getBits(int len) {
int offset = bitGet;
bitGet = offset + len;
int byteIndex = offset >> 3; // originally used /8
int bitIndex = offset & 7; // originally used %8
if ((bitIndex + len) > 16) {
return ((bytes[byteIndex] << 16 |
(bytes[byteIndex + 1] & 0xFF) << 8 |
(bytes[byteIndex + 2] & 0xFF)) >> (24 - bitIndex - len)) & mask[len];
} else if ((offset + len) > 8) {
return ((bytes[byteIndex] << 8 |
(bytes[byteIndex + 1] & 0xFF)) >> (16 - bitIndex - len)) & mask[len];
} else {
return (bytes[byteIndex] >> (8 - offset - len)) & mask[len];
}
}
}
static class Version3 extends BitArray {
int[] ints = new int[2048];
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
int put_i = (initialBitGet >> 3) - 1;
int get_i = put_i;
int buf;
buf = ((bytes[++get_i] & 0xFF) << 16) |
((bytes[++get_i] & 0xFF) << 8) |
(bytes[++get_i] & 0xFF);
do {
buf = (buf << 8) | (bytes[++get_i] & 0xFF);
ints[++put_i] = buf;
} while (get_i < count);
}
public int getBits(int len) {
int bit_idx = bitGet;
bitGet = bit_idx + len;
int shift = 32 - (bit_idx & 7) - len;
int mask = (1 << len) - 1;
int int_idx = bit_idx >> 3;
return (ints[int_idx] >> shift) & mask;
}
}
static class Version4 extends BitArray {
int[] ints = new int[1024];
public void prepareBitGet(int initialBitGet, int count) {
bitGet = initialBitGet;
int g = initialBitGet >> 3;
int p = (initialBitGet >> 4) - 1;
final byte[] b = bytes;
int t = (b[g] << 8) | (b[++g] & 0xFF);
final int[] i = ints;
do {
i[++p] = (t = (t << 16) | ((b[++g] & 0xFF) <<8) | (b[++g] & 0xFF));
} while (g < count);
}
public int getBits(final int len) {
final int i;
bitGet = (i = bitGet) + len;
return (ints[i >> 4] >> (32 - len - (i & 15))) & ((1 << len) - 1);
}
}
public void benchmark(String label) {
int checksum = 0;
readNextBlock(32, 1927);
long time = System.nanoTime();
for (int pass=1<<18; pass>0; --pass) {
prepareBitGet(32, 1927);
for (int i=2047; i>=0; --i) {
checksum += getBits(i & 15);
}
}
time = System.nanoTime() - time;
System.out.println(label+" took "+Math.round(time/1E6D)+" ms, checksum="+checksum);
try { // avoid having the console interfere with our next measurement
Thread.sleep(369);
} catch (InterruptedException e) {}
}
public static void main(String[] argv) {
BitArray test;
// for the sake of getting a little less influence from the OS for stable measurement
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
while (true) {
test = new Version0();
test.benchmark("no implementaion");
test = new Version1();
test.benchmark("Durandal's (original)");
test = new Version2();
test.benchmark("blitzpasta's (adapted)");
test = new Version3();
test.benchmark("MSN's (posted)");
test = new Version4();
test.benchmark("MSN's (half-buffer modification)");
System.out.println("--- next pass ---");
}
}
}
更高效的解决方案(性能方面)
我上面的第一个问题似乎还不够清楚。一个N位的“位序列”形成了一个N位的整数,我需要以最小的开销提取这些整数。我不使用字符串,因为这些值要么用作查找索引,要么直接输入到某些计算中。因此,基本上,上面显示的骨架是一个真正的类,getBits()签名显示了其余代码如何与它交互。
/八
>>三
%八
具有
&
对于如何工作和做什么似乎有点困惑。示例代码的第一个原始post包含一个read()方法,用于指示字节缓冲区的填充位置和时间。当代码被转换成microbench时,这就丢失了。我重新介绍它是为了让它更清楚一点。
其思想是通过添加另一个子类BitArray来击败所有现有版本,后者需要实现getBits()和prepareBitGet(),后者可能是空的。
不要为了给你的解决方案带来优势而改变基准,对所有现有的解决方案都可以这样做,这是一个完全没有意义的优化!(真的!!)
0不执行任何操作,但不执行任何操作。它总是返回0以粗略了解基准开销有多大。只是为了比较。
此外,还添加了对MSN想法的改编(版本3)。为了保证所有竞争对手的公平性和可比性,字节数组填充现在是基准测试的一部分,也是一个准备步骤(见上文)。最初MSN的解决方案做得不太好,在准备int[]缓冲区时有很多开销。我擅自对步骤进行了一点优化,使之成为一个激烈的竞争对手:)
结论
(上面的代码示例更新为包括基于所有适用贡献的版本)。在我的旧AMD盒(Sun JRE 1.6.0_21)上,它们显示为:
5384
女士
10283
V2闪电面食(改编)的
12212
女士
V3 MSN的(已发布)获取
11030
V4 MSN(半缓冲区修改)需要
9700
女士
注意:在这个基准测试中,每次调用getBits()时平均获取7.5位,每个位只读取一次。由于V3/V4必须支付很高的初始化成本,因此它们往往在获取更多、更短的情况下表现出更好的运行时行为(因此,越接近平均获取大小16的最大值,就越糟糕)。不过,V4仍然略领先于
情节。