删除从循环调用的函数中的 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 语句应该与手动编码的函数指针解决方案一样快,因为这实际上是编译器构建的内容。
我有一个名为 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 语句应该与手动编码的函数指针解决方案一样快,因为这实际上是编译器构建的内容。