找到任意长度数组中第一个未设置位的位偏移的最快/最干净的方法是什么?
假设您的函数的原型类似于此size_t first_unset_bit(char unsigned const *buf, size_t bit_count, size_t start_bit);
,并且可以在同一缓冲区上快速连续多次调用它。如果你能提供更好的原型,请证明合理。
如果您使用汇编任何程序集,请提供将在core2或更高版本上运行的x86示例。我将为解决方案提供答案,提供速度和美感的最佳组合。
这是我天真的实施。我不知道它是否真的正确,它还没有在现场系统中使用。
static size_t first_unset_bit(char unsigned const *buf, size_t bit_count, size_t start_bit)
{
for (; start_bit < bit_count; ++start_bit)
{
size_t buf_index = start_bit / CHAR_BIT;
int bit_index = start_bit % CHAR_BIT;
if (!((buf[buf_index] >> bit_index) & 1))
return start_bit;
}
return -1;
}
答案 0 :(得分:3)
经常被忽视, strings.h (是的,那个标准的标题)包含一堆函数: ffs , ffsl 等等,有关详细信息,请参阅here。至少使用gcc和x86,这将编译为相应的一个周期指令,例如BSFL。
我建议:
例如:
cursor = start_pos;
while(position = ffsl(buf))
cursor++;
return (cursor - startpos) * 32 + pos;
(除非你必须测试你是否到达了哨兵,在这种情况下缓冲区是空白的。)
虽然你应该考虑到这一点,因为我并不是一个装配专家...你基本上每32位使用3个周期(一个增量,一个比较,一个BSFL)指示),并想象你可以使用该函数的长版本做得更好。
答案 1 :(得分:2)
REPE SCAS 0xFFFFFFFF
......很可能是答案的重要组成部分!
你别无选择,只能检查第一个未设置位之前的每一位,所以这只是你能以多快的速度做到这一点。一次比较32位是一个好的开始,一旦你知道哪个WORD包含第一个未设置位,你可以使用移位/查找表的组合来找到该单词中的第一个未设置位。
答案 2 :(得分:2)
优化提示:创建查找表,将字节值映射到第一个未设置位而不是循环字节,但不是位。
答案 3 :(得分:1)
Linux有我想象的是一个名为“find_first_zero_bit”的高度优化的实现。
答案 4 :(得分:1)
不使用任何汇编语言,但使用GCC内置函数,并假设bit_count
是long
中位数的倍数,这样的事情应该有效。我更改了您的函数以获取void*
缓冲区参数以避免别名问题。完全未经测试,我可能搞砸了数学,特别是在领先的“if(start_bit%LONG_BIT)块中。
#include <stddef.h>
#include <limits.h>
#define LONG_BIT (CHAR_BIT * sizeof(unsigned long))
size_t
first_unset_bit(const void *buf, size_t bit_count, size_t start_bit)
{
size_t long_count = bit_count / LONG_BIT;
size_t start_long = start_bit / LONG_BIT;
const unsigned long *lbuf = (const unsigned long *)buf;
if (start_bit % LONG_BIT)
{
size_t offset = start_bit % LONG_BIT;
unsigned long firstword = lbuf[start_long];
firstword = ~(firstword | ~((1UL << offset) - 1));
if (firstword)
return start_bit - offset + __builtin_clzl(firstword);
start_long += 1;
}
for (size_t i = start_long; i < long_count; i++)
{
unsigned long word = lbuf[i];
if (~word)
return i*LONG_BIT + __builtin_clzl(~word);
}
return bit_count + 1; // not found
}
答案 5 :(得分:0)
显而易见的解决方案是从start_bit循环直到你到达数组的末尾或找到未设置的位。
因为它可以是任意长度,你不能把它变成一个数字并找到那个值,因为它可能比双倍的大小更高。
答案 6 :(得分:0)
我假设您的缓冲区已对齐,例如malloc
返回的缓冲区。如果不是,您首先需要在开头扫描未对齐的部分。
uint32_t *p = (void *)buf;
while (!(*p+1)) p++;
size_t cnt = (unsigned char *)p - buf << CHAR_BIT;
if (*p>=0xFFFF0000)
if (*p>=0xFFFFFF00)
if (*p>=0xFFFFFFF0)
if (*p>=0xFFFFFFFC)
if (*p>=0xFFFFFFFE) cnt+=31;
else cnt+=30;
else
if (*p>=0xFFFFFFF9) cnt+=29;
else cnt+=28;
else
if (*p>=0xFFFFFFC0)
if (*p>=0xFFFFFFE0) cnt+=27;
else cnt+=26;
else
if (*p>=0xFFFFFF90) cnt+=25;
else cnt+=24;
else
...
我将把二进制搜索的其余部分留给你。
答案 7 :(得分:0)
正如其他人所提到的,汇编语言可能会提供最佳性能。如果这不是一个选项,您可能希望考虑以下(未经测试)例程。你要求的并不是那么多,但它应该足够接近你可以根据你的需要进行调整。
size_t findFirstNonFFbyte (
unsigned char const *buf, /* ptr to buffer in which to search */
size_t bufSize, /* length of the buffer */
size_t startHint /* hint for the starting byte (<= bufSize) */
) {
unsigned char * pBuf = buf + startHint;
size_t bytesLeft;
for (bytesLeft = bufSize - startHint;
bytesLeft > 0;
bytesLeft = startHint, pBuf = buf) {
while ((bytesLeft > 0) && (*pBuf == 0xff)) {
*pBuf++;
bytesLeft--;
}
if (bytesLeft > 0) {
return ((int) (pBuf - buf));
}
}
return (-1);
}
使用......
index = findFirstNonFFbyte (...);
bit_index = index + bitTable[buffer[index]];
附加说明:
以上代码将一次检查8位。如果你知道你的缓冲区将是4字节对齐的,并且它的长度是4字节的偶数倍,那么你可以通过一点调整一次测试32位(不要忘记返回值计算)。
如果您的起始位不是提示而是绝对,那么您可以跳过for循环。
您需要提供自己的位查找表。它应该是一个256字节长的数组。每个条目标识索引该条目的字节的第一个清除位。个人经验告诉我,不同的人会以不同的方式编号。有些将位0称为字节中最重要的位;其他人将位0称为字节的最低有效位。无论你挑选什么样的风格,一定要保持一致。
希望这有帮助。
答案 8 :(得分:0)
使用gcc内置的相当于Microsoft的_BitScanReverse,我使用这样的东西来找到我的内存系统的第一个空闲位(代表块使用):
__forceinline DWORD __fastcall GetNextFreeBlockIndex(PoolBlock* pPoolBlock)
{
DWORD dwIndex;
DWORD dwOffset = 0;
DWORD* pUsage = &pPoolBlock->fUsage[0];
while(dwOffset < MMANAGER_BLOCKS_PER_POOL)
{
DWORD dwUsage = *pUsage;
if(dwUsage != 0xFFFFFFFF && _BitScanForward(&dwIndex,~dwUsage))
{
#if !( MMANAGER_ATOMIC_OPS )
pPoolBlock->pSync.Enter();
#endif
ATOMIC_Write(DWORD,pPoolBlock->dwFreeIndex,dwOffset);
ATOMIC_Write(DWORD*,pPoolBlock->pFreeUsage,pUsage);
#if !( MMANAGER_ATOMIC_OPS )
pPoolBlock->pSync.Leave();
#endif
return dwIndex + dwOffset;
}
pUsage++;
dwOffset += 32;
}
return 0xFFFFFFFF;
}
__forceinline DWORD __fastcall GetFreeBlockIndex(PoolBlock* pPoolBlock)
{
DWORD dwIndex;
DWORD dwUsage = *pPoolBlock->pFreeUsage;
if(dwUsage == 0xFFFFFFFF)
return GetNextFreeBlockIndex(pPoolBlock);
if(_BitScanForward(&dwIndex,~dwUsage))
return dwIndex + pPoolBlock->dwFreeIndex;
return 0xFFFFFFFF;
}
原谅标签,这是一些#if /#endif VS代码。 ofc此代码仅适用于DWORDS
你可以block_size & 3
查找是否有奇数字节,将这些奇数字节复制到DWORD并扫描DWORD,然后剪切大于(block_size & 3) << 3
的任何结果