具有功能列表的C中的动态方法分派

时间:2019-06-24 21:07:41

标签: c

我尝试使用GCC 9在C99中使用各种方法来计算RGB像素的亮度。

typedef enum dt_iop_toneequalizer_method_t
{
  DT_TONEEQ_MEAN = 0,
  DT_TONEEQ_LIGHTNESS,
  DT_TONEEQ_VALUE
} dt_iop_toneequalizer_method_t;

是用户从GUI输入的内容。

然后,我有几种对应于每种情况的功能:

typedef float rgb_pixel[4] __attribute__((aligned(16)));

#pragma omp declare simd aligned(pixel:64)
static float _RGB_mean(const rgb_pixel pixel)
{
  return (pixel[0] + pixel[1] + pixel[2] + pixel[3]) / 3.0f;
}


#pragma omp declare simd aligned(pixel:16)
static float _RGB_value(const rgb_pixel pixel)
{
  return fmaxf(fmaxf(pixel[0], pixel[1]), pixel[2]);
}


#pragma omp declare simd aligned(pixel:16)
static float _RGB_lightness(const rgb_pixel pixel)
{
  const float max_rgb = _RGB_value(pixel);
  const float min_rgb = fminf(pixel[0], fminf(pixel[1], pixel[2]));
  return (max_rgb + min_rgb) / 2.0f;
}

然后,图像上的循环为:

static void exposure_mask(const float *const restrict in, 
                          float *const restrict out,
                          const size_t width, 
                          const size_t height, 
                          const dt_iop_toneequalizer_method_t method)
{
#pragma omp parallel for simd default(none) schedule(static) aligned(in, out:64)
  for(size_t k = 0; k < 4 * width * height; k += 4)
  {
    const rgb_pixel pixel = { in[k], in[k + 1], in[k + 2], 0.0f };
    out[k / 4] = RGB_light(pixel, method);
  }
}

我的第一种方法是将RGB_light()函数与method和函数映射的开关/大小写一起使用,但这会触发每个像素的检查,这非常昂贵。

我的想法是使用方法列表或struct,像这样:

typedef struct RGB_light
{
  // Pixel intensity (method == DT_TONEEQ_MEAN)
  float (*_RGB_mean)(rgb_pixel pixel);

  // Pixel HSL lightness (method == DT_TONEEQ_LIGHTNESS)
  float (*_RGB_lightness)(rgb_pixel pixel);

  // Pixel HSV value (method == DT_TONEEQ_VALUE)
  float (*_RGB_value)(rgb_pixel pixel);
} RGB_light;

然后在循环之前将所有方法初始化一次,例如

static void exposure_mask(const float *const restrict in, 
                          float *const restrict out,
                          const size_t width, 
                          const size_t height, 
                          const dt_iop_toneequalizer_method_t method)
{
  lightness_method = RGB_light[method]; // obviously wrong syntax

#pragma omp parallel for simd default(none) schedule(static) aligned(in, out:64)
  for(size_t k = 0; k < 4 * width * height; k += 4)
  {
    const rgb_pixel pixel = { in[k], in[k + 1], in[k + 2], 0.0f };
    out[k / 4] = lightness_method(pixel);
  }
}

但是我没有成功地将该想法转换为实际的工作代码。

有些事情与我想在Python中执行的操作类似:

def RGB_value(pixel):
  return whatever

def RGB_lightness(pixel):
  return whatever

methods = { 1: RGB_value, 2: RGB_lightness }

def loop(image, method):
  for pixel in image:
    lightness = methods[method](pixel)

2 个答案:

答案 0 :(得分:1)

问题的症结似乎是:

  

我的想法是使用方法的列表或结构,例如:

typedef struct RGB_light
{
  // Pixel intensity (method == DT_TONEEQ_MEAN)
  float (*_RGB_mean)(rgb_pixel pixel);

  // Pixel HSL lightness (method == DT_TONEEQ_LIGHTNESS)
  float (*_RGB_lightness)(rgb_pixel pixel);

  // Pixel HSV value (method == DT_TONEEQ_VALUE)
  float (*_RGB_value)(rgb_pixel pixel);
} RGB_light;
     

然后在循环之前将所有方法初始化一次,例如

static void exposure_mask(const float *const restrict in, 
                          float *const restrict out,
                          const size_t width, 
                          const size_t height, 
                          const dt_iop_toneequalizer_method_t method)
{
  lightness_method = RGB_light[method]; // obviously wrong syntax

,实际的问题是使用什么而不是“明显错误的语法”。但是您已经清楚地知道如何声明函数指针,并描述了基于method进行切换的其他代码。将这些内容组合在一起的自然方法是

    float (*lightness_method)(rgb_pixel pixel);

    switch (method) {
        case DT_TONEEQ_MEAN:
            lightness_method = _RGB_mean;
            break;
        case DT_TONEEQ_LIGHTNESS:
            lightness_method = _RGB_lightness;
            break;
        case DT_TONEEQ_VALUE:
            lightness_method = _RGB_value;
            break;
    }

...,您稍后将在某处跟着...

        float l = lightness_method(one_pixel);

如果在任何数组而不是结构中提供“方法”,类似的方法也适用。在这种情况下,您可以使用method变量而不是使用switch语句为数组建立索引。

但是请注意,由于您似乎专注于性能,因此您可能会发现沿着这些思路的任何方法都有些乏味。通过指针间接调用函数会拒绝编译器进行优化的机会,尽管在像素循环之外解决特定函数指针可能会有所改善,但与重复查找相比,间接调用本身更可能导致您对性能的不满意。

您应该考虑间接使用exposure_mask()的多个版本,每个版本都直接调用特定的亮度函数。至少考虑测试这样的安排。此外,由于您将需要一堆功能几乎相同的功能,因此可以考虑使用宏或编程代码生成器来生成所有功能,而不是手动编写和维护所有这些变体。

答案 1 :(得分:0)

根据@John Bollinger的回答,我尝试了此方法:

#define LOOP(fn)                                                        \
  {                                                                     \
    _Pragma ("omp parallel for simd default(none) schedule(static)      \
    firstprivate(width, height, ch, in, out)                            \
    aligned(in, out:64)" )                                              \
    for(size_t k = 0; k < ch * width * height; k += 4)                  \
    {                                                                   \
      const rgb_pixel pixel = { in[k], in[k + 1], in[k + 2], 0.0f };    \
      out[k / ch] = fn(pixel);                                          \
    }                                                                   \
    break;                                                              \
  }

static void exposure_mask(const float *const restrict in, float *const restrict out,
                                  const size_t width, const size_t height, const size_t ch,
                                  const dt_iop_toneequalizer_method_t method)
{
  switch(method)
  {
    case DT_TONEEQ_MEAN:
      LOOP(pixel_rgb_mean);

    case DT_TONEEQ_LIGHTNESS:
      LOOP(pixel_rgb_lightness);

    case DT_TONEEQ_VALUE:
      LOOP(pixel_rgb_value);
  }
}

但是,结果证明它和我之前(避免使用宏)进行逐像素检查一样快(或慢……),可能是因为我使用了unswitch-loops参数进行编译。