【问题标题】:What is the correct way to tell the compiler that I want a variable to be always stored in a register?告诉编译器我希望变量始终存储在寄存器中的正确方法是什么?
【发布时间】:2017-06-23 12:50:09
【问题描述】:

阅读this question 的答案后,我注意到register 不再是C++17 中的有效存储说明符。一些 cmets 甚至暗示编译器已经忽略 register 有一段时间了。

我使用带有 ARM Cortex-M MCU 的 GCC 6.x,并且有一些带有内联汇编的代码,这些代码绝对需要在寄存器中有一个变量。以前我认为register 关键字会为我做这个,但显然它没有。

  • 在现代 C++ 中,确保编译器始终为给定变量使用寄存器的正确方法是什么?
  • 如果没有标准方法,是否有特定于 GCC 的方法来执行此操作?也许某种属性?还是编译器特定的关键字?

编辑:为什么我需要在寄存器中存储一些东西?
我正在使用 ARM LDREX / STREX 指令实现无锁环形缓冲区。我需要将 ARM LDREX 指令的结果存储在寄存器中,因为将其存储在内存中会破坏 Cortex-M 上的整个机制。

编辑:示例代码。

这是从环形缓冲区中截取的代码sn-p,用于说明问题的重点。兴趣点是__LDREXW__STREXW__CLREX,它们都在cmsis_gcc.h 中定义。 They are intrinsic functions of the ARM synchronization primitives.我用它们来实现无锁机制。

template<typename T, uint32_t maxCount>
class RingBuffer final {

    __attribute__((aligned(8)))
    T buffer[maxCount];
    uint32_t start;
    uint32_t end;

    bool pushBack(const T &item) {
        register uint32_t exclusiveEnd;
        register uint32_t oldEnd;

        do {
            // Load current end value exclusively
            exclusiveEnd = __LDREXW(&end);
            __DMB();

            // Remember old end value so that
            // we can store the item at that location
            oldEnd = exclusiveEnd;

            // Check if ring buffer is full
            if (isFull()) {
                __CLREX();
                __DMB();
                return false;
            }

            // Figure out correct new value
            if (exclusiveEnd == (maxCount - 1)) {
                exclusiveEnd = 0;
            }
            else {
                exclusiveEnd ++;
            }

            // Attempt to store new end value
        } while (0 != __STREXW(exclusiveEnd, &end));
        __CLREX();
        __DMB();

        // Store new item
        //memcpy(buffer + oldEnd, &item, sizeof(T));
        buffer[oldEnd] = item;
        return true;
    }

    // ... other methods ...

}

为什么LDREX 结果必须存储在寄存器中:

在 Cortex-M4 实现的独占保留颗粒是整个内存地址范围(引用自 Cortex-M4 TRM),这意味着如果存储 LDREX 结果的变量最终在内存中而不是寄存器,那么下面的STREX 总是会失败。

注意:此代码在“裸机”硬件上运行,没有操作系统等。

【问题讨论】:

  • 在 C++ 中没有办法做到这一点,除非在内联汇编中编写所有内容。
  • @nwp - 内联汇编可能也不会这样做;编译器可以重新排列您的代码。真正知道的唯一方法是编写一个单独的 ASM 文件。
  • 为什么它绝对需要在寄存器中?表现?还是有其他原因?如果只是性能,那么你可以尝试用纯 C++ 编写,看看优化器是否正确。
  • 在单独的asm 语句中使用ldrex/strex 一直是对内联汇编的无效使用。
  • @stark 无法保证您的方法不会将寄存器溢出到堆栈中。

标签: c++ gcc arm inline-assembly cortex-m


【解决方案1】:

告诉编译器我希望变量始终存储在寄存器中的正确方法是什么?

您不能这样做(在可移植的标准 C++ 或 C 代码中)。您需要信任您的编译器,因此您甚至不应该这样做。

注意:

  • 最近的 C 和 C++ 标准(例如 C11 或 C++14 或 C++17)并没有以命令的方式提及 处理器寄存器,他们提到 @ 987654327@ 关键字(在上个世纪)只是对编译器的提示

  • 一些处理器(至少在过去)甚至没有任何真正的程序员可访问的处理器寄存器。

  • 最重要的是,您应该相信您的编译器足够好optimizations,在某些情况下,将值放入寄存器不是性能最佳(特别是因为该寄存器可以更好地用于其他值)。

但是,作为扩展GCC 编译器允许您放置variable in a specified register我不建议在没有非常好的理由的情况下使用该功能(至少确保在使用和不使用该功能的情况下对您的代码进行基准测试)。

您确实需要了解当前的编译器在大多数情况下优化得比您能做的更好。在尝试手动优化之前,请务必对您的代码进行基准测试(例如,使用 g++ -O3 和适当的 -mtune= 参数编译)。对于性能敏感的例程,还要检查生成的汇编代码(例如使用g++ -O3 -fverbose-asm -S)。

在 Cortex-M4 上,实现的独占保留颗粒是整个内存地址范围(引用自 Cortex-M4 TRM),

然后我建议使用 small extended assembler 代码(用于 GCC)或者,如果绝对必要,声明一个 variable in a specified register

也许您还需要使用-ffixed-reg 选项编译所有代码(包括任何使用的库,包括标准 C 和 C++ 库!)。

但我坚持:你需要比现在更信任你的编译器。您确定您找不到(并且可能从源代码配置和构建)最近的 GCC(例如 GCC 7),它可以作为内置或其他东西启用您的低级同步机制?

【讨论】:

    【解决方案2】:

    register 主要被 C++ 编译器视为提示,甚至在 1998 年批准的第一个标准之前。而且,在很多情况下,编译器在注册方面做得更好分配比程序员,所以它忽略了那个提示。

    在标准 C++ 中没有通用或可移植的方式(即与来自不同供应商和不同主机系统的编译器一起使用的方式)来确保将特定变量放在寄存器中。

    对于某些编译器,可以使用内联汇编器来显式使用寄存器。这种方法的问题是内联汇编程序在编译器和主机之间有所不同(本质上,它是实现定义的)。一些现代编译器也足够积极,它们也优化了内联汇编程序,因此即使在内联汇编程序中也可能删除寄存器的使用。分析和转换通常相对简单(至少与其他类型的优化相比),因此即使优化设置较低也可能发生。

    确定的唯一方法是检查输出汇编器以确定编译器针对您选择的设置(优化等)做了什么。

    可能唯一可靠的方法是在汇编程序中编写代码,而不是在 C++ 中作为内联汇编程序。 (我不知道有任何汇编程序可以在很大程度上优化代码,但我也没有理由尝试找到一个)。根据定义,这在系统之间是不可移植的——汇编程序通常依赖于机器。

    您选择的单词

    以前我认为register 关键字会为我执行此操作,但显然事实并非如此。

    还引发了另一个潜在问题 - 您正试图不必要地强制使用寄存器。

    如果您之前假设 register 关键字就足够了,并且您的程序似乎已经按要求运行,那么您很有可能无需担心寄存器中是否有任何变量。

    我的观点是,如果性能足够重要,足以证明将任何变量强制放入寄存器是合理的,那么您应该知道您的编译器将您选择的变量放入寄存器中,而不仅仅是假设所以。如果您只是假设这是真的,并且没有遇到困难,那么编译器很可能忽略了提示,并且无论如何都达到了您的代码所需的性能。

    我建议您需要质疑并重新验证您的信念,即您的特定变量实际上需要存储在寄存器中。

    如果没有关于您的设计要求的信息 - 以及通过大量测试证明编译的代码在实际场景中不满足这些要求的证据 - 您需要使用寄存器的假设通常是错误的。

    【讨论】:

    • 查看我的问题中关于为什么需要使用寄存器的编辑。
    • 是的,在这种情况下,我完全可以使用特定于编译器的东西。
    • 我其实很好奇,编译器是否优化了汇编,即使它声明了volatile?最后我检查了,asm volatile 是杀死优化器的 goto 方式
    猜你喜欢
    • 1970-01-01
    • 2013-11-11
    • 2010-11-25
    • 1970-01-01
    • 2020-09-04
    • 2019-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多