我怎样才能省去对幂等函数的额外调用?

van*_*joe 8 c c++ gcc

有没有办法告诉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
Run Code Online (Sandbox Code Playgroud)

我可以foo在做任何工作之前检查一个全局变量,但这不是最佳的.

void inline foo(int i){
   static int last_i=i+1;
   if(last_i != i){
        last_i==i;
        //do_work...
    }
}
Run Code Online (Sandbox Code Playgroud)

由于foo是内联函数,编译器应该能够查看调用,foo()并看到它不必执行它.问题是编译器不能像全局变量那样优化,有没有办法让编译器知道这样做是否安全?

Max*_*kin 7

...如果两个后续调用具有相同的参数,则只应调用一次具有副作用的函数...

虽然它有副作用,但该功能必须是幂等的.

C++标准仅区分具有副作用(I/O功能)和没有副作用的功能.从编译器的角度来看,如果函数是不透明的(在同一个翻译单元中没有定义)那么它必须有副作用,因此它是编译器内存屏障,编译器无法优化调用或推断返回值(除非它是一个编译器内部函数,如memcpy).

幂等,计算机科学意义:

在计算机科学中,术语幂等被更全面地用于描述如果执行一次或多次将产生相同结果的操作.[4] 这可能具有不同的含义,具体取决于应用它的上下文.例如,在具有副作用的方法或子程序调用的情况下,这意味着在第一次调用之后修改的状态保持不变.但是,在函数式编程中,幂等函数是对任何值x都具有属性f(f(x))= f(x)的函数.[5]

而C++没有这个概念.


Paw*_*arz 5

你可以使用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;
   }
}
Run Code Online (Sandbox Code Playgroud)


van*_*joe 0

因此,我犹豫是否要检查最后使用的参数的原因是函数调用位于一些非常紧密的内部循环内,因此额外的比较和分支指令会很烦人,尤其是在具有(或非常差)分支预测的平台上。

当我尝试时,我决定看看 gcc 做了什么。我使用了以下代码:

#include <stdio.h>
int check;

void myfun(int num){
        printf("changing to %d\n",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;
}
Run Code Online (Sandbox Code Playgroud)

这会gcc -O2 main.c在 x86(不是我的最终目标)上将 ( ) 编译为:

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
Run Code Online (Sandbox Code Playgroud)

所以正如你所看到的,myfun 只像我想要的那样被调用了两次。因此看起来 gcc 可以正确地做到这一点。如果有人想插话这里优化的任何限制,我会非常感兴趣