【问题标题】:Improving efficiency of std::copy() with back_inserter() or inserter()使用 back_inserter() 或 inserter() 提高 std::copy() 的效率
【发布时间】:2012-09-08 00:56:43
【问题描述】:

back_inserterinsert_iterator 很方便,但效率也很低!

例如,当您附加chars 时,当您在copying 时,每个元素都会产生大量开销,而在许多情况下,确实不需要。

有没有办法让他们更有效率?

【问题讨论】:

  • 那么性能差异是多少(在您的机器上以 % 为单位)?大概每个字符的主要开销是检查sizecapacity 并增加size
  • @SteveJessop:嗯,你希望它是什么?我得到了 5 倍的差异。比较 thisthis
  • @SteveJessop:是的,这是其中很大一部分——但还有更多:它也是每个字符的额外指针取消引用。
  • 我没有特别的期望,我只是对您在没有提供基准数据的情况下声称进行了优化感到震惊和震惊;-) ideone 使用哪些优化标志?在我使用-O3 的机器上,您的两个程序之间的差异稍微不那么显着,但仍然是 3 倍。我没有运行任何使用您实际版本的copy 的代码。
  • @SteveJessop:嗯,老实说,我很震惊,因为它不够明显,无法避免我需要展示一个基准。 ;) 我不知道 IDEOne 使用什么,但 3 倍还是非常糟糕。在我的机器上(带有 /O2 的 VC++ 10),它是 10 倍的差异,不过——60 毫秒对 600 毫秒。这是一个非常讨厌的减速。

标签: c++ inserter


【解决方案1】:

是的,您可以定义一个新版本的std::copy,它可以劫持可优化调用。 :)

以下是 Visual C++ 和 GCC 的示例(或“hack”,如果您希望看到半空的玻璃)。

在我的个人电脑上(我使用 VC++ 2010),下面的代码使调用速度十倍
GCC 的基准也在这里,显示出 5 倍的差异:old versionnew version

但是你使用它之前:

请注意,此代码假设容器提供vector-like 接口

正如目前所写,这仅适用于 C++11,因为它使用 type_traits 标头的元编程功能优化复制操作将保持异常安全的情况。

如果您不需要异常安全性(尽管在实际执行此操作之前您应该三思而后行),或者如果您有另一种检查此类数据类型的方法,那么您可以更改

typename enable_if<..., typename insert_iterator<C> >::type

到:

insert_iterator<C>

其余代码也应该适用于 C++03。

namespace std
{
    template<class FwdIt, class C>
    back_insert_iterator<C> copy(
        FwdIt begin, FwdIt end, back_insert_iterator<C> it,
        forward_iterator_tag * =
          static_cast<typename iterator_traits<FwdIt>::iterator_category *>(0))
    {
        struct It : public back_insert_iterator<C>
        {
            using back_insert_iterator<C>::container;
            static C &deref(C &c) { return  c; }
            static C &deref(C *c) { return *c; }
        };
        copy(begin, end, inserter(It::deref(static_cast<It &>(it).container),
                      It::deref(static_cast<It &>(it).container).end()));
        return it;
    }

    template<class FwdIt, class C>
    typename enable_if<  // Only do this if it would be exception-safe!
        is_nothrow_copy_constructible<typename C::value_type>::value &&
        is_nothrow_copy_assignable<typename C::value_type>::value,
        insert_iterator<C>
    >::type copy(
        FwdIt const &begin, FwdIt const &end,
        insert_iterator<C> output,
        forward_iterator_tag * =                  // only forward iterators
          static_cast<typename iterator_traits<FwdIt>::iterator_category *>(0))
    {
        struct It : public insert_iterator<C>
        {
            using insert_iterator<C>::container;  // protected -> public
            using insert_iterator<C>::iter;       // protected -> public
            static C &deref(C &c) { return  c; }
            static C &deref(C *c) { return *c; }
        };
        It &it(static_cast<It &>(output));
        typename C::iterator it_iter_end(It::deref(it.container).end());
        {
            // Convert iterators to offsets
            typename C::size_type const iter_end_off =
                std::distance(It::deref(it.container).begin(), it_iter_end);
            typename iterator_traits<typename C::iterator>::difference_type off
                = std::distance(It::deref(it.container).begin(), it.iter);

            // Resize container
            It::deref(it.container).resize(
                It::deref(it.container).size() +
                static_cast<typename C::size_type>(std::distance(begin, end)));
            
            // Renormalize, in case invalidated
            it.iter = It::deref(it.container).begin();
            std::advance(it.iter, off);
            it_iter_end = It::deref(it.container).begin();
            std::advance(it_iter_end, iter_end_off);
        }
        typename C::iterator result
          = copy_backward(it.iter, it_iter_end, It::deref(it.container).end());
        copy_backward(begin, end, result);
        return inserter(It::deref(it.container), result);
    }
}

【讨论】:

  • 不允许在 std 命名空间内添加这些特化。您可以使用相同的代码,但它必须在该命名空间之外。演员 static_cast&lt;It&amp;&gt;(output) 也可以是未定义的行为,具体取决于 output 的类型。
  • @DavidRodríguez-dribeas,我不知道谁是对的,但这是:此外,std 命名空间中的所有内容都是保留的。 (不过,您可以添加模板特化。)来自this answer
  • @chris:您可以为您自己的用户定义类型添加标准模板的特化,而不仅仅是任何类型。除此之外,没有函数模板偏特化之类的东西,上面的代码是std::copy重载,而不是函数模板的特化
  • @chris: 17.4.3.1p1 程序可以将任何标准库模板的模板特化添加到命名空间 std。标准库模板的这种特化(完全或部分)会导致未定义的行为,除非声明依赖于用户定义的外部链接名称并且除非特化满足原始模板的标准库要求。跨度>
  • @Mehrdad,但是用有用的信息注释答案并不是“否定这个想法”。即使在答案中说明了这一点,也要将其视为重点。
猜你喜欢
  • 1970-01-01
  • 2014-12-06
  • 1970-01-01
  • 1970-01-01
  • 2011-11-09
  • 2020-04-04
  • 1970-01-01
  • 2010-10-28
  • 2020-11-16
相关资源
最近更新 更多