使用位集在不使用循环的情况下获取数组项的条件和
Using a bitset to get a conditional sum of array items without using loop
有一个位集b
和一个数组arr
。我想通过使用 bitset 来获取数组的总和,这样 arr[i]
只有在设置了 b[i]
时才会被求和。
示例:
bitset<4> b("1001");
arr[4] = {5,4,3,1};
===> the sum would be 1+5 = 6.
显然,我可以使用循环。但是这种计算会重复多次。没有循环有没有更有效的方法?
最快的方法可能是遍历循环并根据当前位是否已设置进行添加。
也许使用位移位而不是直接访问位 n 可以避免标准库的一些隐藏体操来提取特定位,但这还有待通过基准测试来证明。:
int sum=0;
for (size_t i=b.size(); i; i--, b>>=1)
if (b[0])
sum+=arr[i-1];
为了完整性和乐趣,如果您可以使用 vector<bool>
而不是 bitset<>
,您可以只使用标准库:
vector<bool> b{1,0,0,1};
int arr[4] = {5,4,3,1};
int test=inner_product(begin(arr), end(arr), b.begin(), 0);
编辑:附加信息 - 相信您的优化编译器
我在 x86-64 平台的 gcc 7.2 godbolt 在线编译器上进行了实验,以查看由上述不同变体生成的程序集。
最简单的解决方案,通过循环迭代并有条件地添加项目非常快:
- 使用您示例中的常量值,编译器能够通过常量传播并立即确定
6
的结果(参见 here)。
- 如果您的数据结构较小,编译器会自动展开循环,使循环变得非常快 (14 sequential assembler instruction for 4 elements),无需维护任何循环索引。
- 但是,优化器可能会选择不展开更大尺寸的循环。在我的实验中,循环展开的大小最多为 17 个项目,其中 18 个元素是 real loop)
移位替代方案非常接近:
- 最多 17 项,展开循环,生成的代码与简单循环完全相同。
- 从18个元素开始,生成一个循环,每次迭代有one assembly instruction less个。看起来有点快,但只有基准才能真正产生差异,可能在纳秒范围内。
当然,inner_product
要重得多,因为它确实进行了乘法运算,需要将bools转换为ints,而且它必须应对bool向量的动态大小。
Is there a more effective way without a loop ?
常见的for循环总是存在三个很容易被回避的低效之处:步幅增量、循环结束测试和循环结束跳转。然而,这些都是非常小的,所以不要抱太大希望。
您可以轻松地 "unroll" 循环以获得非常小的收益,但要付出更多代码行的代价(以 space 换取性能)。随着 bitset 的大小和数组的增长,这个方案变得乏味......
std::bitset<4> b (std::string("1001"));
std::array<int, 4> a {{5,4,3,1}};
// loop form
int sum = 0;
for (uint i=0; i < 4; ++i) // stride increment, test end-of-loop
{
if (b[i]) sum += a[i];
} // end-of-loop jump
std::cout << "\n sum: " << sum << std::endl;
// unrolled loop form -- no stride increment,
// no end-of-loop test, and
// no end-of-loop jump
sum = 0;
if (b[0]) sum += a[0];
if (b[1]) sum += a[1];
if (b[2]) sum += a[2];
if (b[3]) sum += a[3];
// bigger array, more lines ...
std::cout << "\n sum: " << sum << std::endl;
注意 - 类似的方法支持大循环。在此示例中,每个 'line' 执行 1 个总和。对于大循环,每一行都可以调用一个函数来执行这些行中的 10(或 100 或 1000,等等)。我不记得上次看到另一个程序员展开循环代码是什么时候了。增益很小。
有一个位集b
和一个数组arr
。我想通过使用 bitset 来获取数组的总和,这样 arr[i]
只有在设置了 b[i]
时才会被求和。
示例:
bitset<4> b("1001");
arr[4] = {5,4,3,1};
===> the sum would be 1+5 = 6.
显然,我可以使用循环。但是这种计算会重复多次。没有循环有没有更有效的方法?
最快的方法可能是遍历循环并根据当前位是否已设置进行添加。
也许使用位移位而不是直接访问位 n 可以避免标准库的一些隐藏体操来提取特定位,但这还有待通过基准测试来证明。:
int sum=0;
for (size_t i=b.size(); i; i--, b>>=1)
if (b[0])
sum+=arr[i-1];
为了完整性和乐趣,如果您可以使用 vector<bool>
而不是 bitset<>
,您可以只使用标准库:
vector<bool> b{1,0,0,1};
int arr[4] = {5,4,3,1};
int test=inner_product(begin(arr), end(arr), b.begin(), 0);
编辑:附加信息 - 相信您的优化编译器
我在 x86-64 平台的 gcc 7.2 godbolt 在线编译器上进行了实验,以查看由上述不同变体生成的程序集。
最简单的解决方案,通过循环迭代并有条件地添加项目非常快:
- 使用您示例中的常量值,编译器能够通过常量传播并立即确定
6
的结果(参见 here)。 - 如果您的数据结构较小,编译器会自动展开循环,使循环变得非常快 (14 sequential assembler instruction for 4 elements),无需维护任何循环索引。
- 但是,优化器可能会选择不展开更大尺寸的循环。在我的实验中,循环展开的大小最多为 17 个项目,其中 18 个元素是 real loop)
移位替代方案非常接近:
- 最多 17 项,展开循环,生成的代码与简单循环完全相同。
- 从18个元素开始,生成一个循环,每次迭代有one assembly instruction less个。看起来有点快,但只有基准才能真正产生差异,可能在纳秒范围内。
当然,inner_product
要重得多,因为它确实进行了乘法运算,需要将bools转换为ints,而且它必须应对bool向量的动态大小。
Is there a more effective way without a loop ?
常见的for循环总是存在三个很容易被回避的低效之处:步幅增量、循环结束测试和循环结束跳转。然而,这些都是非常小的,所以不要抱太大希望。
您可以轻松地 "unroll" 循环以获得非常小的收益,但要付出更多代码行的代价(以 space 换取性能)。随着 bitset 的大小和数组的增长,这个方案变得乏味......
std::bitset<4> b (std::string("1001"));
std::array<int, 4> a {{5,4,3,1}};
// loop form
int sum = 0;
for (uint i=0; i < 4; ++i) // stride increment, test end-of-loop
{
if (b[i]) sum += a[i];
} // end-of-loop jump
std::cout << "\n sum: " << sum << std::endl;
// unrolled loop form -- no stride increment,
// no end-of-loop test, and
// no end-of-loop jump
sum = 0;
if (b[0]) sum += a[0];
if (b[1]) sum += a[1];
if (b[2]) sum += a[2];
if (b[3]) sum += a[3];
// bigger array, more lines ...
std::cout << "\n sum: " << sum << std::endl;
注意 - 类似的方法支持大循环。在此示例中,每个 'line' 执行 1 个总和。对于大循环,每一行都可以调用一个函数来执行这些行中的 10(或 100 或 1000,等等)。我不记得上次看到另一个程序员展开循环代码是什么时候了。增益很小。