查找表vs if-else

Sim*_*ter 7 c++ optimization performance

今天我使用查找表而不是if-else读取代码来剪切两个求和的uint8值.地图是i in i={0...255},255 in i={256...511}.我想知道这可能有多大,并尝试使用gprof找到它,

g++ -std=c++0x -pg perfLookup.cpp -O2 -o perfLookup && ./perfLookup && gprof perfLookup |less
Run Code Online (Sandbox Code Playgroud)

随附下面的代码.现在没有-O2标志,gprof表示lookup()占45%,而ifelse()占执行时间的48%.使用-O2但查找()为56%,ifelse()为43%.但这个基准是否真的正确?也许很多代码都被优化了,因为dst永远不会被读取?

#include <iostream>
#include <cstdint>
#include <vector>

void lookup(std::vector<uint8_t> src, int repeat) {
  uint8_t lookup[511];
  for (int i = 0; i < 256; i++) {
    lookup[i] = i;
  }
  for (int i = 256; i < 512; i++) {
    lookup[i] = 255;
  }

  std::vector<uint8_t> dst(src.size());
  for (int i = 0; i < repeat; i++) {
    for (int i = 0; i < src.size(); i++) {
      dst[i] = lookup[src[i]];
    }
  }

}

void ifelse(std::vector<uint8_t> src, int repeat) {
  std::vector<uint8_t> dst(src.size());
  for (int i = 0; i < repeat; i++) {
    for (int i = 0; i < src.size(); i++) {
      dst[i] = (src[i] > 255) ? 255 : src[i];
    }
  }
}

int main()
{
  int n = 10000;
  std::vector<uint8_t> src(n);
  for (int i = 0; i < src.size(); i++) {
    src[i] = rand() % 510;
  }

  lookup(src, 10000);
  ifelse(src, 10000);
}
Run Code Online (Sandbox Code Playgroud)

更新的代码:

#include <iostream>
#include <cstdint>
#include <cstring>
#include <vector>
#include <algorithm>

// g++ -std=c++0x -pg perfLookup.cpp  -O2 -o perfLookup && ./perfLookup && gprof perfLookup |less

std::vector<uint16_t> lookup(std::vector<uint16_t> src, int repeat) {
  uint16_t lookup[511];
  for (int i = 0; i < 256; i++) {
    lookup[i] = i;
  }
  for (int i = 256; i < 511; i++) {
    lookup[i] = 255;
  }

  std::vector<uint16_t> dst(src.size());
  for (int i = 0; i < repeat; i++) {
    for (int k = 0; k < src.size(); k++) {
      dst[k] = lookup[src[k]]; 
    }
  }

  return dst;

}

std::vector<uint16_t> ifelse(std::vector<uint16_t> src, int repeat) {
  std::vector<uint16_t> dst(src.size());
  for (int i = 0; i < repeat; i++) {
    for (int k = 0; k < src.size(); k++) {
      dst[k] = (src[k] > 255) ? 255 : src[k];
    }
  }
  return dst;
}

std::vector<uint16_t> copyv(std::vector<uint16_t> src, int repeat) {
  std::vector<uint16_t> dst(src.size());
  for (int i = 0; i < repeat; i++) {
    dst = src;
    for (int k = 0; k < src.size(); k++) {
      if (dst[k] > 255) {
    dst[k] = 255; 
      }
    }
  }
  return dst;
}

std::vector<uint16_t> copyC(std::vector<uint16_t> src, int repeat)
{
  uint16_t* dst = (uint16_t *) malloc(sizeof(uint16_t) * src.size()); // Alloc array for dst

  for (int i = 0; i < repeat; i++) {
    std::memcpy(dst, &src[0], sizeof(uint16_t) * src.size()); // copy src into array

    for (int k = 0; k < src.size(); k++) {
      if ((dst[k] & 0xFF00) != 0)
    dst[k] = 0x00FF;
    }
  }

  free(dst); 
  return std::vector<uint16_t>(); 
}

int main()
{
  int n = 10000;
  std::vector<uint16_t> src(n);
  for (int i = 0; i < src.size(); i++) {
    src[i] = rand() % 510;
  }
  std::vector<uint16_t> dst;
  dst = lookup(src, 10000);
  dst = ifelse(src, 10000);
  dst = copyv(src,   10000);
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*ler 7

那么,既然src被声明为std::vector<uint8_t>,src[i]永远大于255,这是一个8位无符号整数,最高的可能值.

因此,我的猜测是编译器优化了检查.剩下的只是样板循环,因此基准没有意义.

如果检查没有意义(即检查64而不是255),"优化"的结果可能是高度机器依赖的.分支预测可以(取决于输入数据)在降低分支成本方面做得很好.另一方面,查找表需要(再次取决于输入数据)随机存储器访问并破坏缓存...


Kon*_*lph 7

除了亚历山大已经说过的事情:

查找表可以大大提高性能.但是,这首先会被创建查找表所花费的时间所抵消.通常你会单独对此进行基准测试

必须记住的另一件事是查找表需要缓存中的空间,因此如果它很大,可能会导致缓存未命中.如果有足够的缓存未命中,则该if方法将比查找表更快.

最后,gprof非常好地识别瓶颈.但我不会将它用于基准测试.请改用计时功能.gprof使用可能严格来说映射到消耗时间的采样,但这里不太精确.