【问题标题】:Repeat contents of a std::vector重复 std::vector 的内容
【发布时间】:2020-11-20 17:08:50
【问题描述】:

假设我有一个普通类型的向量(可能很大),例如

std::vector<int> v{1,2,3};

重复n次最好的方法是什么?

例如3次将给出结果{1,2,3,1,2,3,1,2,3}

当然,这是一个经常出现的问题(数值库通常内置有这样的函数)。我幼稚的解决方案:

template<typename T>
std::vector<T> repeat(const std::vector<T> &input, unsigned int times) {
    std::vector<T> result;
    auto input_size = input.size();
    result.reserve(input_size * times);
    for (std::size_t rep = 0; rep < times; ++rep) {
        for (std::size_t i = 0; i < input_size; ++i) {
            result.push_back(input[i % input_size]);
        }
    }
    return result;
}

这当然可以更快?也许使用 std::copy?但是,在这种情况下,我不确定如何在避免零初始化的同时告诉向量它的新大小。显然这不容易做到(例如,参见1)。我也尝试使用迭代器来实现,但似乎并没有更快。

【问题讨论】:

  • 比什么更快?对于性能问题,您需要展示您正在使用的设置,以及您的解决方案在某些基准测试中的性能。
  • 您对分析结果的编辑非常好。但是您不能将其添加到问题中。继续并将其作为答案发布。
  • @cigien 我看到您作为问题的一部分删除的分析,尚未完全回答(有一些细微的变化,其中一些在我提供的链接中,我没有尝试过并且这个线程中没有人说任何关于将它们与所讨论的进行比较的内容)。由于您已经删除了该部分,因此我将其发布为答案。但是,根据网站政策,我根本不知道这是正确的方法,并且“我无法将其添加到问题中”。
  • 是的,问题框仅供提问。您可以对其进行编辑以澄清您的问题。但是您对各种解决方案的分析结果不是问题的一部分。它们是一个答案,应该写在答案框中。
  • 你能用memcpy代替push_back吗?

标签: c++ performance stdvector


【解决方案1】:

使用 range-v3,您可以编写:

#include <range/v3/all.hpp>
namespace rv = ranges::views;

template<typename T>
auto repeat(const std::vector<T> &input, unsigned int times) 
{
    return rv::repeat_n(input, times) 
           | rv::join 
           | ranges::to<std::vector<T>>;
}

这是demo

我怀疑这将有足够好的性能满足您的需求。

【讨论】:

    【解决方案2】:

    我会立即调整向量的大小以避免任何中间重新分配。然后,您可以使用 std::copy 和一些算术将 input 向量复制到 result,并使用特定的偏移量到该预分配向量中。

    template<typename T>
    std::vector<T> repeat(const std::vector<T> &input, unsigned int times) {
        std::vector<T> result(input.size() * times);
        for (std::size_t rep = 0; rep < times; ++rep) {
            std::copy(input.begin(), input.end(), std::next(result.begin(), rep * input.size()));
        }
        return result;
    }
    

    【讨论】:

    • 我会更进一步,调用result.begin() 1 次并将迭代器保存到在每次循环迭代时递增的局部变量:std::vector&lt;T&gt; result(input.size() * times); auto iter = result.begin(); for (std::size_t rep = 0; rep &lt; times; ++rep, iter += input.size()) { std::copy(input.begin(), input.end(), iter); }
    • 这对于int(或其他简单构造的类型)来说是可以的。既然您经历了将其作为模板的麻烦,我认为最好声明一个空向量并使用reserve()
    • @RemyLebeau 我分析了您的版本,但它似乎并没有更快(请参阅我添加到问题中的分析代码)。
    • @VladFeinstein 所以你的意思是像我的问题中提供的代码?注意:对于普通类型,该版本明显较慢。
    • @AdomasBaliuka - 我指的是 Cory Kramer 回答的 this 中的代码,而不是 std::vector&lt;T&gt; result(input.size() * times); do std::vector&lt;T&gt; result; result.reserve(input.size() * times); 但是,再一次,@ 没有区别987654331@类型
    【解决方案3】:

    这最初是对问题的编辑。用户 cigien 建议将其发布为答案。这包含一些不完整(即尚未探索实施解决方案的所有可能性)的分析结果。

    我做了一些分析代码(我绝不是分析专家)比较我的版本和 Cory Kramer 的答案。答案中的代码似乎比我的快 4.5 倍(在quick-bench.com 使用 GCCv10.1、C++17、O3 优化进行了测试)。 Remy Lebeau 建议保存一个临时的迭代器似乎没有任何区别。

    在问自己之前,我错过了一些重复的问题:12。其中的一些答案提出了更细微的不同解决方案,我尚未对此进行分析。

    range-v3 库(由 cigien 回答)虽然看起来很方便,但不是我可以使用的选项,我也没有对其进行分析。

    分析代码:

    // This code is intended to be used at quick-bench.com.
    // Needs profiling library AND ADDITIONAL INCLUDES to compile, 
    // see https://github.com/google/benchmark
        
    #include<vector>
        
    template<typename T>
    std::vector<T> repeat_1(const std::vector<T> &input, unsigned int times) {
        std::vector<T> result;
        auto input_size = input.size();
        result.reserve(input_size * times);
        for (std::size_t rep = 0; rep < times; ++rep) {
            for (std::size_t i = 0; i < input_size; ++i) {
                result.push_back(input[i % input_size]);
            }
        }
        return result;
    }
        
    template<typename T>
    std::vector<T> repeat_2(const std::vector<T> &input, unsigned int times) {
        std::vector<T> result(input.size() * times);
        for (std::size_t rep = 0; rep < times; ++rep) {
            std::copy(input.begin(), input.end(),
                      std::next(result.begin(), rep * input.size()));
        }
        return result;
    }
        
    template<typename T>
    std::vector<T> repeat_3(const std::vector<T> &input, unsigned int times) {
        std::vector<T> result(input.size() * times);
        auto iter = result.begin();
        for (std::size_t rep = 0; rep < times; ++rep, iter += input.size()) {
            std::copy(input.begin(), input.end(), iter);
        }
        return result;
    }
    
    
    static void version_1(benchmark::State &state) {
        std::vector<int> vec = {12, 4, 4, 5, 16, 6, 6, 17, 77, 8, 54};
        for (int i = 0; i < 100'000; ++i) {
            vec.push_back(i % 10'000);
        }
    
        for (auto _ : state) {
            auto repeated = repeat_1(vec, 1000);
            // Make sure the variable is not optimized away by compiler
            benchmark::DoNotOptimize(repeated);
        }
    }
    BENCHMARK(version_1);
        
    static void version_2(benchmark::State &state) {
        std::vector<int> vec = {12, 4, 4, 5, 16, 6, 6, 17, 77, 8, 54};
        for (int i = 0; i < 100'000; ++i) {
            vec.push_back(i % 10'000);
        }
    
        for (auto _ : state) {
            auto repeated = repeat_2(vec, 1000);
            // Make sure the variable is not optimized away by compiler
            benchmark::DoNotOptimize(repeated);
        }
    }
    BENCHMARK(version_2);
        
    static void version_3(benchmark::State &state) {
        std::vector<int> vec = {12, 4, 4, 5, 16, 6, 6, 17, 77, 8, 54};
        for (int i = 0; i < 100'000; ++i) {
            vec.push_back(i % 10'000);
        }
    
        for (auto _ : state) {
            auto repeated = repeat_3(vec, 1000);
            // Make sure the variable is not optimized away by compiler
            benchmark::DoNotOptimize(repeated);
        }
    }
    BENCHMARK(version_3);
    

    【讨论】:

    • 这很好,感谢您发布答案。小点,链接到您运行的 actual 快速基准测试会很好。运行基准测试时,您将获得一个可以粘贴到此处的唯一 URL。
    • @cigien 更新了 URL。假设您有 Google Benchmark,也许您想使用 range-v3 库来分析您的版本以进行比较?
    • 感谢您添加链接。我添加了 range-v3 版本here,因此您可以更改答案中的链接。请注意,quick-bench 似乎具有旧版本的 range-v3,因此命名空间是 view 而不是 views,它是 to_ 而不是 to。因此,请务必将该特定函数的源代码也添加到您的答案中。
    • @cigien 我包含了你的代码。当我解决它时,我仍然会使用back_inserter 来分析insertcopy。那么希望我能在这里接受最快的答案。
    • 我自己回滚了,因为答案与我的错误基准不正确。
    猜你喜欢
    • 2017-03-21
    • 2016-02-07
    • 1970-01-01
    • 2014-11-19
    • 1970-01-01
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    • 2018-06-06
    相关资源
    最近更新 更多