按位运算的性能下降
performance hit at bitwise operation
// nx_, ny_ is like 350 * 350
#define IJ_REF(_i, _j) ((_j)*nx_+(_i))
#define HAS_BIT(_v, _bit) (((_v) & (_bit)) == (_bit))
for (int ix = 0; ix < nx_; ++ix) { // 0.019s
for (int iy = 0; iy < ny_; ++iy) { // 0.716s
int32 r = IJ_REF(ix, iy); // 0.548s
if (!HAS_BIT(image_[r], FLAG)) { // 3.016s
int32 k = r * 4; // 0.242s
pTex[k] = pTex[k + 1] = pTex[k + 2] = pTex[k + 3] = 255; // 1.591s
}
}
}
HAS_BIT行的汇编是:
我猜 and
指令是一个 &
操作,所以它应该这么昂贵吗?
PS:FLAG 是 0x2 所以我猜想编译器做了一些优化来为 HAS_BIT
生成单个指令。我使用 Vtune 进行分析。
命中不是因为您使用的是按位指令,而是因为该指令从内存中读取 - 比使用寄存器的偏移计算更昂贵的操作。
代码的问题是它没有连续读取内存,因为根据IJ_REF
你的图像是按行存储的,但你是按列读取的。
如果交换循环顺序,您应该能够通过增加缓存命中次数来提高性能:
for (int iy = 0; iy < ny_; ++iy) {
for (int ix = 0; ix < nx_; ++ix) {
int32 r = IJ_REF(ix, iy);
if (!HAS_BIT(image_[r], FLAG)) {
int32 k = r * 4;
pTex[k] = pTex[k + 1] = pTex[k + 2] = pTex[k + 3] = 255;
}
}
}
你能描述这个变体(没有分支)和 post 结果吗?我很好奇...(可能会更慢,如果你很少将那些 255 写入 pTex,因为这段代码会用 or
触及每个 pTex 字节)。
#include <string>
#define IJ_REF(_i, _j) ((_j)*nx_+(_i))
#define HAS_BIT(_v, _bit) (((_v) & (_bit)) == (_bit))
int main()
{
constexpr uint32_t ny_ = 350, nx_ = 350;
constexpr uint8_t FLAG = 2;
uint8_t image_[ny_*nx_];
uint8_t pTex[ny_*nx_*4];
// let access pTex by uint32_t directly
uint32_t *pTex32bPtr = reinterpret_cast<uint32_t *>(pTex);
// debug input
image_[IJ_REF(nx_-2, ny_-1)] = FLAG;
image_[IJ_REF(nx_-1, ny_-1)] = ~FLAG;
pTex32bPtr[IJ_REF(nx_-2, ny_-1)] = 0x12345678;
pTex32bPtr[IJ_REF(nx_-1, ny_-1)] = 0x12345678;
// prepare for loop
const uint32_t endOfs = ny_*nx_;
constexpr uint32_t pTexORValue[2] = {0, 0xFFFFFFFF};
// loop trough all [x,y] values
for (uint32_t srcOfs = 0; srcOfs < endOfs; ++srcOfs) {
unsigned ORindex = !HAS_BIT(image_[srcOfs], FLAG);
// if you know FLAG is always 2, it can be:
// ORindex = image_[srcOfs]&2; with pTexORValue array:
// [3] = {0xFFFFFFFF, 0, 0};
pTex32bPtr[srcOfs] |= pTexORValue[ORindex];
}
// debug output
for (size_t i = IJ_REF(nx_-2, ny_-1) * 4; i < IJ_REF(nx_, ny_-1)*4; ++i) {
printf(" %02x", pTex[i]);
}
}
我也有点奇怪,为什么你的编译器会执行 movzx edx
+ and edx
,而它却可以执行 test byte ptr [eax+ecx],2
。 FLAG
的类型是什么?哦,现在我明白了,这是因为你的 HAS_BIT
宏。其实是"has_all_bits"测试
如果您打算只测试单个位,或者任何位都不错,您应该尝试(这应该允许 test
用法):
#define HAS_SOME_BIT(_v, _bits) (((_v) & (_bits)) != 0)
它甚至可以帮助我 post 上面编辑的代码得到更好的优化。
并且在将 FLAG 固定为 2 的程序集中,甚至可以将 OR 值计算为:
mov ebx,image_offset
loop:
movzx eax,Image_[ebx]
; copy bit 0x02 to all 32 bits
shl eax,30
sar eax,31
not eax ; flip it to "not HAS_BIT"
or pTex[ebx*4],eax
...
// nx_, ny_ is like 350 * 350
#define IJ_REF(_i, _j) ((_j)*nx_+(_i))
#define HAS_BIT(_v, _bit) (((_v) & (_bit)) == (_bit))
for (int ix = 0; ix < nx_; ++ix) { // 0.019s
for (int iy = 0; iy < ny_; ++iy) { // 0.716s
int32 r = IJ_REF(ix, iy); // 0.548s
if (!HAS_BIT(image_[r], FLAG)) { // 3.016s
int32 k = r * 4; // 0.242s
pTex[k] = pTex[k + 1] = pTex[k + 2] = pTex[k + 3] = 255; // 1.591s
}
}
}
HAS_BIT行的汇编是:
我猜 and
指令是一个 &
操作,所以它应该这么昂贵吗?
PS:FLAG 是 0x2 所以我猜想编译器做了一些优化来为 HAS_BIT
生成单个指令。我使用 Vtune 进行分析。
命中不是因为您使用的是按位指令,而是因为该指令从内存中读取 - 比使用寄存器的偏移计算更昂贵的操作。
代码的问题是它没有连续读取内存,因为根据IJ_REF
你的图像是按行存储的,但你是按列读取的。
如果交换循环顺序,您应该能够通过增加缓存命中次数来提高性能:
for (int iy = 0; iy < ny_; ++iy) {
for (int ix = 0; ix < nx_; ++ix) {
int32 r = IJ_REF(ix, iy);
if (!HAS_BIT(image_[r], FLAG)) {
int32 k = r * 4;
pTex[k] = pTex[k + 1] = pTex[k + 2] = pTex[k + 3] = 255;
}
}
}
你能描述这个变体(没有分支)和 post 结果吗?我很好奇...(可能会更慢,如果你很少将那些 255 写入 pTex,因为这段代码会用 or
触及每个 pTex 字节)。
#include <string>
#define IJ_REF(_i, _j) ((_j)*nx_+(_i))
#define HAS_BIT(_v, _bit) (((_v) & (_bit)) == (_bit))
int main()
{
constexpr uint32_t ny_ = 350, nx_ = 350;
constexpr uint8_t FLAG = 2;
uint8_t image_[ny_*nx_];
uint8_t pTex[ny_*nx_*4];
// let access pTex by uint32_t directly
uint32_t *pTex32bPtr = reinterpret_cast<uint32_t *>(pTex);
// debug input
image_[IJ_REF(nx_-2, ny_-1)] = FLAG;
image_[IJ_REF(nx_-1, ny_-1)] = ~FLAG;
pTex32bPtr[IJ_REF(nx_-2, ny_-1)] = 0x12345678;
pTex32bPtr[IJ_REF(nx_-1, ny_-1)] = 0x12345678;
// prepare for loop
const uint32_t endOfs = ny_*nx_;
constexpr uint32_t pTexORValue[2] = {0, 0xFFFFFFFF};
// loop trough all [x,y] values
for (uint32_t srcOfs = 0; srcOfs < endOfs; ++srcOfs) {
unsigned ORindex = !HAS_BIT(image_[srcOfs], FLAG);
// if you know FLAG is always 2, it can be:
// ORindex = image_[srcOfs]&2; with pTexORValue array:
// [3] = {0xFFFFFFFF, 0, 0};
pTex32bPtr[srcOfs] |= pTexORValue[ORindex];
}
// debug output
for (size_t i = IJ_REF(nx_-2, ny_-1) * 4; i < IJ_REF(nx_, ny_-1)*4; ++i) {
printf(" %02x", pTex[i]);
}
}
我也有点奇怪,为什么你的编译器会执行 movzx edx
+ and edx
,而它却可以执行 test byte ptr [eax+ecx],2
。 FLAG
的类型是什么?哦,现在我明白了,这是因为你的 HAS_BIT
宏。其实是"has_all_bits"测试
如果您打算只测试单个位,或者任何位都不错,您应该尝试(这应该允许 test
用法):
#define HAS_SOME_BIT(_v, _bits) (((_v) & (_bits)) != 0)
它甚至可以帮助我 post 上面编辑的代码得到更好的优化。
并且在将 FLAG 固定为 2 的程序集中,甚至可以将 OR 值计算为:
mov ebx,image_offset
loop:
movzx eax,Image_[ebx]
; copy bit 0x02 to all 32 bits
shl eax,30
sar eax,31
not eax ; flip it to "not HAS_BIT"
or pTex[ebx*4],eax
...