【问题标题】:How to speed up the below code to compute LBP on CPU significantly?如何加速下面的代码以显着计算 CPU 上的 LBP?
【发布时间】:2015-02-06 20:22:57
【问题描述】:

以下代码在对象检测程序中被密集调用,执行时间约为 80%。有什么办法可以显着加快速度吗?

#define CALC_SUM_(p0, p1, p2, p3, offset) ((p0)[offset] - (p1)[offset] - (p2)[offset] + (p3)[offset])
inline int calc_lbp2(float *p[], int offset)
{
    int cval = CALC_SUM_( p[5], p[6], p[9], p[10], offset );

    return (CALC_SUM_( p[0],  p[1],  p[4],  p[5],  offset ) >= cval ? 128 : 0) |   // 0
           (CALC_SUM_( p[1],  p[2],  p[5],  p[6],  offset ) >= cval ? 64 : 0) |    // 1
           (CALC_SUM_( p[2],  p[3],  p[6],  p[7],  offset ) >= cval ? 32 : 0) |    // 2
           (CALC_SUM_( p[6],  p[7],  p[10], p[11], offset ) >= cval ? 16 : 0) |  // 5
           (CALC_SUM_( p[10], p[11], p[14], p[15], offset ) >= cval ? 8 : 0)|  // 8
           (CALC_SUM_( p[9],  p[10], p[13], p[14], offset ) >= cval ? 4 : 0)|   // 7
           (CALC_SUM_( p[8],  p[9],  p[12], p[13], offset ) >= cval ? 2 : 0)|    // 6
           (CALC_SUM_( p[4],  p[5],  p[8],  p[9],  offset ) >= cval ? 1 : 0);
}

我尝试了SSE,但是程序花费了大约50ms左右(原始执行时间约为170ms):

inline int calc_lbp_sse(float *p[], int offset)
{
    static unsigned short bits[] = {0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001};
    short c = CALC_SUM_( p[5], p[6], p[9], p[10], offset );
    __m128i a = _mm_setr_epi16
                (
                    CALC_SUM_( p[0],  p[1],  p[4],  p[5],  offset ),
                    CALC_SUM_( p[1],  p[2],  p[5],  p[6],  offset ),
                    CALC_SUM_( p[2],  p[3],  p[6],  p[7],  offset ),
                    CALC_SUM_( p[6],  p[7],  p[10], p[11], offset ),
                    CALC_SUM_( p[10], p[11], p[14], p[15], offset ),
                    CALC_SUM_( p[9],  p[10], p[13], p[14], offset ),
                    CALC_SUM_( p[8],  p[9],  p[12], p[13], offset ),
                    CALC_SUM_( p[4],  p[5],  p[8],  p[9],  offset )
                );
    __m128i b = _mm_setr_epi16(c, c, c, c, c, c, c, c);

    __m128i res = _mm_cmplt_epi16(b,a);
    unsigned short* vals = (unsigned short*)&res;

    return ((vals[0]&bits[0]) | (vals[1]&bits[1]) | (vals[2]&bits[2]) | (vals[3]&bits[3]) |
            (vals[4]&bits[4]) | (vals[5]&bits[5]) |(vals[6]&bits[6]) |(vals[7]&bits[7]));
}

【问题讨论】:

  • 现在您正在使用 SSE 在单个位置加速 LBP 的评估。考虑重构代码 s.t.您正在连续评估 8 或 16 个连续像素的 LBP;这可能会更好。
  • 另外请注意,LBP 评估本质上具有丑陋的内存访问模式,并且您可能会遇到很多很多缓存未命中,尤其是在您的特征和图像很大的情况下。
  • 您是否尝试将所有 ... >= cval ? 64 : 0 样式表达式替换为 (... >= cval) << 6 之类的东西以摆脱分支?
  • set操作都是复杂的复合操作,除了_mm_setzero_si128。作为一般规则,唯一可以使用它们的地方是在循环之外。现在唯一“实际上是 SSE 指令”的是_mm_cmplt_epi16,这很好,但是您已经花费了更多的指令来构建它的操作数,而不是此时您可能保存的。顺便说一句,形成那个位掩码闻起来像 pmovmskb
  • 您可以在像素上添加带有主循环的代码吗?特别有趣的是每次迭代如何填充p

标签: c optimization x86 sse simd


【解决方案1】:

我在台式计算机上运行了您的函数 200000000 次,耗时 5.3 秒。然后我改变了这一行:

int cval = CALC_SUM_( p[5], p[6], p[9], p[10], offset );

到这里:

float cval = CALC_SUM_( p[5], p[6], p[9], p[10], offset );

我重新运行了相同的测试,现在只需要 3.0 秒。现在我对 LBP 不熟悉,但似乎您并不是故意将您的中心值转换为 int。从我读到的关于 LBP 的内容中,您只是将相邻值与中心值进行比较。但如果强制转换为 int 确实很重要,那么请忽略这个答案。

顺便说一句,我尝试了 japreiss 建议的方法,将 ? : 替换为 << 6,但无论哪种方式我都获得了完全相同的速度。所以显然编译器已经优化了它(我正在使用gcc -O3)。

【讨论】:

  • 我自己实际上已经实现了 LBP(并且知道 OP 的代码来源于 OpenCV 的深处),并且可以确认您的发现是合理的;如果需要进行任何类型的转换,性能就交给狗了。我从完全重写 LBP 评估中获得了更大的收益,但那是针对一个非常专业的处理器,这个余量太小,无法解释我是如何做到的。
猜你喜欢
  • 2019-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-14
  • 1970-01-01
  • 2021-02-16
相关资源
最近更新 更多