分类: LINUX
2009-10-29 23:39:46
图1
图2
例1在某一内存块上进行XOR操作,其表示了一个基于整数的位集,你能在64位模式中对此进行优化。例2依赖于long
long的C语言类型,但不会被某些编译器所支持。正如你所看到的,此处没有改变位集的总体大小,即使只花了较少的两次操作来重组矢量。例2有效地减少了
循环的开销,相当于带系数2的循环展开,而只有一小点不利之处,就是它是纯64位的,如果在32位系统上编译,将会因为long大小的不同而给出错误的结
果。
例1:
{
int a1[2048];
int a2[2048];
int a3[2048];
for (int i = 0; i < 2048; ++i)
{
a3[i] = a1[i] ^ a2[i];
}
}
例2: bainei.com
{
long long a1[1024];
long long a2[1024];
long long a3[1024];
for (int i = 0; i < 1024; ++i)
{
a3[i] = a1[i] ^ a2[i];
}
}
你还能作进一步的修改,如例3所示,其利用了更宽的寄存器在32位和64位CPU上做同样的工作,在做如此的类型转换时,请注意指针对齐方式。 如果你只是盲目地把int指针转换为64位long指针,指针地址将不会是8字节对齐的,在某些架构的机器上,还可能导致程序崩溃,或者带来性能损失。例 3中的代码是不安全的,因为放置在堆栈中的32位int变量有可能4字节对齐,由此导致程序崩溃,如果在堆中分配(malloc),就能防止此类事情的发 生。
例3: bainei.com
{
int a1[2048];
int a2[2048];
int a3[2048];
long long* pa1 = (long long*) a1;
long long* pa2 = (long long*) a2;
long long* pa3 = (long long*) a3;
for (int i = 0; i < sizeof(a1) / sizeof(long long); ++i)
{
pa3[i] = pa1[i] ^ pa2[i];
}
}
位计数算法
在位集算法中最重要的一个操作是在位串中计算1位的数量,默认的操作方法是把每一个整数分成4个字符,并在一张预先计算好的位计数表中依次查 找。这种线性操作的方法能用一种16位宽的表格加以改进,所付出的代价是表格可能要更大才行。此外,更大的表格很可能会产生一些额外的内存取数操作,由此 影响CPU缓存命中率,结果并没有带来想象中的性能提升。
作为另一个可供选择的方法,例4就没有使用查找表,但却一次并行计算两个int。 bainei.com
例4:
int popcount(long long b)
{
b = (b & 0x5555555555555555LU) + (b >> 1 & 0x5555555555555555LU);
b = (b & 0x3333333333333333LU) + (b >> 2 & 0x3333333333333333LU);
b = b + (b >> 4) & 0x0F0F0F0F0F0F0F0FLU;
b = b + (b >> 8);
b = b + (b >> 16);
b = b + (b >> 32) & 0x0000007F;
return (int) b;
}
位串词典比较
可进行64位优化的另一种程序是位集中的词典比较,其最直接的实现是每次从位序列中取出两个字,并一位一位地转换进行比较,此迭代算法具有 O(N/2)的复杂度,此处的N是总的位数。例5中演示了两个字的迭代比较法;此算法并不能通过64位并行化处理极大地提高性能。然而,例6演示了另一种 可供选择的算法,其复杂性与机器字(不是位)数除以2成正比,其在64位上极具潜力。
例5:
int bitcmp(int w1, int w2)
{
while (w1 != w2)
{
int res = (w1 & 1) - (w2 & 1);
if (res != 0)
return res;
w1 >>= 1;
w2 >>= 1;
}
return 0;
} bainei.com
例6: bainei.com
int compare_bit_string(int a1[2048], int a2[2048])
{
long long* pa1 = (long long*) a1;
long long* pa2 = (long long*) a2;
for (int i = 0; i < sizeof(a1) / sizeof(long long); ++i)
{
long long w1, w2, diff;
w1 = a1[i];
w2 = a2[i];
diff = w1 ^ w2;
if (diff)
{
return (w1 & diff & -diff) ? 1 : -1;
}
}
return 0;
}
面临的问题 bainei.com
问题来了,为了64位,我们犯得着这样吗?当代的32位CPU都是超标量、推理性执行机器,具有在几个执行区域中并行乱序地同时执行几条指令的 能力,而不需要程序员的干预;反观64位处理器只展示了其具有同样的性能--但只是在纯64位中;话说回来,在如Intel Itanium(安腾处理器)特别强调并行编程的某些架构上,并且在编译器级别显式地进行了优化的情况下,程序代码适合于64位,并且已为64位优化就显 得尤为必要。
bainei.com
还有一点,程序的性能不只是受限于CPU的MHz表现,而且也受限于CPU的内存带宽--其又受限于系统总线,总之,上述的算法不可能总是表现 出最高性能,这也是不可改变的一个事实,并且硬件设计师也非常了解。当然,我们也看到了双通道内存控制器所带来的效率上的提高,并且内存速度也在稳步向前 发展,这在一定程度上减轻了系统总线成为瓶颈的可能性,面对当今发展越来越快的硬件,经过优化的64位算法将会有更好的表现。
算法优化及二进制位距
另一个可进行64位优化之处是在位串中计算(二进制)位距。二进制位距常用于进行分类与查找对象的相似之处的数据采集与AI(人工智能)程序,其通常表示为二进制描述符(位串)。此处优化的重点是位距算法,因为它会在系统中每对对象之间重复执行。
最知名的位距度量算法是加重平均位距,其是位中的一个最小数,并能被改变以转换为另一个位串。换句话来说,你可以使用XOR位运算符来结合位串,并利用得到的结果计算出位数。 bainei.com
如例7中采用了如上算法的程序,最明显的优化之处是去掉了临时位集,并同时计算XOR与总数量。而临时文件的产生是C++编译器的"内部运动",且因为内存的复制与重分配,降低了程序效率,请看例8:
例7:
#include <bitset>
using namespace std;
const unsigned BSIZE = 1000;
typedef bitset<BSIZE> bset;
unsigned int humming_distance(const bset& set1, const bset& set2)
{
bset xor_result = set1 ^ set2;
return xor_result.count();
} bainei.com
例8:
{
unsigned int hamming;
int a1[2048];
int a2[2048];
long long* pa1;
long long* pa2;
pa1 = (long long*) a1; pa2 = (long long*) a2;
hamming = 0;
for (int i = 0; i < sizeof(a1) / sizeof(long long); ++i)
{
long long b;
b = pa1[i] ^ pa2[i];
b = (b & 0x5555555555555555LU) + (b >> 1 & 0x5555555555555555LU);
b = (b & 0x3333333333333333LU) + (b >> 2 & 0x3333333333333333LU);
b = b + (b >> 4) & 0x0F0F0F0F0F0F0F0FLU;
b = b + (b >> 8);
b = b + (b >> 16);
b = b + (b >> 32) & 0x0000007F;
hamming += b;
}
}
图3 bainei.com
为了进一步加深理解,来看一下Tversky Index:
TI = BITCOUNT(A & B) / [a*(BITCOUNT(A-B) + b*BITCOUNT(B-A) + BITCOUNT(A & B)]
公式中包含了三个操作,BITCOUNT_AND(A, B)、BITCOUNT_SUB(A, B)、BITCOUNT_SUB(B, A)。三个操作能被结合成一条流水线操作,见图4。这种技术改进了数据存储位置,更好的复用了CPU缓存,也意味着减少CPU延迟及提高程序性能,见例 9:
例9:
{
double ti;
int a1[2048];
int a2[2048];
long long* pa1;
long long* pa2;
pa1 = (long long*) a1; pa2 = (long long*) a2;
ti = 0;
for (int i = 0; i < sizeof(a1) / sizeof(long long); ++i)
{
long long b1, b2, b3;
b1 = pa1[i] & pa2[i];
b2 = pa1[i] & ~pa2[i];
b3 = pa2[i] & ~pa1[i];
b1 = popcount(b1);
b2 = popcount(b2);
b3 = popcount(b3);
ti += double(b1) / double(0.4 * b2 + 0.5 * b3 + b1);
}
}
图4 bainei.com
void bit_xor(unsigned* dst, const unsigned* src, unsigned block_size)
{
const __m128i* wrd_ptr = (__m128i*)src;
const __m128i* wrd_end = (__m128i*)(src + block_size);
__m128i* dst_ptr = (__m128i*)dst;
do
{
__m128i xmm1 = _mm_load_si128(wrd_ptr);
__m128i xmm2 = _mm_load_si128(dst_ptr);
__m128i xmm1 = _mm_xor_si128(xmm1, xmm2);
__mm_store_si128(dst_ptr, xmm1);
++dst_ptr;
++wrd_ptr;
} while (wrd_ptr < wrd_end);
}
既然在64位上数值优化有这些好处,那你还等什么呢?