在 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>:
}
对于开始使用 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>:
}