按位运算的性能下降

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],2FLAG 的类型是什么?哦,现在我明白了,这是因为你的 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
    ...