在R中,我们的布尔类型实际上可以有三个值,true、false和na,我们使用C级别的int来表示这一点。
我正在研究一个矢量化的&&操作(是的,我们已经在R中有了这个功能,但请跟我一起),它还处理na情况。标量结果如下:
F && F == F
F && T == F
F && N == F
T && F == F
T && T == T
T && N == N
N && F == F
N && T == N
N && N == N
请注意,它的工作原理类似于C语言中的
&&
,但与除false
以外的任何内容组合时,na
值会传播。在这种情况下,我们“知道”&&
永远不可能为真,因此返回false
。现在来看实现。假设我们有两个向量
v_out
和v_x
,我们想对它们执行向量化的&&
操作。我们可以用结果覆盖v_out
。一种选择是:// Option 1
for (int i = 0; i < size; ++i) {
int elt_out = v_out[i];
int elt_x = v_x[i];
if (elt_out == 0) {
// Done
} else if (elt_x == 0) {
v_out[i] = 0;
} else if (elt_out == na) {
// Done
} else if (elt_x == na) {
v_out[i] = na;
}
}
另一个选择是:
// Option 2
for (int i = 0; i < size; ++i) {
int elt_out = v_out[i];
if (elt_out == 0) {
continue;
}
int elt_x = v_x[i];
if (elt_x == 0) {
v_out[i] = 0;
} else if (elt_out == na) {
// Done
} else if (elt_x == na) {
v_out[i] = na;
}
}
我原以为第二种选项会更快,因为它避免了在不需要时访问
v_x[i]
。但实际上,当使用-O2
编译时,它的速度要慢两倍!在下面的脚本中,我得到了以下计时结果。请注意,我使用Mac并使用Clang进行编译。
It seems reasonable with O0. They are about the same.
2x faster with O2 with Option 1!
Option 1, `clang -O0`
0.110560
Option 2, `clang -O0`
0.107710
Option 1, `clang -O2`
0.032223
Option 2, `clang -O2`
0.070557
这里发生了什么?我最好的猜测是,这与选项1中始终按线性方式访问
v_x[i]
有关,这极其快速。但在选项2中,实际上是以一种“随机”的方式访问v_x[i]
(有点),因为它可能访问v_x[10]
,但然后不需要从v_x
获取另一个元素,直到v_x[120]
,而且因为该访问不是线性的,所以它可能要慢得多。可重现的脚本:
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <time.h>
int main() {
srand(123);
int size = 1e7;
int na = INT_MIN;
int* v_out = (int*) malloc(size * sizeof(int));
int* v_x = (int*) malloc(size * sizeof(int));
// Generate random numbers between 1-3
// 1 -> false
// 2 -> true
// 3 -> na
for (int i = 0; i < size; ++i) {
int elt_out = rand() % 3 + 1;
if (elt_out == 1) {
v_out[i] = 0;
} else if (elt_out == 2) {
v_out[i] = 1;
} else {
v_out[i] = na;
}
int elt_x = rand() % 3 + 1;
if (elt_x == 1) {
v_x[i] = 0;
} else if (elt_x == 2) {
v_x[i] = 1;
} else {
v_x[i] = na;
}
}
clock_t start = clock();
// Option 1
for (int i = 0; i < size; ++i) {
int elt_out = v_out[i];
int elt_x = v_x[i];
if (elt_out == 0) {
// Done
} else if (elt_x == 0) {
v_out[i] = 0;
} else if (elt_out == na) {
// Done
} else if (elt_x == na) {
v_out[i] = na;
}
}
// // Option 2
// for (int i = 0; i < size; ++i) {
// int elt_out = v_out[i];
//
// if (elt_out == 0) {
// continue;
// }
//
// int elt_x = v_x[i];
//
// if (elt_x == 0) {
// v_out[i] = 0;
// } else if (elt_out == na) {
// // Done
// } else if (elt_x == na) {
// v_out[i] = na;
// }
// }
clock_t end = clock();
double time = (double) (end - start) / CLOCKS_PER_SEC;
free(v_out);
free(v_x);
printf("%f\n", time);
return 0;
}
根据评论中的一些问题,以下是对未来读者的澄清几点:
我使用的是2018年 15英寸的MacBook Pro,搭载2.9 GHz的6核Intel i9-8950HK(6核Coffee Lake)。
我测试使用的特定Clang版本是
Apple clang version 13.1.6 (clang-1316.0.21.2.5)
,目标为x86_64-apple-darwin21.6.0
R限制了我使用
int
作为数据类型(即使有更有效的选项),并且以下编码:false = 0
,true = 1
,na = INT_MIN
。我提供的可重现示例遵守此规定。原始问题实际上并不是要求使代码运行更快。我只是想了解我的两种if/else方法之间的差异。尽管如此,一些答案表明无分支方法可以更快,我非常感谢那些用户提供的解释!这极大地影响了我正在工作的实现的最终版本。