如何省略对幂等函数的附加调用?
How can I elide additional calls to an idempotent function?
是否有一种方法告诉gcc,如果有副作用的函数只应该在两个后续调用具有相同的参数时调用一次?我想要以下行为:
foo(6);//run this function
foo(6);//optimize this away
foo(6);//optimize this away
foo(5);//run this function
foo(6);//run this function again
我可以让foo
在做任何工作之前检查全局变量,但这不是最优的。
void inline foo(int i){
static int last_i=i+1;
if(last_i != i){
last_i==i;
//do_work...
}
}
由于foo
是一个内联函数,编译器应该能够查看foo()
的调用,并看到它不必执行它。问题是编译器不能优化全局变量,有没有办法让编译器知道它是安全的?
…有副作用的函数应该只在两个后续调用具有相同参数的情况下调用一次…
这个函数必须是幂等的尽管它有副作用。
c++标准只区分有副作用的函数(I/O函数)和没有副作用的函数。从编译器的角度来看,如果函数是不透明的(在相同的翻译单元中没有定义),那么它一定有副作用,因此它是一个编译器内存屏障,编译器不能优化调用或推断返回值(除非它是一个编译器的内在函数,如memcpy
)。
幂等性,计算机科学意义:
在计算机科学中,幂等一词被更广泛地用于描述一种操作,该操作如果执行一次或多次将产生相同的结果。[4]根据应用上下文的不同,这可能有不同的含义。例如,在具有副作用的方法或子例程调用的情况下,这意味着在第一次调用后修改的状态保持不变。在函数式编程中,幂等函数是指对任意值x具有f(f(x)) = f(x)的函数。[5]
而c++没有这个概念
您可以使用static
变量:
int foo(int param){
static int last=0;
static int result=1;
if(last==param) return result;
else{
last=param;
result=param/2+1;
return result;
}
}
为了让编译器优化掉有副作用的函数,它必须了解它产生的副作用。GCC没有注释来描述副作用的类型,所以这是不可能的。
如果函数在同一个编译单元中,编译器可能会发现调用是冗余的,但只有当函数足够简单,编译器才能完全理解时,这才有效,而这种情况很少发生。您最好将该逻辑放入调用方或被调用方。
为完整起见,如果函数没有有副作用,您可以使用__attribute__((pure))
和__attribute__((const))
告诉编译器这一点。
No。这种行为不是我所见过的许多语言的语义的一部分,当然也不是C/c++。Gcc不能提供选项来编译具有错误语义行为的代码!
然而,反过来也是可能的。如果函数foo()是"纯"的,即没有副作用,那么一个好的编译器将处理eg y=foo(x)+foo(x);只使用一个对foo()的调用。Ada语言为此目的提供了一个断言纯粹性的pragma。
可以使用functor:
class{
auto foo(int a);
int last_arg;
bool first_invoke=true;
public:
auto operator(int a){
if(first_invoke){
first_invoke=false;
last_arg=a;
return foo(a);
}
if(a==last_arg)
//do_something_special;
else{
last_arg=a;
return foo(a);
}
}
}foo;
这个解决方案是标准相关的,而不是编译器相关的。
或者您可以使用foo
的static
变量
所以我犹豫是否检查使用的最后一个参数的原因是,函数调用在一些非常紧密的内循环中,所以额外的比较和分支指令会很烦人,特别是在具有(或非常差的)分支预测的平台上。
当我尝试gcc时,我决定看看它能做什么。我使用了以下代码:
#include <stdio.h>
int check;
void myfun(int num){
printf("changing to %dn",num);
}
static inline __attribute__((always_inline)) void idem(int num){
if(num!=check){
myfun(num);
check=num;
}
}
int main(){
idem(5);
idem(5);
idem(4);
idem(4);
return 0;
}
在x86(不是我的最终目标)上编译(gcc -O2 main.c
)为:
0000000000400440 <main>:
400440: 48 83 ec 08 sub $0x8,%rsp
400444: 83 3d e5 0b 20 00 05 cmpl $0x5,0x200be5(%rip) # 601030 <check>
40044b: 74 14 je 400461 <main+0x21>
40044d: bf 05 00 00 00 mov $0x5,%edi
400452: e8 09 01 00 00 callq 400560 <myfun>
400457: c7 05 cf 0b 20 00 05 movl $0x5,0x200bcf(%rip) # 601030 <check>
40045e: 00 00 00
400461: bf 04 00 00 00 mov $0x4,%edi
400466: e8 f5 00 00 00 callq 400560 <myfun>
40046b: c7 05 bb 0b 20 00 04 movl $0x4,0x200bbb(%rip) # 601030 <check>
400472: 00 00 00
400475: 31 c0 xor %eax,%eax
400477: 5a pop %rdx
400478: c3 retq
400479: 90 nop
40047a: 90 nop
40047b: 90 nop
你可以看到myfun只被调用两次。因此,看起来gcc可以正确地执行此操作。如果有人想对这里的优化进行任何限制,我将非常感兴趣
- 如何用参数值调用函数(仅在运行时已知)
- 从python中调用C++函数并获取返回值
- 当使用通配符和null指针调用函数时,对输出的说明
- 从R调用C++函数并对其进行集成时出错
- 使用QTreeView,如何通过调用函数只突出显示特定的行/列
- 如何在qt中从另一个类调用函数
- 在 COUT 语句中使用 COUT 调用函数
- 如何从线程中的不同模块调用函数?
- C++从函数指针数组调用函数
- 当 A 在 for 循环中调用函数 B 时,如何计算函数 A 的空间复杂度?
- 如何在 C/C++ 中从外部库调用函数
- 如何使用运算符在同一行中多次调用函数
- 是否可以创建一个从不同类调用函数的线程?
- 无法为类成员调用函数
- 如何从另一个标头 c++ 调用函数
- C++有什么方法可以在既不调用函数模板也不提供其模板参数的情况下引用函数模板?
- 如何只允许在调用函数 B 后调用函数 A?
- 我可以这样调用函数吗?
- 如何在 c++ 的类中递归调用函数方法?
- 为什么在指向对象的迭代器上调用函数不允许我更改对象本身?