在 Rcpp (C++) 中的同一代码中多次声明一个变量

declaring a variable many times in the same code in Rcpp (C++)

对于开始使用 Rcpp 的 R 用户来说,声明变量是一件新鲜事。我的问题是多次声明同一个命名变量时实际发生了什么。在许多示例中,我看到每次都声明了 for 循环的索引。

cppFunction('
int add1( const int n ){
     int y = 0;
     for(int i=0; i<n; i++){
       for(int j=0; j<n; j++) y++;
       for(int j=0; j<(n*2); j++) y++;
     }
     return y ;
}
')

而不是

cppFunction('
int add2( const int n ){
     int y = 0;
     int i, j;
     for(i=0; i<n; i++){
       for(j=0; j<n; j++) y++;
       for(j=0; j<(n*2); j++) y++;
     }
     return y ;
}
')

两者似乎都给出了相同的答案。但是在同一个程序中多次声明一个变量(同名)通常可以吗?如果不行,什么时候不行?或者我不明白 'declare' 是什么意思,例如,上面的两个函数是相同的(例如,即使在第一个函数中也没有多次声明)。

是封装的缘故。 你可以尝试的是

for(int i = 0; i<5;i++) {std::cout<<i<<std::endl; }
std::cout<<i<<std::endl;

此代码不起作用,因为 "i" 仅在 for 循环内声明。 同样,如果你有一个 "if-statement",在里面声明的每个变量都被封装并且不再存在于 if 语句之外。 clamps { } 也封装了变量。 你不能做的是

int i = 5;
int i = 4;

现在您尝试再次声明同一个变量,这会报错。

在您给出的 2 个示例中,您选择哪个不会有太大区别 - 编译器几乎肯定会以相同的方式优化它们。

两者都是完全合法的。您引用的第二种情况很好,因为每个变量都包含在 for 循环的范围内。

就个人而言,除非循环的索引与其他一些预先存在的变量相关,否则我将始终像您的第二个示例一样编写循环。我认为这是一个更简洁的解决方案,符合在需要的地方声明变量的想法。

C/C++ 将允许您做一些不完全直观的事情 - 它允许您在嵌套范围内重新定义相同的变量名,然后事情就会开始变得混乱:

for (int i = 0; i < 10; i++) {
    for (int i = 10; i < 100; i++) {
        // Be careful what you do here!  
    }
}

在内循环中,任何对 'i' 的引用都将引用在内循环中声明的 'i' - 外循环 'i' 现在不可访问。我已经看到很多基于此的错误,它们很难被发现,因为它几乎从来不是程序员有意选择的。

概览

好吧,让我们看看编译器转换这两个语句后的汇编代码。这种情况下的编译器理想情况下应该提供相同的优化(我们可能希望 运行 带有 -O2 标志)。

测试用例

我已经使用纯 C++ 编写了您的文件。也就是说,我选择直接通过终端执行编译,而不是依赖 Rcpp 黑魔法,它会在每次编译期间滑入 #include <Rcpp.h>

test.cpp

#include <iostream>

int add2( const int n ){
     int y = 0;
     int i, j;
     for(i=0; i<n; i++){
       for(j=0; j<n; j++) y++;
       for(j=0; j<(n*2); j++) y++;
     }
     return y ;
}

int add1( const int n ){
     int y = 0;
     for(int i=0; i<n; i++){
       for(int j=0; j<n; j++) y++;
       for(int j=0; j<(n*2); j++) y++;
     }
     return y ;
}

int main(){

    std::cout << add1(2) << std::endl;

    std::cout << add2(2) << std::endl;
}

分解二进制文件

为了查看 C++ 代码是如何被翻译成汇编的,我选择在 macOS 上使用 objdump 而不是内置的 otools。 (也非常欢迎有人提供该输出)。

在 macOS 中,我做了:

gcc -g -c test.cpp
# brew install binutils # required for (g)objdump 
gobjdump -d -M intel -S test.o

这给出了我在 post 末尾分块的以下带注释的输出。简而言之,两个版本的程序集完全相同。

基准为王

另一种验证方法是进行简单的微基准测试。如果两者之间存在显着差异,那将提供证据来建议不同的优化。

# install.packages("microbenchmark")
library("microbenchmark")

microbenchmark(a = add1(100L), b = add2(100L))

给出:

Unit: microseconds
 expr    min     lq     mean median      uq     max neval
    a 53.081 53.268 55.35613 53.576 53.8825  92.078   100
    b 53.069 53.261 56.28195 53.431 53.6795 169.841   100

调换顺序:

microbenchmark(b = add2(100L), a = add1(100L))

给出:

Unit: microseconds
 expr    min      lq     mean  median      uq     max neval
    b 53.112 53.3215 60.14641 55.0575 60.7685 196.865   100
    a 53.130 53.6850 58.72041 55.2845 60.6005  93.401   100

本质上,基准测试本身表明这两种方法之间没有显着差异。

附录

长输出

长输出add1

int add1( const int n ){
  a0:   55                      push   rbp
  a1:   48 89 e5                mov    rbp,rsp
  a4:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
     int y = 0;
  a7:   c7 45 f8 00 00 00 00    mov    DWORD PTR [rbp-0x8],0x0
     for(int i=0; i<n; i++){
  ae:   c7 45 f4 00 00 00 00    mov    DWORD PTR [rbp-0xc],0x0
  b5:   8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
  b8:   3b 45 fc                cmp    eax,DWORD PTR [rbp-0x4]
  bb:   0f 8d 76 00 00 00       jge    137 <__Z4add1i+0x97>
       for(int j=0; j<n; j++) y++;
  c1:   c7 45 f0 00 00 00 00    mov    DWORD PTR [rbp-0x10],0x0
  c8:   8b 45 f0                mov    eax,DWORD PTR [rbp-0x10]
  cb:   3b 45 fc                cmp    eax,DWORD PTR [rbp-0x4]
  ce:   0f 8d 1b 00 00 00       jge    ef <__Z4add1i+0x4f>
  d4:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  d7:   05 01 00 00 00          add    eax,0x1
  dc:   89 45 f8                mov    DWORD PTR [rbp-0x8],eax
  df:   8b 45 f0                mov    eax,DWORD PTR [rbp-0x10]
  e2:   05 01 00 00 00          add    eax,0x1
  e7:   89 45 f0                mov    DWORD PTR [rbp-0x10],eax
  ea:   e9 d9 ff ff ff          jmp    c8 <__Z4add1i+0x28>
       for(int j=0; j<(n*2); j++) y++;
  ef:   c7 45 ec 00 00 00 00    mov    DWORD PTR [rbp-0x14],0x0
  f6:   8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
  f9:   8b 4d fc                mov    ecx,DWORD PTR [rbp-0x4]
  fc:   c1 e1 01                shl    ecx,0x1
  ff:   39 c8                   cmp    eax,ecx
 101:   0f 8d 1b 00 00 00       jge    122 <__Z4add1i+0x82>
 107:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
 10a:   05 01 00 00 00          add    eax,0x1
 10f:   89 45 f8                mov    DWORD PTR [rbp-0x8],eax
 112:   8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
 115:   05 01 00 00 00          add    eax,0x1
 11a:   89 45 ec                mov    DWORD PTR [rbp-0x14],eax
 11d:   e9 d4 ff ff ff          jmp    f6 <__Z4add1i+0x56>
     }
 122:   e9 00 00 00 00          jmp    127 <__Z4add1i+0x87>
     return y ;
}

add2

的长输出
int add2( const int n ){
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
     int y = 0;
   7:   c7 45 f8 00 00 00 00    mov    DWORD PTR [rbp-0x8],0x0
     int i, j;
     for(i=0; i<n; i++){
   e:   c7 45 f4 00 00 00 00    mov    DWORD PTR [rbp-0xc],0x0
  15:   8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
  18:   3b 45 fc                cmp    eax,DWORD PTR [rbp-0x4]
  1b:   0f 8d 76 00 00 00       jge    97 <__Z4add2i+0x97>
       for(j=0; j<n; j++) y++;
  21:   c7 45 f0 00 00 00 00    mov    DWORD PTR [rbp-0x10],0x0
  28:   8b 45 f0                mov    eax,DWORD PTR [rbp-0x10]
  2b:   3b 45 fc                cmp    eax,DWORD PTR [rbp-0x4]
  2e:   0f 8d 1b 00 00 00       jge    4f <__Z4add2i+0x4f>
  34:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  37:   05 01 00 00 00          add    eax,0x1
  3c:   89 45 f8                mov    DWORD PTR [rbp-0x8],eax
  3f:   8b 45 f0                mov    eax,DWORD PTR [rbp-0x10]
  42:   05 01 00 00 00          add    eax,0x1
  47:   89 45 f0                mov    DWORD PTR [rbp-0x10],eax
  4a:   e9 d9 ff ff ff          jmp    28 <__Z4add2i+0x28>
       for(j=0; j<(n*2); j++) y++;
  4f:   c7 45 f0 00 00 00 00    mov    DWORD PTR [rbp-0x10],0x0
  56:   8b 45 f0                mov    eax,DWORD PTR [rbp-0x10]
  59:   8b 4d fc                mov    ecx,DWORD PTR [rbp-0x4]
  5c:   c1 e1 01                shl    ecx,0x1
  5f:   39 c8                   cmp    eax,ecx
  61:   0f 8d 1b 00 00 00       jge    82 <__Z4add2i+0x82>
  67:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  6a:   05 01 00 00 00          add    eax,0x1
  6f:   89 45 f8                mov    DWORD PTR [rbp-0x8],eax
  72:   8b 45 f0                mov    eax,DWORD PTR [rbp-0x10]
  75:   05 01 00 00 00          add    eax,0x1
  7a:   89 45 f0                mov    DWORD PTR [rbp-0x10],eax
  7d:   e9 d4 ff ff ff          jmp    56 <__Z4add2i+0x56>
     }
  82:   e9 00 00 00 00          jmp    87 <__Z4add2i+0x87>

输出短输出

add1

的短输出
int add1( const int n ){
     int y = 0;
     for(int i=0; i<n; i++){
 127:   8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
 12a:   05 01 00 00 00          add    eax,0x1
 12f:   89 45 f4                mov    DWORD PTR [rbp-0xc],eax
 132:   e9 7e ff ff ff          jmp    b5 <__Z4add1i+0x15>
       for(int j=0; j<n; j++) y++;
       for(int j=0; j<(n*2); j++) y++;
     }
     return y ;
 137:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
 13a:   5d                      pop    rbp
 13b:   c3                      ret    
 13c:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]

0000000000000140 <_main>:
}

add2

的短输出
int add2( const int n ){
     int y = 0;
     int i, j;
     for(i=0; i<n; i++){
  87:   8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
  8a:   05 01 00 00 00          add    eax,0x1
  8f:   89 45 f4                mov    DWORD PTR [rbp-0xc],eax
  92:   e9 7e ff ff ff          jmp    15 <__Z4add2i+0x15>
       for(j=0; j<n; j++) y++;
       for(j=0; j<(n*2); j++) y++;
     }
     return y ;
  97:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  9a:   5d                      pop    rbp
  9b:   c3                      ret    
  9c:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]

00000000000000a0 <__Z4add1i>:
}