gpt4 book ai didi

c++ - 严格的别名和内存对齐

转载 作者:行者123 更新时间:2023-12-01 22:09:37 24 4
gpt4 key购买 nike

我有性能至关重要的代码,并且有一个巨大的函数在函数开始时在堆栈上分配40个大小不同的数组。这些阵列中的大多数必须具有一定的对齐方式(因为使用需要内存对齐的cpu指令(对于Intel和ARM CPU)可以在链中的其他位置访问这些阵列。

由于某些版本的gcc根本无法正确对齐堆栈变量(特别是对于arm代码),甚至有时它表示目标体系结构的最大对齐量小于我的代码实际请求的数量,因此我只能选择分配这些数组在堆栈上并手动对齐。

因此,对于每个数组,我需要执行类似的操作以使其正确对齐:

short history_[HIST_SIZE + 32];
short * history = (short*)((((uintptr_t)history_) + 31) & (~31));

这样, history现在在32字节边界上对齐。对所有40个数组执行相同的操作很繁琐,再加上这部分代码确实占用大量CPU,因此我无法为每个数组执行相同的对齐技术(此对齐困惑会混淆优化器,并且不同的寄存器分配会减慢函数的运行时间) ,有关详细说明,请参阅问题末尾的说明)。

所以...很显然,我只想进行一次手动对齐,并假设这些数组一个接一个地定位。我还向这些数组添加了额外的填充,以便它们始终是32字节的倍数。因此,然后我只需在堆栈上创建一个巨型char数组并将其转换为具有所有这些对齐数组的结构:
struct tmp
{
short history[HIST_SIZE];
short history2[2*HIST_SIZE];
...
int energy[320];
...
};


char buf[sizeof(tmp) + 32];
tmp * X = (tmp*)((((uintptr_t)buf) + 31) & (~31));

这样的事情。也许不是最优雅,但是它产生了非常好的结果,对生成的程序集进行手动检查证明生成的代码或多或少是适当的和可接受的。更新了构建系统以使用更新的GCC,突然之间我们开始在生成的数据中出现一些瑕疵(例如,即使在具有禁用的asm代码的纯C构建中,验证测试套件的输出也不再精确)。该问题花费了很长时间进行调试,并且似乎与别名规则和GCC的较新版本有关。

那么,我该如何完成呢?请不要浪费时间试图解释它不是标准的,不可移植的,未定义的(我已经读过很多关于它的文章)。另外,我也无法更改代码(我可能还会考虑修改GCC来解决问题,但不重构代码)...基本上,我想要做的只是应用一些黑魔法,以便使用更新的GCC在不禁用优化的情况下为这种类型的代码生成功能相同的代码?

编辑:
  • 我在多个OS/编译器上使用了此代码,但是当我切换到基于GCC 4.6的较新NDK时开始出现问题。我在GCC 4.7(从NDK r8d)中得到了同样糟糕的结果
  • 我提到32字节对齐。如果它伤害您的眼睛,请用您喜欢的任何其他数字代替它,例如666(如果有帮助)。甚至没有提到大多数架构不需要这种对齐的意义。如果我在堆栈上对齐8KB本地数组,则我将15字节放宽为16字节对齐,将31放宽为32字节对齐。我希望我的意思很清楚。
  • 我说在性能关键代码中堆栈上有40个数组。我可能还需要说,这是第三方的旧代码,一直运行良好,我不想弄乱它。无需说是好是坏,对此没有意义。
  • 此代码/功能已经过良好测试和定义的行为。我们有该代码要求的确切编号,例如它分配Xkb或RAM,使用Y kb的静态表,最多消耗Z kb的堆栈空间,并且它不能更改,因为代码不会更改。
  • 通过说“对齐困惑使优化器困惑”,我的意思是,如果我尝试分别对齐每个数组,则代码优化器会为对齐代码分配额外的寄存器,而代码的性能关键部分突然没有足够的寄存器并开始进行垃圾回收而是导致代码变慢。在ARM CPU上观察到了此行为(我完全不担心intel)。
  • 通过工件,我的意思是输出变得不精确,添加了一些噪声。由于此类型别名问题或编译器中存在一些错误,最终导致函数输出错误。

    简而言之,问题的关键...我如何分配随机数量的堆栈空间(使用char数组或alloca,然后将指针对齐到该堆栈空间,然后将此内存块重新解释为某种具有定义良好布局的结构只要结构本身正确对齐,就可以保证某些变量的对齐。我正在尝试使用各种方法来转换内存,将大堆栈分配移到一个单独的函数上,但仍然会得到错误的输出和堆栈损坏,我真的开始越来越多地认为这个巨大的功能会在gcc中碰到某种错误,这很奇怪,通过执行这种强制转换,无论我如何尝试,都无法完成该工作。禁用了所有需要对齐的优化,它现在是纯C风格的代码,但仍然得到不好的结果(非位精确输出和偶然的堆栈损坏崩溃)。修正了所有问题的简单修复程序,我写的不是:
    char buf[sizeof(tmp) + 32];
    tmp * X = (tmp*)((((uintptr_t)buf) + 31) & (~31));

    此代码:
    tmp buf;
    tmp * X = &buf;

    然后所有错误都消失了!唯一的问题是此代码无法对数组进行正确的对齐,并且在启用优化后会崩溃。

    有趣的观察:
    我提到这种方法效果很好,并产生了预期的输出:
    tmp buf;
    tmp * X = &buf;

    在其他文件中,我添加了一个独立的noinline函数,该函数仅将void指针强制转换为该结构tmp *:
    struct tmp * to_struct_tmp(void * buffer32)
    {
    return (struct tmp *)buffer32;
    }

    最初,我以为如果我使用to_struct_tmp转换分配的内存,它将欺骗gcc产生我期望得到的结果,但是,它仍然会产生无效的输出。如果我尝试通过这种方式修改工作代码:
    tmp buf;
    tmp * X = to_struct_tmp(&buf);

    那么我会得到同样糟糕的结果!哇,我还能说什么?也许,基于严格混叠规则,gcc假定tmp * Xtmp buf不相关,并且在从to_struct_tmp返回后立即将tmp buf删除为未使用的变量?还是做一些奇怪的事情而产生意想不到的结果。我还尝试检查生成的程序集,但是,将tmp * X = &buf;更改为tmp * X = to_struct_tmp(&buf);会为该函数生成完全不同的代码,因此,混叠规则一定程度上会极大地影响代码生成。

    结论:
    经过各种测试,我有一个主意,无论我如何尝试,为何都无法使它正常工作。基于严格的类型别名,GCC认为静态数组未使用,因此不为其分配堆栈。然后,还将使用堆栈的局部变量写入存储tmp结构的同一位置;换句话说,我的巨型结构与该函数的其他变量共享相同的堆栈内存。只有这可以解释为什么它总是导致相同的不良结果。 -fno-strict-aliasing可以解决此问题,如本例所述。
  • 最佳答案

    只需禁用基于别名的优化并称之为一天

    如果您的问题实际上是由与严格别名相关的优化引起的,那么-fno-strict-aliasing将解决问题。此外,在这种情况下,您不必担心会丢失优化,因为根据定义,这些优化对您的代码而言是不安全的,因此您无法使用它们。

    Praetorian的要点。我记得在gcc中引入别名分析后,一个开发人员的歇斯底里。某位Linux内核作者希望(A)为事物添加别名,并且(B)仍获得该优化。 (这是一个过分的简化,但是-fno-strict-aliasing似乎可以解决问题,花费不多,而且所有人都必须有其他鱼来油炸。)

    关于c++ - 严格的别名和内存对齐,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14170010/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com