在编译时生成函数

时间:2016-12-20 13:42:09

标签: c++11

我有一张图片。每个像素都包含有关RGB强度的信息。现在我想总结这些渠道的意图,但我也想选择哪些渠道强度来总和。 Straightforwad的实现看起来像这样:

int intensity(const unsiged char* pixel, bool red, bool green, bool blue){
    return 0 + (red ? pixel[0] : 0) + (green ? pixel[1] : 0) + (blue ? pixel[2] : 0);
}

因为我会为图像中的每个像素调用此函数,我想丢弃所有条件。如果可以的话。所以我想我必须为每个案例都有一个函数:

std::function<int(const unsigned char* pixel)> generateIntensityAccumulator(
    const bool& accumulateRChannel,
    const bool& accumulateGChannel,
    const bool& accumulateBChannel)
    {
    if (accumulateRChannel && accumulateGChannel && accumulateBChannel){
            return [](const unsigned char* pixel){
                return static_cast<int>(pixel[0]) + static_cast<int>(pixel[1]) + static_cast<int>(pixel[2]);
            };
        }

        if (!accumulateRChannel && accumulateGChannel && accumulateBChannel){
            return [](const unsigned char* pixel){
                return static_cast<int>(pixel[1]) + static_cast<int>(pixel[2]);
            };
        }

        if (!accumulateRChannel && !accumulateGChannel && accumulateBChannel){
            return [](const unsigned char* pixel){
                return static_cast<int>(pixel[2]);
            };
        }

        if (!accumulateRChannel && !accumulateGChannel && !accumulateBChannel){
            return [](const unsigned char* pixel){
                return 0;
            };
        }

        if (accumulateRChannel && !accumulateGChannel && !accumulateBChannel){
            return [](const unsigned char* pixel){
                return static_cast<int>(pixel[0]);
            };
        }

        if (!accumulateRChannel && accumulateGChannel && !accumulateBChannel){
            return [](const unsigned char* pixel){
                return static_cast<int>(pixel[1]);
            };
        }

        if (accumulateRChannel && !accumulateGChannel && accumulateBChannel){
            return [](const unsigned char* pixel){
                return static_cast<int>(pixel[0]) + static_cast<int>(pixel[2]);
            };
        }

        if (accumulateRChannel && accumulateGChannel && !accumulateBChannel){
            return [](const unsigned char* pixel){
                return static_cast<int>(pixel[0]) + static_cast<int>(pixel[1]);
            };
        }
    }

现在我可以在进入图像循环之前使用这个生成器并在没有任何条件的情况下使用函数:

...

auto accumulator = generateIntensityAccumulator(true, false, true);

for(auto pixel : pixels){
auto intensity = accumulator(pixel);
}

...

但对于这样简单的任务来说,这是一个很好的写作,我觉得有一种更好的方法可以实现这一点:例如,让编译器为我做一个肮脏的工作并生成所有上述情况。有人能指出我正确的方向吗?

2 个答案:

答案 0 :(得分:2)

使用这样的std::function将花费您的亲爱的,因为您不会让编译器通过内联它的优化来优化。

您要做的是模板的好工作。并且由于您使用整数,表达式本身可能会被优化掉,从而使您无需编写每个版本的特化。看看这个例子:

#include <array>
#include <chrono>
#include <iostream>
#include <random>
#include <vector>

template <bool AccumulateR, bool AccumulateG, bool AccumulateB>
inline int accumulate(const unsigned char *pixel) {
  static constexpr int enableR = static_cast<int>(AccumulateR);
  static constexpr int enableG = static_cast<int>(AccumulateG);
  static constexpr int enableB = static_cast<int>(AccumulateB);
  return enableR * static_cast<int>(pixel[0]) +
         enableG * static_cast<int>(pixel[1]) +
         enableB * static_cast<int>(pixel[2]);
}

int main(void) {
  std::vector<std::array<unsigned char, 3>> pixels(
      1e7, std::array<unsigned char, 3>{0, 0, 0});

  // Fill up with randomness
  std::random_device rd;
  std::uniform_int_distribution<unsigned char> dist(0, 255);
  for (auto &pixel : pixels) {
    pixel[0] = dist(rd);
    pixel[1] = dist(rd);
    pixel[2] = dist(rd);
  }

  // Measure perf
  using namespace std::chrono;

  auto t1 = high_resolution_clock::now();
  int sum1 = 0;
  for (auto const &pixel : pixels)
    sum1 += accumulate<true, true, true>(pixel.data());
  auto t2 = high_resolution_clock::now();
  int sum2 = 0;
  for (auto const &pixel : pixels)
    sum2 += accumulate<false, true, false>(pixel.data());
  auto t3 = high_resolution_clock::now();

  std::cout << "Sum 1 " << sum1 << " in "
            << duration_cast<milliseconds>(t2 - t1).count() << "ms\n";
  std::cout << "Sum 2 " << sum2 << " in "
            << duration_cast<milliseconds>(t3 - t2).count() << "ms\n";
}

使用 Clang 3.9 -O2编译,在我的CPU上产生此结果:

Sum 1 -470682949 in 7ms
Sum 2 1275037960 in 2ms

请注意我们这里有溢出的事实,您可能需要使用大于int的内容。 uint64_t可能会这样做。如果检查汇编代码,您将看到函数的两个版本内联并以不同方式进行优化。

答案 1 :(得分:1)

首先要做的事情。不要写一个std::function,只需要一个pixel;写一个连续范围pixel s(像素的扫描线)。

其次,您要撰写templateintensity

template<bool red, bool green, bool blue>
int intensity(const unsiged char* pixel){
  return (red ? pixel[0] : 0) + (green ? pixel[1] : 0) + (blue ? pixel[2] : 0);
}
很简单,嗯?这将优化到您手工制作的版本。

template<std::size_t index>
int intensity(const unsiged char* pixel){
  return intensity< index&1, index&2, index&4 >(pixel);
}

这个从index的位映射到要调用的intensity<bool, bool, bool>。现在是扫描线版本:

template<std::size_t index, std::size_t pixel_stride=3>
int sum_intensity(const unsiged char* pixel, std::size_t count){
  int value = 0;
  while(count--) {
    value += intensity<index>(pixel);
    pixel += pixel_stride;
  }
  return value;
}

我们现在可以生成扫描线强度计算器:

int(*)( const unsigned char* pel, std::size_t pixels )
scanline_intensity(bool red, bool green, bool blue) {
  static const auto table[] = {
    sum_intensity<0b000>, sum_intensity<0b001>,
              sum_intensity<0b010>, sum_intensity<0b011>,
    sum_intensity<0b100>, sum_intensity<0b101>,
              sum_intensity<0b110>, sum_intensity<0b111>,
  };
  std::size_t index = red + green*2 + blue*4;
  return sum_intensity[index];
}

并完成。

这些技术可以是通用的,但你不需要通用技术。

如果你的像素跨度不是3(比如有一个alpha通道),则需要传递sum_intensity(理想情况下作为模板参数)。

相关问题