给出uint32数字0x12345678(例如RGBW颜色值),我如何有效地 并动态缩放其中的每个字节(给定缩放因子0 <= f <= 1
(或等效整数)除数)?
我知道我可以做得更长一些(可以通过一个结构将数字分解为各个部分,然后循环依次操作每个部分),但是有没有办法更快地完成而不循环? (静态值映射是另一种方法,但最好使用动态方法。)
编辑:C ++(C概念也很有趣),嵌入式,数百或数千个像素(而不是数百万个)。具体缩放RGBW led。
出现了另一件事-这是gcc,所以type punning is allowed(我已经将它用于类似的事情-我只是想看看是否有比这更好的方法)。
再次编辑:这是针对嵌入式平台(微控制器)的。虽然我一直在寻求可以帮助更广泛受众的答案,但我还是故意在语言和算法的背景下询问此问题,而不是针对特定平台和指令集进行优化,因为如果存在平台特定的优化可能会有所不同
答案 0 :(得分:28)
可以通过在多个“满”位上一次更有效地使用乘法来减少乘法次数,而不会在空度上浪费那么多的位。仍然需要一些填充位,以确保一个通道的乘积不会破坏另一通道的结果。使用8位定点标度,并且由于每个通道有8位,因此输出为每个通道16位,因此其中两个可以并排安装在uint32_t
中。这需要8位填充。因此,R和B(它们之间有8个零)可以一起乘以一个缩放比例,与G和W相同。结果是每个通道16位结果的高8位。像这样(未测试):
uint32_t RB = RGBW & 0x00FF00FF;
uint32_t GW = (RGBW >> 8) & 0x00FF00FF;
RB *= scale;
GW *= scale;
uint32_t out = ((RB >> 8) & 0x00FF00FF) | (GW & 0xFF00FF00);
scale
是一个从0..256开始的数字,它被解释为0..1,以1/256为步长。因此scale = 128
相当于将通道值减半,依此类推。
可以通过在相乘之后添加适当的偏差来添加舍入步骤。
在不使用x
结果的情况下,乘法执行此操作:
这里是quickbench,用于比较Timo的注释中的各种缩放方法。
答案 1 :(得分:11)
您可以直接使用移位和掩码来计算输入值的二乘幂:
if(NROW(Sick_WTG_USA) == 0){
stop('NO SICK WTGs')
} else {source("./Codes/2WKcheck.R")}
(这里pause
是unsigned long src_2 = ((src >> 1) & 0x7f7f7f7fUL) + (src & 0x01010101UL);
unsigned long src_4 = ((src >> 2) & 0x3f3f3f3fUL) + ((src >> 1) & 0x01010101UL);
unsigned long src_8 = ((src >> 3) & 0x1f1f1f1fUL) + ((src >> 2) & 0x01010101UL);
unsigned long src_16 = ((src >> 4) & 0x0f0f0f0fUL) + ((src >> 3) & 0x01010101UL);
unsigned long src_32 = ((src >> 5) & 0x07070707UL) + ((src >> 4) & 0x01010101UL);
unsigned long src_64 = ((src >> 6) & 0x03030303UL) + ((src >> 5) & 0x01010101UL);
unsigned long src_128 = ((src >> 7) & 0x01010101UL) + ((src >> 6) & 0x01010101UL);
unsigned long src_256 = ((src >> 7) & 0x01010101UL);
,每个字段分别除以2,src_2
是src
,每个字段分别除以4,依此类推)。
从0/256到255/256的任何其他分数都可以通过可选地将每个值相加来得出(例如0.75为src_4
)。如果您的嵌入式系统没有快速乘数(您可以在处理所有像素之前通过比例因子预先计算出必要的遮罩),或者如果您只需要有限的比例因子集(您可以将其硬编码,您需要将两个分数的幂组合到一组专门的缩放函数中。
例如,在其内部循环中使用专门的scale-by-0.75功能可以做到:
src
尽管不适用于您的用例,但该方法还可以用于预先计算对向量的每个分量也应用不同缩放因子的蒙版。
答案 2 :(得分:3)
在讨论中提到,最佳解决方案可以是 特定于体系结构。有人还建议在汇编中对其进行编码。 就便携性而言,组装要付出一定的代价,但同时也 您是否(以及可以提高多少)可以击败编译器的问题 优化器。
我在基于AVR的Arduino上进行了实验 微控制器。这是一个非常有限的8位哈佛RISC MCU,具有 一个8×8→16位硬件乘法器。
这是简单的实现,使用类型调整 乘以单个字节:
static inline uint32_t scale_pixel(uint32_t rgbw, uint16_t scale)
{
union {
uint32_t value;
uint8_t bytes[4];
} x = { .value = rgbw };
x.bytes[0] = x.bytes[0] * scale >> 8;
x.bytes[1] = x.bytes[1] * scale >> 8;
x.bytes[2] = x.bytes[2] * scale >> 8;
x.bytes[3] = x.bytes[3] * scale >> 8;
return x.value;
}
在-Os
上用gcc编译(通常在这些内存受限的设备中)
这需要28个CPU周期才能执行,即每个字节7个周期。
编译器足够聪明,可以将rgbw
和x
分配给同一CPU
注册并避免复制。
以下是基于harold的答案的版本:
static inline uint32_t scale_pixel(uint32_t rgbw, uint16_t scale)
{
uint32_t rb = rgbw & 0x00FF00FF;
uint32_t gw = (rgbw >> 8) & 0x00FF00FF;
rb *= scale;
gw *= scale;
uint32_t out = ((rb >> 8) & 0x00FF00FF) | (gw & 0xFF00FF00);
return out;
}
这是一个非常聪明的优化,很可能会在32位上获得回报 单片机但是,在这个小的8位处理器上,它花费了176个CPU周期 执行!生成的程序集具有对库函数的两次调用 实现了完整的32位乘法,以及许多移动和 清除寄存器。
最后,这是我的嵌入式程序集版本:
static inline uint32_t scale_pixel(uint32_t rgbw, uint16_t scale)
{
asm(
"tst %B[scale] \n\t" // test high byte of scale
"brne 0f \n\t" // if non zero, we are done
"mul %A[rgbw], %A[scale] \n\t" // multiply LSB
"mov %A[rgbw], r1 \n\t" // move result into place
"mul %B[rgbw], %A[scale] \n\t" // same with three other bytes
"mov %B[rgbw], r1 \n\t" // ...
"mul %C[rgbw], %A[scale] \n\t"
"mov %C[rgbw], r1 \n\t"
"mul %D[rgbw], %A[scale] \n\t"
"mov %D[rgbw], r1 \n"
"0:"
: [rgbw] "+r" (rgbw) // output
: [scale] "r" (scale) // input
: "r0", "r1" // clobbers
);
return rgbw;
}
这一事实使用了比例因子不能大于256的事实。 实际上,任何大于256的因子都被视为256,这可能是 被认为是功能。执行只需要14个周期 如果比例为256,则3个循环。
摘要:
我从此实验得出的结论是,您正在此处查看 真正意义上的微优化。你不能 在没有任何假设的情况下认真尝试在C级别进行优化 关于它将运行的架构。另外,如果速度是2 对您而言很重要,值得尝试在汇编中实现。采用 有条件的编译以在 目标架构,然后回退到通用C实现中 任何其他架构。