【问题标题】:Can I tell my compiler to ignore the side effects of a statement or a function?我可以告诉我的编译器忽略语句或函数的副作用吗?
【发布时间】:2021-01-18 12:14:25
【问题描述】:

假设我的代码有以下功能:

inline int foo() {
    bar();
    int y = baz();
    return y;
}

并假设bar()baz() 有副作用。

如果我写:

int z = foo();
printf("z is %d\n", z);

那么显然bar()baz() 都必须被执行。但如果我写:

foo();

并且不使用返回值,调用baz() 的唯一原因是它的副作用。

有没有一种方法可以告诉我的编译器(对于任何一个:GCC、clang、MSVC、Intel),例如“您可能会忽略 baz() 的副作用以决定是否优化它”?或者“你可以忽略这条指令的副作用来决定是否优化它”?

这个问题适用于 C 和 C++,但可能与各种程序语言相关。让我们把它改成 C++,因为这是我现在用得最多的。

注意:

  • bar()baz() 不在我的控制范围内。
  • foo() 的签名不得更改。当然,如果我愿意,我可以使用一些代理并懒惰地申请baz();或者只是为不使用y 的情况编写一个不同的函数。这不是我要问的。

【问题讨论】:

  • 您可以将可选的输出参数传递给函数,并且仅在不是NULL 时才调用baz。它当然不如int z = foo(); 好,但可以做到这一点。
  • 大概foo 必须是一个内联函数,或者你有一些非常激进的 lto,否则单独的编译模型模型使得编译器不太可能假设调用者如何使用返回值。
  • @NateEldredge:链接到有关“纯”或“常量”作为属性的信息?另外,如果bar()baz() 不受我控制并且来自some_lib.h 怎么办?
  • @StoryTeller-UnslanderMonica:将foo() 内联,否则问题可能只是关于翻译单元内的优化。
  • @Bathsheba:这可能会改变副作用的时间,包括它们的相对时间。另外,问题底部的注释也是如此。

标签: c++ compiler-optimization side-effects compiler-options elision


【解决方案1】:

作为标准 C 和 C++ 的扩展,gcc 和 clang 支持 pure function attribute 通知编译器该函数没有副作用并且可以删除不需要的调用。如果你用这个属性声明一个函数,编译器会相信你,即使你在撒谎。

#include <iostream>

static int foo() __attribute__((pure));

static int foo() {
  std::cout << "I am a side effect!" << std::endl;
  return 17;
}

int main() {
  foo();
  return 0;
}

This prints no output. 请注意,clang 会发出警告,提示您忽略了 pure 函数的返回值。

(特别是,当你在 C 中做类似的事情时,gcc optimizes out the call with -O0,但 leaves it in with -O2!)

函数的定义不需要在 this 的作用域内,即使之前的声明已经在作用域内,你也可以用 this 属性重新声明一个函数。所以它也可以用于库函数。 Example.

当然,对编译器撒谎总是要您自担风险,并且可以想象这可能会产生我没有想到的潜在不良影响。如果您愿意仔细检查生成的代码以确保它确实在执行您想要的操作,这可能是一个有用的技巧,但我不会太相信它。

【讨论】:

  • Peculiarly, when you do 因为它是static,所以在考虑pure 之前它被内联在main 中(这就是我的理解)。如果您删除 static 或添加 noinline,那么它会为我删除。
  • 如果一个函数被声明为“纯”尽管有副作用,那么行为是否仅限于让编译器尽可能频繁或尽可能少地调用它认为合适的函数,或者限定符被视为邀请编译器生成无意义的代码,无论任何副作用是否重要?
  • @supercat:是的,好问题,我不知道。我不知道编译器会将pure 属性用于除省略不必要的调用之外的任何内容,但它可能会执行其他可能会破坏的操作。我肯定会将其归类为 hack,我认为这是不可避免的,因为您正试图让编译器生成与通常定义的行为不同的代码。
  • @NateEldredge:在许多情况下,知道编译器会忽略某些它应该没有理由关心的事情,这将使程序员能够比其他方式更清晰、更有效地编写代码。将提示属性或限定符视为邀请特定优化而不考虑它们是否会不利地影响程序行为将允许它们更频繁地应用,而不是将它们视为邀请完全任意的行为,因为它们的影响可能是可观察到。
【解决方案2】:

在相当多的情况下,函数的副作用只有在返回值最终被使用时才会相关,但据我所知,C 和 C++ 都没有任何方法来指示它们。

作为一个常见的例子,让例程执行一些计算并返回结果可能很有用,但如果(例如由于溢出)它们无法返回明显错误的结果,则会引发陷阱。根据函数的使用方式,让它陷入陷阱可能比返回明显错误的答案更可取,但可能不如让它以与以某种方式产生正确答案无法区分的方式表现更可取。如果一个程序执行了 12 次计算,但只使用了其中的 10 次,那么完全跳过未使用的计算可能比生成代码以查看其中是否会发生溢出更有效。

也许有帮助的是测试编译器是否可以保证特定左值的值永远不会显着影响程序行为的构造;总是允许实现说它不能,但是在编译器可以提供这种保证的情况下,它会允许适当编写的代码说,例如“如果这个函数要返回的 x 的值永远不会被使用,请不要费心计算它;否则,检查溢出并捕获是否会发生;如果返回值被使用并且存在不会溢出,计算函数的值。”

不过,我不知道有任何 C 实现提供这种功能。

【讨论】:

  • 我没有询问语言 - 我知道答案。问题只是关于编译器。
猜你喜欢
  • 2022-08-19
  • 2015-08-04
  • 2011-03-30
  • 1970-01-01
  • 1970-01-01
  • 2011-10-30
  • 2015-11-05
  • 2013-12-25
  • 1970-01-01
相关资源
最近更新 更多