删除从循环调用的函数中的 switch-case

Removing a switch-case in a function called from a loop

我有一个名为 blend_pixels() 的函数,其任务是根据指定的混合模式将单个像素混合到另一个像素上。该函数又会被几乎所有想要绘制任何东西的函数调用。

问题是每个像素都会调用该函数,这意味着它每秒被调用数千万次,并且它包含一个 switch-case 语句,它会遍历所有可能的混合模式,直到找到正确的模式。

显然这比调用直接执行所需操作的函数要慢一些,这就是我要解决的问题。调用 blend_pixels() 的父函数通常只传递它们自己在调用后作为参数接收的混合模式,所以我不能只让它们调用一个只会执行一种混合模式的小函数。但是对于父函数的每次调用只需要进行一次选择(父函数每次调用对很多像素进行操作,而 blend_pixels() 为每个像素调用,在循环中遍历所有必要的像素).

函数如下所示:

void blend_pixels(lrgb_t *bg, lrgb_t fg, int32_t p, const int mode)
{
    int32_t r, g, b;

    switch (mode)
    {
        case SOLID:
            *bg = fg;
            break;

        case ADD:
            r = (fg.r * p >> 15) + bg->r;   if (r>ONE) bg->r = ONE; else bg->r = r;
            g = (fg.g * p >> 15) + bg->g;   if (g>ONE) bg->g = ONE; else bg->g = g;
            b = (fg.b * p >> 15) + bg->b;   if (b>ONE) bg->b = ONE; else bg->b = b;
            break;

        case SUB:
            r = -(fg.r * p >> 15) + bg->r;  if (r<0) bg->r = 0; else bg->r = r;
            g = -(fg.g * p >> 15) + bg->g;  if (g<0) bg->g = 0; else bg->g = g;
            b = -(fg.b * p >> 15) + bg->b;  if (b<0) bg->b = 0; else bg->b = b;
            break;

        case MUL:
            ... // you get the idea
    }
}

并以这种方式调用:

void parent_function(lrgb_t *fb, int w, int h, lrgb_t colour, ... int blendingmode)
{
    ...
    for (iy=y0; iy<y1; iy++)
        for (ix=x0; ix<x1; ix++)
        {
            p = some_weighting_formula();

            blend_pixels(&fb[iy*w+ix], colour, p, blendingmode);
        }
}

它本身可能被称为:

parent_function(fb, w, h, orange, ... /*whatever*/, ADD);

"ADD" 是枚举中的整数

很明显,任何选择混合算法的切换案例都应该在 parent_function 的循环之外完成。但是怎么办?

你可以用函数指针来做到这一点。

首先为您的函数指针定义一个类型定义:

typedef void (*blend_function)(lrgb_t *, lrgb_t, int32_t);

然后将 blend_pixels 的每个部分拆分成自己的函数,每个函数都具有相同的参数和 return typedef:

void blend_pixels_add(lrgb_t *bg, lrgb_t fg, int32_t p)
...
void blend_pixels_sub(lrgb_t *bg, lrgb_t fg, int32_t p)
...
void blend_pixels_mult(lrgb_t *bg, lrgb_t fg, int32_t p)
...

然后在你的父函数中,你可以赋一个函数指针类型的变量,赋给你要使用的函数的地址:

void parent_function(lrgb_t *fb, int w, int h, lrgb_t colour, ... int blendingmode)
{
    ...
    blend_function blend;
    switch (blendingmode)
    {
        case ADD:
            blend = blend_pixels_add;
            break;
        case SUB:
            blend = blend_pixels_sub;
            break;
        ...
    }
    for (iy=y0; iy<y1; iy++)
        for (ix=x0; ix<x1; ix++)
        {
            p = some_weighting_formula();

            blend(&fb[iy*w+ix], colour, p);
        }
}

解决您对 "and it contains a switch-case statement going through all possible blending modes until it finds the right one." 的担忧,这可能不是真正发生的事情。

Switch 语句通常被编译成所谓的跳转 table。在跳转 table 中,代码不会遍历所有情况以寻找正确的情况,而是将 switch() 语句的参数用作地址数组中的索引。类似于:

jump_table[SOLID] -> case SOLID address
jump_table[ADD] -> case ADD address
...

因此,在这种实现中,考虑很多很多值的 switch 语句应该与手动编码的函数指针解决方案一样快,因为这实际上是编译器构建的内容。