Каков наиболее эффективный способ добавления одного std::vector в конец другого?
Пусть v1 - целевой вектор, v2 должен быть добавлен к нему.
Теперь я делаю:
v1.reserve(v1.size() + v2.size());
copy(v2.begin(), v2.end(), back_inserter(v1));
Это самый эффективный способ? Или может быть, это может быть сделано только путем копирования куска памяти?
Спасибо!
Ответы
Ответ 1
После много споров (и разумных комментариев от Matthieu M. и villintehaspam), я изменю свое предложение на
v1.insert( v1.end(), v2.begin(), v2.end() );
Я сохраню первое предложение здесь:
v1.reserve( v1.size() + v2.size() );
v1.insert( v1.end(), v2.begin(), v2.end() );
Есть несколько причин сделать это последним способом, хотя ни один из них не достаточно силен:
- нет гарантии того, какой размер будет перераспределять вектор - например. если размер суммы 1025, он может перераспределяться до 2048 - в зависимости от реализации. Для
reserve
такой гарантии нет, но для конкретной реализации это может быть правдой. Если вы хотите найти узкое место, вам может быть разумно проверить это.
- резерв утверждает, что наши намерения понятны - оптимизация может быть более эффективной в этом случае (резерв может подготовить кэш в некоторой первоклассной реализации).
- также с
reserve
мы гарантируем С++ Standard, что будет только одно перераспределение, тогда как insert
может быть реализовано неэффективно и сделать несколько перераспределений (также что-то для тестирования с конкретной реализацией).
Ответ 2
Вероятно, лучше и проще использовать выделенный метод: vector.insert
v1.insert(v1.end(), v2.begin(), v2.end());
Как отмечает Майкл, если итераторы не являются итераторами ввода, вектор будет определять требуемый размер и скопировать прилагаемые данные с одной линейной сложностью.
Ответ 3
Я просто быстро измерил производительность с помощью следующего кода и
v1.insert( v1.end(), v2.begin(), v2.end() );
кажется правильным выбором (как уже было сказано выше).
Тем не менее, вы найдете приведенную ниже производительность.
Тестовый код:
#include <vector>
#include <string>
#include <boost/timer/timer.hpp>
//==============================================================================
//
//==============================================================================
/// Returns a vector containing the sequence [ 0, ... , n-1 ].
inline std::vector<int> _range(const int n)
{
std::vector<int> tmp(n);
for(int i=0; i<n; i++)
tmp[i] = i;
return tmp;
}
void test_perf_vector_append()
{
const vector<int> testdata1 = _range(100000000);
const vector<int> testdata2 = _range(100000000);
vector<int> testdata;
printf("--------------------------------------------------------------\n");
printf(" METHOD: push_back()\n");
printf("--------------------------------------------------------------\n");
testdata.clear();
{ vector<int>().swap(testdata); }
testdata = testdata1;
{
boost::timer::auto_cpu_timer t;
for(size_t i=0; i<testdata2.size(); i++)
{
testdata.push_back(testdata2[i]);
}
}
printf("--------------------------------------------------------------\n");
printf(" METHOD: reserve() + push_back()\n");
printf("--------------------------------------------------------------\n");
testdata.clear();
{ vector<int>().swap(testdata); }
testdata = testdata1;
{
boost::timer::auto_cpu_timer t;
testdata.reserve(testdata.size() + testdata2.size());
for(size_t i=0; i<testdata2.size(); i++)
{
testdata.push_back(testdata2[i]);
}
}
printf("--------------------------------------------------------------\n");
printf(" METHOD: insert()\n");
printf("--------------------------------------------------------------\n");
testdata.clear();
{ vector<int>().swap(testdata); }
testdata = testdata1;
{
boost::timer::auto_cpu_timer t;
testdata.insert( testdata.end(), testdata2.begin(), testdata2.end() );
}
printf("--------------------------------------------------------------\n");
printf(" METHOD: reserve() + insert()\n");
printf("--------------------------------------------------------------\n");
testdata.clear();
{ vector<int>().swap(testdata); }
testdata = testdata1;
{
boost::timer::auto_cpu_timer t;
testdata.reserve( testdata.size() + testdata.size() );
testdata.insert( testdata.end(), testdata2.begin(), testdata2.end() );
}
printf("--------------------------------------------------------------\n");
printf(" METHOD: copy() + back_inserter()\n");
printf("--------------------------------------------------------------\n");
testdata.clear();
{ vector<int>().swap(testdata); }
testdata = testdata1;
{
boost::timer::auto_cpu_timer t;
testdata.reserve(testdata.size() + testdata2.size());
copy(testdata2.begin(), testdata2.end(), back_inserter(testdata));
}
printf("--------------------------------------------------------------\n");
printf(" METHOD: reserve() + copy() + back_inserter()\n");
printf("--------------------------------------------------------------\n");
testdata.clear();
{ vector<int>().swap(testdata); }
testdata = testdata1;
{
boost::timer::auto_cpu_timer t;
testdata.reserve(testdata.size() + testdata2.size());
copy(testdata2.begin(), testdata2.end(), back_inserter(testdata));
}
}
С Visual Studio 2008 SP1, x64, Release mode,/O2/LTCG вывод выглядит следующим образом:
--------------------------------------------------------------
METHOD: push_back()
--------------------------------------------------------------
0.933077s wall, 0.577204s user + 0.343202s system = 0.920406s CPU (98.6%)
--------------------------------------------------------------
METHOD: reserve() + push_back()
--------------------------------------------------------------
0.612753s wall, 0.452403s user + 0.171601s system = 0.624004s CPU (101.8%)
--------------------------------------------------------------
METHOD: insert()
--------------------------------------------------------------
0.424065s wall, 0.280802s user + 0.140401s system = 0.421203s CPU (99.3%)
--------------------------------------------------------------
METHOD: reserve() + insert()
--------------------------------------------------------------
0.637081s wall, 0.421203s user + 0.218401s system = 0.639604s CPU (100.4%)
--------------------------------------------------------------
METHOD: copy() + back_inserter()
--------------------------------------------------------------
0.743658s wall, 0.639604s user + 0.109201s system = 0.748805s CPU (100.7%)
--------------------------------------------------------------
METHOD: reserve() + copy() + back_inserter()
--------------------------------------------------------------
0.748560s wall, 0.624004s user + 0.124801s system = 0.748805s CPU (100.0%)
Ответ 4
Если вы используете Boost, вы можете загрузить версию разработки библиотеки RangeEx
Ответ 5
Если у вас есть вектор типов pod, и вам действительно нужна производительность, вы можете использовать memcpy, который должен быть быстрее, чем vector < > . insert (...):
v2.resize(v1.size() + v2.size());
memcpy((void*)&v1.front(), (void*)&v2[v1.size()], sizeof(v1.front())*v1.size());
Обновление:
Хотя я бы использовал это только в том случае, если производительность действительно нужна, код безопасен для типов контейнеров.