【问题标题】:ARM GCC removing required code during optimizationARM GCC 在优化期间删除所需的代码
【发布时间】:2021-12-31 08:50:44
【问题描述】:

我有以下代码,它可以从 16bpp 图像到 1bpp 图像进行非常基本的转换,代码按预期运行,直到我启用编译器优化,此时我只得到一个黑色图像。

#define RSCALE 5014709
#define GSCALE 9848225
#define BSCALE 1912602

uint16_t _convertBufferTo1bit(uint8_t* buffer, uint16_t size)
{
    uint8_t* dst_ptr = buffer;
    uint8_t* end_ptr = buffer + size;
    uint16_t pos = 0;
    uint8_t r, g, b, i;
    uint32_t lum;

    while(buffer < end_ptr)
    {
        for(i = 8; i > 0; i--)
        {
            r = (*buffer & 0xF8);
            g = ((*buffer & 0x07) << 5);
            buffer += 1;
            g |= (*buffer & 0x03);
            b = ((*buffer & 0x1F) << 3);
            buffer += 1;

            lum = ((RSCALE * r) + (GSCALE * g) + (BSCALE * b));
            if(lum > 0x7FFFFFFF)
            {
                //White
                dst_ptr[pos] |= (1 << (i-1));
            }
            else
            {
                //black
                dst_ptr[pos] &= ~(1 << (i-1));
            }
        }
        pos++;
    }

    return pos;
}

查看反编译的程序集时,我可以看到if(lum &gt; 0x7FFFFFFF) 语句和所有相关计算已被编译器删除。有人可以帮我理解为什么吗?

-O0 -std=c++17 -Wall -Wextra

https://godbolt.org/z/GhPezzh33

-O1 -std=c++17 -Wall -Wextra

https://godbolt.org/z/bn1M4319h

【问题讨论】:

  • @RichardCritten 这是一个零终止循环。它应该可以正常工作
  • 对不起,你是绝对正确的。发现了别的东西 - 从 lum &gt; 0x7FFFFFFF 中删除最后一个 F 并且代码正确生成 - 仍在调查这个。
  • 编译器已确定您无法访问0x7ffffff,因为uint8_tint(您的常量)将具有共同的提升类型int,它不能表示大于该值的数字.将常量更改为 [value]u: godbolt
  • 系数总和不是0xffffff或0x1000000,而是0xfff970。是有意的吗?
  • 检查你的系数:g|=*buffer &gt;&gt; 3 ?

标签: c++ gcc arm stm32 compiler-optimization


【解决方案1】:

在这段代码中:

lum = ((RSCALE * r) + (GSCALE * g) + (BSCALE * b));
if(lum > 0x7FFFFFFF)

RSCALEGSCALEBSCALE 分别是 501470998482251912602。假设 int 在使用的​​ C 实现中是 32 位,这些都是 int 常量。

rgb 都是 uint8_t 类型,因此在乘法中它们被提升为 int

那么((RSCALE * r) + (GSCALE * g) + (BSCALE * b)) 是一个完全使用int 操作数产生int 结果的计算。因此编译器可以看到lum 被分配了int 结果的值,并且它有权假设结果在INT_MININT_MAX 的范围内。此外,它可以看到所有操作数都是非负数,因此不可能出现负数结果,从而将可能的范围缩小到0INT_MAX。这排除了将负值分配给 uint32_t 将导致换行到高值的可能性。所以编译器可能会假设lum &gt; 0x7FFFFFFF 永远不会是真的。

计算可能会溢出int,然后行为未定义,仍然允许编译器使用假设。

要更正此问题,请将每个乘法的至少一个操作数更改为 unsigned

【讨论】:

  • 完美,谢谢
  • @MikeNorgate 永远不要使用小于 32 位的任何东西作为局部变量,除非你有很好的理由,比如模数效应。除了潜在的错误外,它甚至会损害 ARM 的性能。您无需与 0x7fffff 进行比较,只需将 24 位打包即可。如果您有兴趣,这对 NEON 将是一个很好的实践。否则,绝对没有理由使用 32 位系数。 ARM 有 16 位乘法指令,运行速度是原来的两倍。
  • @Jake'Alquimista'LEE 感谢您的指点,这是我第一次使用 ARM,所以在性能方面还有一些学习要做
  • @MikeNorgate 这不仅适用于 ARM。您应该更多地使用局部变量。每次打包到目的地时,CPU 都会从内存中读取、打包并写回,在任何 CPU 上,因为您没有使用局部变量,而是访问内存。
  • 很好看。顺便说一句,这被-fsanitize=undefined-ftrapv 捕获。
猜你喜欢
  • 2019-05-18
  • 2011-12-16
  • 2014-06-08
  • 2017-07-19
  • 2011-04-06
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 2013-07-11
相关资源
最近更新 更多