为什么要使用 range::views::remove_if | Ranges::to_vector 和 Ranges::actions::remove_if 生成不同的代码?我应该更喜欢哪一个,为什么?

问题描述 投票:0回答:1

拿这个

#include <range/v3/view/remove_if.hpp>
#include <range/v3/range/conversion.hpp>
#include <vector>

std::vector<int> foo(std::vector<int> v, bool(*p)(int)) {
    return v | ranges::views::remove_if(p) | ranges::to_vector;
}

相比之下

#include <range/v3/action/remove_if.hpp>
#include <vector>

std::vector<int> bar(std::vector<int> v, bool(*p)(int)) {
    return std::move(v) | ranges::actions::remove_if(p);
}

周围没有模板,只有两个 TU,每个 TU 都提供具有相同签名的纯函数。鉴于它们的实现,从调用者的角度来看,我希望这两个函数能够完成相同的任务。他们似乎就是这么做的。

但是,它们编译成相当不同的代码,以至于 GCC(至少是 trunk)为后者生成更短的代码,而 Clang(trunk)为前者生成更短的代码。

我看不出这两个函数有任何理由编译成不同的代码,除了“编译器很难为这两个函数提供相同的代码”,但是什么让它变得如此困难?或者,如果我错了,为什么这两个函数必须编译为不同的程序集?

除了基准测试之外,还有什么理由让我更喜欢其中一种实现而不是另一种实现?

完整示例

c++ gcc clang c++20 range-v3
1个回答
0
投票

我不确定理论上两者是否可能生成相同的代码。让我们来看看这两种方法。

行动

std::vector<int> bar(std::vector<int> v, bool(*p)(int)) {
    return std::move(v) | ranges::actions::remove_if(p);
}

对于操作,这是采取

v
,将其就地进行变异以删除满足
p
的元素,然后返回相同的
v
。这相当于写了:

std::vector<int> bar(std::vector<int> v, bool(*p)(int)) {
    std::erase_if(v, p);
    return v;
}

或者,从 C++20 之前开始:

std::vector<int> bar(std::vector<int> v, bool(*p)(int)) {
    v.erase(std::remove_if(v.begin(), v.end(), p), v.end());
    return v;
}

肯定不会发生分配,我们只是移动一堆int

,然后更改
v.size()

观点

std::vector<int> foo(std::vector<int> v, bool(*p)(int)) { return v | ranges::views::remove_if(p) | ranges::to_vector; }

views::remove_if

 是一个惰性过滤器。这让我们可以了解 
v
 中不满足 
p
 的元素。然后,
to_vector
将构造一个新的
vector
,需要分配,并将
v
中所有不满足
p
的元素复制到新的
vector
中。返回新向量。

它会

混合优化吗?

最初,表达式

v | remove_if(p) | to_vector

 分配一个与 
vector<int>
 不同的新 
v
v
 在此表达式的整个长度内都处于活动状态,因此您无法在此处重用 
v
 的内存。

这里的优化不仅仅是认识到

v

即将被销毁,因此它的分配可以被重用。而且新的 
vector
 最多与 
v
 大小相同,因此重用其分配是一个可行的策略。 
而且这个新vector
的元素以允许重用该分配的方式填充。

再加上分配是可观察的,甚至可能无法从一开始就省略——即使假设编译器可以以某种方式完成所有这些步骤。

从根本上来说,这两种情况只是不同的算法。有时编译器可以弄清楚这一点,但这似乎是一个巨大的延伸。如果存在这样的优化,那么它基本上是针对这种情况手工制作的。

我应该更喜欢哪一个,为什么?

一般来说,这个问题的答案是使用最具体的工具来完成工作。如果您有一个

vector<int>

 并且您只想要不满足 
p
 的元素,并且根本不需要原始元素 - 这就是 
actions::remove_if
 (或者,根据上下文,只需直接调用 
std::erase_if
)。这就是 
actions::remove_if
 旨在解决的工作。

© www.soinside.com 2019 - 2024. All rights reserved.