Диспетчеризация динамического метода в C со списком функций - PullRequest
2 голосов
/ 25 июня 2019

Я пытаюсь вычислить яркость пикселя RGB различными способами, в C99 с GCC 9. У меня есть это перечисление:

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

, который является пользовательским вводом из графического интерфейса.

Тогда у меня есть несколько функций, соответствующих каждому случаю:

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 ]

1 голос
/ 25 июня 2019

Суть вопроса, по-видимому, такова:

Моя идея состоит в том, чтобы использовать список или структуру методов, например:

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(), каждая из которых напрямую вызывает определенную функцию освещенности.По крайней мере, рассмотрите возможность проверки такой договоренности.Кроме того, поскольку вам потребуется множество функций, которые в основном идентичны, вы можете рассмотреть возможность использования генератора макросов или программного кода для их генерации вместо написания и поддержки всех этих вариантов вручную.

0 голосов
/ 25 июня 2019

Основываясь на ответе @Джона Боллинджера, я попробовал это:

#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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...