给优化器更大的“许可证”

Posted

技术标签:

【中文标题】给优化器更大的“许可证”【英文标题】:Give an optimizer greater "License" 【发布时间】:2016-04-15 21:28:53 【问题描述】:

我知道volatile 变量背后的概念。基本上所有对该变量的读写必须发生。有没有办法让优化器只做所有的写入并假设读取将始终保持不变(除非通过写入修改)。

另外(沿着相同的思路)有一种方法可以为编译器定义一种新的内存类型来存储变量。例如,如果我有一个带 SD 卡的微控制器,我可以将 SD 卡定义为存储内存的地方(或者我是否明确必须自己完成所有读/写操作)。

作为记录,如果我可以在 gcc 上专门(且仅)做任何事情,我将使用 gcc 作为我的编译器

【问题讨论】:

要检测变量被写入修改,编译器必须生成代码......读取变量 @Lashane 但是如果它假设除非它写入它,否则什么都没有改变(换句话说,没有其他线程会改变它)它可以判断它是否被改变(理论上)......跨度> 答案是“不”。 @Lashane 但我想强迫它完成所有的写入......只是不关心每次都重读它 一个例子是缓存。变量的写入将进入 SDCard 以及缓存。读取将引用缓存。 【参考方案1】:

我知道 volatile 变量背后的概念。

好的...

基本上所有对该变量的读取和写入都必须发生。

绝对不是“基本上”。

有没有办法让优化器摆脱只做所有的写操作并假设读操作总是保持不变(除非被写修改)。

没有

volatile 用于对内存映射 I/O 的读/写访问进行建模。即使写入的值与之前写入的值相同,对此类 I/O 的“写入”通常也会触发电子设备中的活动。

volatile 没有其他用途 - 不,甚至在多线程中也没有(无论如何它都不会做你想做的事)。

来自§7.1.6.1

[ 注意:volatile 是实现的提示,以避免涉及对象的激进优化 因为对象的值可能会通过实现无法检测到的方式进行更改。此外, 对于某些实现,易失性可能表明需要特殊的硬件指令才能访问 物体。详细语义见 1.9。一般来说, volatile 的语义旨在 在 C++ 中与在 C 中相同。 — 尾注 ]

这里的含义是,除非您确切了解实现对 volatile 变量的作用,否则您无权使用它。

它的使用是不可移植的,所以如果使用的话,应该包含在你正在处理的任何概念的特定于实现的专业化中。

【讨论】:

Volatile 也可用于映射任务和处理器之间的共享内存。我的理解是,如果内存位置在程序控制之外被修改,内存是易失的。 @ThomasMatthews c++ 内存模型没有“任务”和“处理器”的概念。只有当内存访问受到诸如 std::atomic 或 std::mutex 之类的内存栅栏保护时,它才理解线程。 volatile 本身仅在实现定义的方式中有用。 @RichardHodges 是的,这就是volatile 的需要(对于当前任务或进程之外的任何可能修改变量的内容) @DarthRubik 是的,但它仅在实现定义的方式中具有任何意义。添加了标准的引用来说明。【参考方案2】:

简单地消除读取的波动性怎么样?

事实证明这根本不简单。

以下似乎只能在 clang 上可靠地工作。并且仅在进行易失性写入之前创建 hw_reg_read 别名时。

volatile int * hw_reg = (int*)0x0001003B;

int main()

    /*obtain nonvolatile alias*/
    int* hw_reg_read = const_cast<int*>(hw_reg);

    /*volatile write*/
    *hw_reg = 42;

    /*nonvolatile read*/
    int i = *hw_reg_read;

    return i;

我暂时把它留在这里当稻草人。

【讨论】:

在godbolt上检查过。不工作。编译器在实践中不会抛弃 volatile。 可以在编译器中实现这一点,但非常困难。我很难相信任何编译器编写者都曾为此烦恼过。 @RichardHodges 看起来你是对的。通过在进行易失性写入之前制作非易失性读指针,我确实让它在铿锵声中工作。但这对任何其他编译器都不起作用。我会相应地更新我的答案。【参考方案3】:

在较高级别上,您可以使用智能指针,如discussed here。

您正在谈论以某种方式赋予变量另一个属性(至少)。如果变量具有给定的属性,则在访问它时采取不同的操作(读取或写入)。

为此,您需要修改编译器。这是可能的。例如,8051 编译器具有允许对变量进行位访问和页面访问的属性。许多嵌入式系统的编译器都有将变量放置在特定内存块中的语法。

我建议先尝试智能指针包装器。

另一个想法是将变量放入一个类中并提供 getterssetters 来为您的环境建模(不允许直接访问)。

【讨论】:

唯一的问题是我希望它只写。我只希望编译器假设没有其他进程会写入它......但我确实希望每次我告诉它时都会写入 @DarthRubik 所以请保留一份非易失性副本。写两个并阅读副本。 @ThomasMatthews 我添加了一个帖子,其基本思想与该帖子相同,但方式不同【参考方案4】:

我想这可能就是我想要的:

template<class T>
class smartWriter
    volatile T* Volatile;  //Use this for writes
    T* NotVol;  //Use this for reads
    public:
        smartWriter(T* add) : Volatile(add),NotVol(add)
        
        operator T&() const
            return *NotVol;  //reads involve the non volatile one
        
        void operator=(T data)
            *Volatile = data;  //writes involve the volatile one
        

;

int main(int argc,char** args)
    smartWriter<int> smtVar(((int*)0xdeadbeaf)) ;  //Initialize var with address "0xdeadbeef"
    smtVar = 12;  //Optimizer can not get rid of this
    int other = smtVar;  //Optimizer might get rid of this?

    system("Pause");

感谢您用智能指针激发我的想象力......

【讨论】:

也许 C++ 有所不同,但从 C11 规范来看,“如果尝试通过使用具有非 volatile- 的左值来引用使用 volatile 限定类型定义的对象限定类型,行为未定义。” @user3386109 是的,但我相信它是隐式投射的? (也许) @user3386109:这个总体思路很可能适用于许多特定平台。如果您可以检查 asm 以确保它在您的实际目标平台上按预期工作,那么您并不总是需要编写严格可移植的代码。我怀疑这就像有符号整数溢出一样,编译器实际上试图基于它不可能发生的假设进行优化。 (不过,未定义的行为仍然很糟糕,比“实现定义的”要糟糕得多。)

以上是关于给优化器更大的“许可证”的主要内容,如果未能解决你的问题,请参考以下文章

MySQL为什么"错误"选择代价更大的索引

关于网站有价值的外链带来更大的影响

MySQL为什么"错误"选择代价更大的索引

1197多行事务要求更大的max_binlog_cache_size处理与优化

系统优化的思路

023-zabbix性能优化中的几个中肯建议