我正在尝试针对linux,GCC和64位使用进行更新,并保留在github Ken Silverman's Paint N Draw 3D C软件中。我得到了他的允许,但他忙于帮助。我不想做得很糟糕,我也不是一位精打细算的专家,所以我想在上传之前修复主要部分。
在他的代码pnd3d.c中,他使用了一个名为bitmal_t *
的结构,该结构包含一个malloc(我认为他的元素mal表示malloc的大小)和一个将体素距离表示为无符号int的大小(在2009年,这是一个32位int级联集合中的32位)。因此,基本上,距离是沿扩展位链的(1)上多少位的函数。对于碰撞,他会上下寻找零和一。
这是他的bitmal_t:
//buf: cast to: octv_t* or surf_t*
//bit: 1 bit per sizeof(buf[0]); 0=free, 1=occupied
typedef struct bit { void *buf; unsigned int mal, *bit, ind, num, siz; } bitmal_t;
这是他的测距代码,该代码在位范围内上下寻找1或0。我发布了他的原件,而不是我那笨拙的无效版本。
这里是重现它所需的所有代码段。
static __forceinline int dntil0 (unsigned int *lptr, int z, int zsiz)
{
// //This line does the same thing (but slow & brute force)
//while ((z < zsiz) && (lptr[z>>5]&(1<<KMOD32(z)))) z++; return(z);
int i;
//WARNING: zsiz must be multiple of 32!
i = (lptr[z>>5]|((1<<KMOD32(z))-1)); z &= ~31;
while (i == 0xffffffff)
{
z += 32; if (z >= zsiz) return(zsiz);
i = lptr[z>>5];
}
return(bsf(~i)+z);
}
static __forceinline int uptil0 (unsigned int *lptr, int z)
{
// //This line does the same thing (but slow & brute force)
//while ((z > 0) && (lptr[(z-1)>>5]&(1<<KMOD32(z-1)))) z--; return(z);
int i;
if (!z) return(0); //Prevent possible crash
i = (lptr[(z-1)>>5]|(-1<<KMOD32(z))); z &= ~31;
while (i == 0xffffffff)
{
z -= 32; if (z < 0) return(0);
i = lptr[z>>5];
}
return(bsr(~i)+z+1);
}
static __forceinline int dntil1 (unsigned int *lptr, int z, int zsiz)
{
// //This line does the same thing (but slow & brute force)
//while ((z < zsiz) && (!(lptr[z>>5]&(1<<KMOD32(z))))) z++; return(z);
int i;
//WARNING: zsiz must be multiple of 32!
i = (lptr[z>>5]&(-1<<KMOD32(z))); z &= ~31;
while (!i)
{
z += 32; if (z >= zsiz) return(zsiz);
i = lptr[z>>5];
}
return(bsf(i)+z);
}
static __forceinline int uptil1 (unsigned int *lptr, int z)
{
// //This line does the same thing (but slow & brute force)
//while ((z > 0) && (!(lptr[(z-1)>>5]&(1<<KMOD32(z-1))))) z--; return(z);
int i;
if (!z) return(0); //Prevent possible crash
i = (lptr[(z-1)>>5]&((1<<KMOD32(z))-1)); z &= ~31;
while (!i)
{
z -= 32; if (z < 0) return(0);
i = lptr[z>>5];
}
return(bsr(i)+z+1);
}
这是他设置为1和0的范围:
//Set all bits in vbit from (x,y,z0) to (x,y,z1-1) to 0's
#ifndef _WIN64
static __forceinline void setzrange0 (void *vptr, int z0, int z1)
{
int z, ze, *iptr = (int *)vptr;
if (!((z0^z1)&~31)) { iptr[z0>>5] &= ((~(-1<<z0))|(-1<<z1)); return; }
z = (z0>>5); ze = (z1>>5);
iptr[z] &=~(-1<<z0); for(z++;z<ze;z++) iptr[z] = 0;
iptr[z] &= (-1<<z1);
}
//Set all bits in vbit from (x,y,z0) to (x,y,z1-1) to 1's
static __forceinline void setzrange1 (void *vptr, int z0, int z1)
{
int z, ze, *iptr = (int *)vptr;
if (!((z0^z1)&~31)) { iptr[z0>>5] |= ((~(-1<<z1))&(-1<<z0)); return; }
z = (z0>>5); ze = (z1>>5);
iptr[z] |= (-1<<z0); for(z++;z<ze;z++) iptr[z] = -1;
iptr[z] |=~(-1<<z1);
}
#else
static __forceinline void setzrange0 (void *vptr, __int64 z0, __int64 z1)
{
unsigned __int64 z, ze, *iptr = (unsigned __int64 *)vptr;
if (!((z0^z1)&~63)) { iptr[z0>>6] &= ((~(LL(-1)<<z0))|(LL(-1)<<z1)); return; }
z = (z0>>6); ze = (z1>>6);
iptr[z] &=~(LL(-1)<<z0); for(z++;z<ze;z++) iptr[z] = LL(0);
iptr[z] &= (LL(-1)<<z1);
}
//Set all bits in vbit from (x,y,z0) to (x,y,z1-1) to 1's
static __forceinline void setzrange1 (void *vptr, __int64 z0, __int64 z1)
{
unsigned __int64 z, ze, *iptr = (unsigned __int64 *)vptr;
if (!((z0^z1)&~63)) { iptr[z0>>6] |= ((~(LL(-1)<<z1))&(LL(-1)<<z0)); return; }
z = (z0>>6); ze = (z1>>6);
iptr[z] |= (LL(-1)<<z0); for(z++;z<ze;z++) iptr[z] = LL(-1);
iptr[z] |=~(LL(-1)<<z1);
}
#endif
答案 0 :(得分:2)
写一些通过原始测试的单元测试!
首先,SSE2是x86-64的基线,因此,您一定要使用它,而不仅仅是64位整数。
GCC(与MSVC不同)不假定严格混叠,因此设置了位范围功能(根据WIN64是否将传入的指针强制转换为有符号的int*
(!!)或uint64_t*
指针)可能需要使用-fno-strict-aliasing进行编译,以使指针广播定义良好。
您可以将设置/清除位范围函数的循环部分替换为memset(gcc可以内联),或者如果您希望大小通常较小(例如小于200字节或因此,不值得调用libc memset的开销)
我认为第一块中的dntil0
函数只是前0或前1位(向前或向后)的位搜索循环。
使用SSE2内部函数从头开始重写它们:_mm_cmpeq_epi8
/ _mm_movemask_epi8
找到不是全0或全1位的第一个字节,然后使用{ {1}}或bsf
。
请参见glibc source code for SSE2 memchr或任何更简单的SSE2优化实现,以了解如何进行字节搜索。或使用glibc bsr
作为比较等于的示例,但这很简单:寻找非零memmem
(表示匹配),而不是寻找一个非零的_mm_movemask_epi8()
。结果为!= 0xffff
(全为),表明存在不匹配。在该位掩码上使用bsf
或bsr
查找SIMD向量中的字节索引。
因此,您总共在每个函数中使用BSR或BSF两次:一次在SIMD向量中查找字节索引,再一次在目标字节中查找位索引。
对于位扫描功能,请使用GCC __builtin_clz
或__builtin_ctz
查找第一个1
位。 Bit twiddling: which bit is set?
要搜索第一个零而不是第一个零,请按位求反,例如__builtin_ctz( ~p[idx] )
,其中p
是unsigned char*
到您的搜索缓冲区中(您正在使用{{1} } on),而_mm_loadu_si128()
是该16字节窗口内的偏移量。 (您对idx
结果得出的__builtin_ctz()
进行了计算,该结果超出了矢量循环。)
原件的工作方式:
movemask
以32位循环(z -= 32
的大小,因为它是在假定它将为x86 Windows或x86-64 Windows编译的情况下编写的。)
int
正在将位索引转换为lptr[z>>5];
索引。因此,它只是一次遍历缓冲区1 int
。
当它找到一个4字节的元素int
时,它发现了一个!= 0xFFFFFFFF
包含不为1的位;即它包含了我们正在寻找的东西。因此,它使用int
或bsf
进行位扫描并找到该bsr
内 中该位的位置。
它将其添加到int
(此z
开头的位位置)。
这与我上面描述的算法完全相同,但是一次实现一个整数,而不是一次实现16个字节。
它实际上应该使用int
或uint32_t
进行位操作,而不是用符号unsigned int
签名,但是显然可以在MSVC上正常工作。
int
这是大小检查,如果未找到任何位,则退出循环。