【问题标题】:Is erasing a range more efficient than erasing each of the elements separately?擦除范围是否比单独擦除每个元素更有效?
【发布时间】:2021-12-29 07:55:02
【问题描述】:

如果您定义了要“擦除”的元素范围,那么为范围内的每个元素调用vector::erase(iterator) 还是调用一次vector::erase(iterator,iterator 更有效?

【问题讨论】:

  • 我不认为存在一个绝对和明确的答案,但你可以在你的系统和你的 c++ 实现上进行测量
  • @MatG 我的想法是vector::erase(iterator) 重新分配(移动元素)内存,因此如果您需要多次调用vector::erase(iterator),效率通常低于vector::erase(iterator,iterator)
  • @alexander.sivak 呃,你可能是对的。如果您测量,请发布您的结果!只是出于好奇,我会尝试quick-bench.com

标签: c++ algorithm vector erase-remove-idiom


【解决方案1】:

当然,这取决于具体情况,但您可以通过运行一些细节来感受一下。让我们看一个例子:

#include <iostream>
#include <vector>

uint64_t now() {
    return __builtin_ia32_rdtsc();
}

template< typename T >
void erase1( std::vector<T>& vec ) {
    while ( !vec.empty() ) {
        vec.erase( vec.begin() );
    }
}

template< typename T >
void erase2( std::vector<T>& vec ) {
    while ( !vec.empty() ) {
        vec.erase( vec.begin()+vec.size()-1 );
    }
}

template< typename T > 
void erase3( std::vector<T>& vec ) {
    vec.erase( vec.begin(), vec.end() );
}


int main() {
    for ( unsigned N = 1; N< (1<<20); N*=2 ) { 
        std::vector<int> vec;
        vec.resize( N );
        for ( uint32_t j=0; j<N; ++j ) vec[j] = N;
        uint64_t t0 = now();
        erase1( vec );
        uint64_t t1 = now();

        vec.resize( N );
        for ( uint32_t j=0; j<N; ++j ) vec[j] = N;
        uint64_t t2 = now();
        erase2( vec );
        uint64_t t3 = now();

        vec.resize( N );
        for ( uint32_t j=0; j<N; ++j ) vec[j] = N;
        uint64_t t4 = now();
        erase3( vec );
        uint64_t t5 = now();
        std::cout << (t1-t0) << " " << (t3-t2) << " " << (t5-t4) << std::endl;
    }
}

erase1()会从前面一一擦除。 erase2() 将从后面逐项擦除,erase3() 将擦除整个范围。

本次运行的结果是:

Program stdout
54 46 22
1144 66 24
230 116 22
362 74 24
596 108 24
924 128 22
2906 230 38
4622 270 24
11648 542 22
31764 960 34
94078 1876 24
313308 3874 32
1089342 7470 34
4967132 14792 34
25695930 14720 24
134255144 61492 24
585366944 122838 34
3320946224 115778 22
17386215680 484930 24

结果是循环的。

您可以看到从正面擦除的成本非常高。从后面看要快得多,但显然仍然是 O(N)。并且擦除范围几乎是瞬时的并且 O(1)。

【讨论】:

  • 结果可能与需要运行其析构函数的类型不同。对于int,在擦除它们时,实现可能会跳过很多工作。
  • @chris 当然正如我所说的,这只是为了感受一下。
  • 也许更好的基准是删除中间 50% 的元素,同时显示范围转移的线性成本。
  • @AkiSuihkonen 这是个好主意。
【解决方案2】:

来自https://en.cppreference.com/w/cpp/container/vector/erase

复杂性线性:调用 T 的析构函数的次数是 与擦除的元素个数相同,T 的赋值运算符为 称为次数等于元素个数 删除元素后的向量

擦除一个范围应该更有效,因为它允许在结束迭代器之后和包括结束迭代器之后的元素只移动一次(但移动量更大)。当然它是特定于实现的。

【讨论】:

    【解决方案3】:

    使用std::remove_if 比使用for 循环调用erase 方法更有效,因为对于方法erase 的每次调用,从擦除位置开始的所有元素都被移到左侧。也就是说同一个元素可以向左移动多次。

    使用该算法,每个元素只向左移动一次。

    请注意,在 C++ 20 中,附加了两个函数 std::erasestd::erase_if 可以用来代替惯用语 erase-remove。也就是说如果在 C++ 20 标准之前你需要写成例子

    std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    v.erase( std::remove_if( std::begin( v ), std::end( v ),
                             []( const auto &item )
                             {
                                 return item % 2;
                             } ), std::end( v ) );
    

    那么现在你可以写了

    std::erase_if( v, []( const auto &item ) { return item % 2; } );
    

    【讨论】:

      猜你喜欢
      • 2014-06-11
      • 2020-01-09
      • 1970-01-01
      • 2011-07-17
      • 1970-01-01
      • 2013-09-12
      • 2021-05-24
      • 2017-06-26
      • 1970-01-01
      相关资源
      最近更新 更多