Last active
June 30, 2023 04:01
-
-
Save kebien6020/5dc10f24630cbeebe8b7fb8e5bd207dd to your computer and use it in GitHub Desktop.
Benchmark testing vector vs list for different sized elements
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Compile: | |
| // g++ -O3 -std=c++20 -lbenchmark main.cpp -o main | |
| // You need google benchmark installed to compile: https://github.com/google/benchmark | |
| // Run: | |
| // ./main | |
| #include <benchmark/benchmark.h> | |
| #include <array> | |
| #include <iterator> | |
| #include <list> | |
| #include <memory> | |
| #include <random> | |
| #include <ranges> | |
| #include <vector> | |
| using std::advance; | |
| using std::array; | |
| using std::begin; | |
| using std::list; | |
| using std::make_unique; | |
| using std::mt19937; | |
| using std::ssize; | |
| using std::uniform_int_distribution; | |
| using std::unique_ptr; | |
| using std::vector; | |
| using std::views::iota; | |
| template <int N> | |
| using Blob = array<char, N>; | |
| auto gen = mt19937{42}; // fixed seed for reproducible results | |
| auto dist = uniform_int_distribution{}; | |
| auto rand_idx(int size) -> int { | |
| using D = decltype(dist); | |
| return dist(gen, D::param_type{0, size - 1}); | |
| } | |
| template <int N> // N is size of each element | |
| static void VectorInsert(benchmark::State& state) { | |
| auto vec = vector<Blob<N>>(100); | |
| for (auto _ : state) { | |
| for (auto _ : iota(0, state.range(0))) { | |
| auto pos = begin(vec); | |
| advance(pos, rand_idx(ssize(vec))); | |
| vec.emplace(pos); // If you insert instead of emplacing you incur either an additional copy or a move | |
| // For the Blob type (array under the hood) move is exactly the same as copy | |
| } | |
| } | |
| benchmark::DoNotOptimize(vec); // Makes the compiler think that the contents of the vector are used in some way | |
| } | |
| BENCHMARK_TEMPLATE(VectorInsert, 8)->Range(8, 8 << 10); | |
| BENCHMARK_TEMPLATE(VectorInsert, 64)->Range(8, 8 << 10); | |
| BENCHMARK_TEMPLATE(VectorInsert, 512)->Range(8, 8 << 10); | |
| template <int N> | |
| static void ListInsert(benchmark::State& state) { | |
| auto lst = list<Blob<N>>(100); | |
| for (auto _ : state) { | |
| for (auto _ : iota(0, state.range(0))) { | |
| auto pos = begin(lst); | |
| advance(pos, rand_idx(ssize(lst))); // O(n) for lists, O(1) for vector | |
| lst.emplace(pos); | |
| } | |
| } | |
| benchmark::DoNotOptimize(lst); | |
| } | |
| BENCHMARK_TEMPLATE(ListInsert, 8)->Range(8, 8 << 10); | |
| BENCHMARK_TEMPLATE(ListInsert, 64)->Range(8, 8 << 10); | |
| BENCHMARK_TEMPLATE(ListInsert, 512)->Range(8, 8 << 10); | |
| template <int N> | |
| static void VecPtrInsert(benchmark::State& state) { | |
| auto vec = vector<unique_ptr<Blob<N>>>(100); | |
| for (auto _ : state) { | |
| for (auto _ : iota(0, state.range(0))) { | |
| auto pos = begin(vec); | |
| advance(pos, rand_idx(ssize(vec))); | |
| vec.emplace(pos, make_unique<Blob<N>>()); | |
| } | |
| } | |
| benchmark::DoNotOptimize(vec); | |
| } | |
| BENCHMARK_TEMPLATE(VecPtrInsert, 8)->Range(8, 8 << 10); | |
| BENCHMARK_TEMPLATE(VecPtrInsert, 64)->Range(8, 8 << 10); | |
| BENCHMARK_TEMPLATE(VecPtrInsert, 512)->Range(8, 8 << 10); | |
| BENCHMARK_MAIN(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Results
As expected, list is mostly trash even at higher sized elements. No idea what's happening with VectorInsert<512>/4096 maybe it hit bad rng with the insertion indices, VectorInsert<512>/8192 is literally more work no matter how you look at it. You might want to test with a different random seed.
What surprised me the most is that VectorInsert didn't really outperform VecPtrInsert at 8-byte elements, but maybe with a more chaotic memory allocation pattern a vector of pointers could be more unpredictable while the vector of elements maintains predictable performace, idk tho. One important aspect is that we're not really doing a linear search on the vector cases, we just pick an index an insert there. With the linear search we would have to chase each pointer in the vector up to the insertion point in order to see the contents, probably in that case the plain vector would outperform the vector of pointers.
As you might expect, both the linked list and the vector of pointers don't really care about the size of the elements, and both appear to scale more or less linearly over the amount of insertions, it's just that the linked list has a much steeper slope. The plain vector does struggle as you increase the element size.
Raw output