繁体   English   中英

插入矢量变换

[英]Inserting a vector transformation

我之前发布了关于连接两个std::vector s的最佳方法的问题 ,其中必须首先转换一个向量。 虽然使用std::transform的显而易见的解决方案可能不是理论上最优的解决方案,但多容量检查的成本不太可能是显着的。

但是,如果我们考虑插入一个必须转换为另一个向量的向量的更普遍的问题,那么现在可能存在潜在的重大开销。

这样做的最佳方法是什么?

我在这里看到几个选项:

方法1:临时变换

std::vector<T1> temp {};
temp.reserve(vec.size());
std::transform(vec2.begin(), vec2.end(), std::back_inserter(temp), op);
vec1.insert(vec1.begin() + pos, std::make_move_iterator(temp.begin()),
               std::make_move_iterator(temp.end()));

但是现在开销不是一个简单的容量检查,而是额外的成本是对tempsize_of(T1) * vec2.size()的分配/释放。 如果vec2很大,这很容易成为一个重要的成本。

方法2:循环插入

vec1.reserve(vec1.size() + vec2.size());
std::size_t i {pos};
for (const auto& p : vec2) {
    vec1.insert(vec1.begin() + i, op);
    ++i;
}

这避免了方法1的附加分配/解除分配,但还有另一种严重的问题与这种解决方案:每一个的n = vec1.size() - pos中的元素vec1必须被移位n次, O(n^2)的操作。

方法3:转移和复制

vec1.resize(vec1.size() + pair_vec.size());
std::move(vec1.begin() + pos, vec1.end(), vec1.begin() + pos + vec2.size());
std::transform(vec2.begin(), vec2.end(), vec1.begin() + pos, op);

这似乎接近我们想要的,我们只支付'额外' n默认构造函数。

编辑

我的班次和复制方法(3)不正确,它应该是:

auto v1_size = vec1.size();
vec1.resize(vec1.size() + vec2.size());
std::move(vec1.begin() + pos, vec1.begin() + v1_size, vec1.begin() + pos + vec2.size());
std::transform(vec2.begin(), vec2.end(), vec1.begin() + pos, op);

测试(使用@ VaughnCato和@ ViktorSehr的方法更新)

我测试了方法1和3(方法2显然不会很好 - 易于验证),以及@VaughnCato@ViktorSehr建议的方法。 这是完整的代码:

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <cstdint>
#include <chrono>
#include <numeric>
#include <random>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/range/adaptor/transformed.hpp>

using std::size_t;

std::vector<int> generate_random_ints(size_t n)
{
    std::default_random_engine generator;
    auto seed1 = std::chrono::system_clock::now().time_since_epoch().count();
    generator.seed((unsigned) seed1);
    std::uniform_int_distribution<int> uniform {};
    std::vector<int> v(n);
    std::generate_n(v.begin(), n, [&] () { return uniform(generator); });
    return v;
}

std::vector<std::string> generate_random_strings(size_t n)
{
    std::default_random_engine generator;
    auto seed1 = std::chrono::system_clock::now().time_since_epoch().count();
    generator.seed((unsigned) seed1);
    std::uniform_int_distribution<int> uniform {};
    std::vector<std::string> v(n);
    std::generate_n(v.begin(), n, [&] () { return std::to_string(uniform(generator)); });
    return v;
}

template <typename D=std::chrono::nanoseconds, typename F>
D benchmark(F f, unsigned num_tests)
{
    D total {0};
    for (unsigned i = 0; i < num_tests; ++i) {
        auto start = std::chrono::system_clock::now();
        f();
        auto end = std::chrono::system_clock::now();
        total += std::chrono::duration_cast<D>(end - start);
    }
    return D {total / num_tests};
}

template <typename T1, typename T2, typename UnaryOperation>
void temp_transform(std::vector<T1> vec1, const std::vector<T2> &vec2, size_t pos, UnaryOperation op)
{
    std::vector<T1> temp {};
    temp.reserve(vec2.size());
    std::transform(vec2.begin(), vec2.end(), std::back_inserter(temp), op);
    vec1.insert(vec1.begin() + pos, std::make_move_iterator(temp.begin()),
                std::make_move_iterator(temp.end()));
}

template <typename T1, typename T2, typename UnaryOperation>
void shift_copy(std::vector<T1> vec1, const std::vector<T2> &vec2, size_t pos, UnaryOperation op)
{
    auto v1_size = vec1.size();
    vec1.resize(vec1.size() + vec2.size());
    std::move(vec1.begin() + pos, vec1.begin() + v1_size, vec1.begin() + pos + vec2.size());
    std::transform(vec2.begin(), vec2.end(), vec1.begin() + pos, op);
}

template <typename T1, typename T2, typename UnaryOperation>
void boost_transform(std::vector<T1> vec1, const std::vector<T2> &vec2, size_t pos, UnaryOperation op)
{
    auto v2_begin = boost::make_transform_iterator(vec2.begin(), op);
    auto v2_end   = boost::make_transform_iterator(vec2.end(), op);
    vec1.insert(vec1.begin() + pos, v2_begin, v2_end);
}

template <typename T1, typename T2, typename UnaryOperation>
void boost_adapter(std::vector<T1> vec1, const std::vector<T2> &vec2, size_t pos, UnaryOperation op)
{
   auto transformed_range = vec2 | boost::adaptors::transformed(op);
   vec1.insert(vec1.begin() + pos, transformed_range.begin(), transformed_range.end());
}

int main(int argc, char **argv)
{
    unsigned num_tests {1000};
    size_t vec1_size {1000000};
    size_t vec2_size {1000000};
    size_t insert_pos {vec1_size / 2};

    // Switch the variable names to inverse test
    auto vec2 = generate_random_ints(vec1_size);
    auto vec1 = generate_random_strings(vec2_size);

    //auto op = [] (const std::string& str) { return std::stoi(str); };
    auto op = [] (int i) { return std::to_string(i); };

    auto f_temp_transform_insert = [&vec1, &vec2, &insert_pos, &op] () {
        temp_transform(vec1, vec2, insert_pos, op);
    };
    auto f_shift_copy_insert = [&vec1, &vec2, &insert_pos, &op] () {
        shift_copy(vec1, vec2, insert_pos, op);
    };
    auto f_boost_transform_insert = [&vec1, &vec2, &insert_pos, &op] () {
        boost_transform(vec1, vec2, insert_pos, op);
    };
   auto f_boost_adapter_insert = [&vec1, &vec2, &insert_pos, &op] () {
       boost_adapter(vec1, vec2, insert_pos, op);
   };

    auto temp_transform_insert_time  = benchmark<std::chrono::milliseconds>(f_temp_transform_insert, num_tests).count();
    auto shift_copy_insert_time      = benchmark<std::chrono::milliseconds>(f_shift_copy_insert, num_tests).count();
    auto boost_transform_insert_time = benchmark<std::chrono::milliseconds>(f_boost_transform_insert, num_tests).count();
    auto boost_adapter_insert_time   = benchmark<std::chrono::milliseconds>(f_boost_adapter_insert, num_tests).count();

    std::cout << "temp_transform: " << temp_transform_insert_time << "ms" << std::endl;
    std::cout << "shift_copy: " << shift_copy_insert_time << "ms" << std::endl;
    std::cout << "boost_transform: " << boost_transform_insert_time << "ms" << std::endl;
    std::cout << "boost_adapter: " << boost_adapter_insert_time << "ms" << std::endl;

    return 0;
}

结果

编译:

g++ vector_insert.cpp -std=c++11 -O3 -o vector_insert_test

平均用户运行时间是:

|    Method   | std::string -> int (ms) | int -> std::string (ms) |
|:-----------:|:-----------------------:|:-----------------------:|
| 1           |            68           |           220           |
| 3           |            67           |           222           |
| @VaughnCato |            71           |           239           |
| @ViktorSehr |            72           |           236           |

TLDR

  • boost方法没有std::transform方法那么快。
  • std::transform方法几乎同样好 - 虽然很难解释int - > std::stringstd::string - > int performance之间的差异。

@ VaughnCato的方法使用boost::transform_iterator你的其他问题,应该为这一个也运作良好:

auto vec1begin = boost::make_transform_iterator(vec1.begin(), f);
auto vec1end = boost::make_transform_iterator(vec1.end(), f);
vec2.insert(middle, vec1begin, vec1end);

如何使用boost :: range :: adapters :: transformed

std::vector<...> first_vector;
auto transform_functor = [](...){...};
auto transformed_range = first_vector | boost::adaptors::transformed(transform_functor):
some_vector.insert(some_vector.end(), transformed_range.begin(), transformed_range.end());

鉴于boost :: transformvector :: insert函数尽可能巧妙地实现,这应该能够忽略任何无法容量的容量检查。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM