-2

It's encouraged to use STL over dynamic arrays but I'm curious what could be the bottleneck? MAX_PACKET_LENGTH is 32768.

---------------------------------------------------------------------------
Benchmark                                    Time           CPU Iterations
---------------------------------------------------------------------------
BM_VectorInit                             1335 ns       1193 ns     497778
BM_RawInit                                  58 ns         55 ns   10000000


static void BM_VectorInit(benchmark::State &state)
{
    for (auto _ : state) 
    {
        std::vector<char> a(MAX_PACKET_LENGTH);
    }
}

static void BM_RawInit(benchmark::State &state)
{
    for (auto _ : state) 
    {
        auto a = new char[MAX_PACKET_LENGTH];
        delete[] a;
    }
}
  • 6
    Not sure but a reasonable optimizer would remove both pieces of code. – juanchopanza May 15 '18 at 21:24
  • 1
    "*It's encouraged to use STL over dynamic arrays*" yes, this is for you the programmer and anyone else who ever has to look at your code. The price you pay for this is a little overhead. – scohe001 May 15 '18 at 21:25
  • 1
    What about `auto a = new char[MAX_PACKET_LENGTH]{};`? `vector` will zero initialize the array. – Praetorian May 15 '18 at 21:26
  • 2
    Also, the equivalent raw array version would be `auto a = new char[MAX_PACKET_LENGTH]{};` – juanchopanza May 15 '18 at 21:26
  • 1
    As @juan says, you need to actually do something with the vector/array to prevent the compiler from optimising the code away - I have a short blog article on the topic at https://latedev.wordpress.com/2011/10/15/the-joy-of-benchmarks/ –  May 15 '18 at 21:27
  • [Take a look](https://godbolt.org/g/WAWTti). `` means that with optimizations enabled, those functions do nothing at all. Benchmarking can be a tricky art. – Drew Dormann May 15 '18 at 21:31
  • Here is the [assembly generated](https://godbolt.org/g/1AZJDx) for each version-ish – Cory Kramer May 15 '18 at 21:31
  • If you ever write a naked `delete` without a *very good reason*, your code will fail my code review! – Jesper Juhl May 15 '18 at 21:35
  • What compiler, what optimization options, and what hardware, and what OS? This is not a [mcve]. `clang -O3` with `-stdlib=libc++` can often optimize away creation of a `std::vector`, but not with `libstdc++`. – Peter Cordes May 15 '18 at 23:40

2 Answers2

4

First, as @juanchopanza suggests - we can't reproduce your figures if you don't provide a proper test program; you've just given us two functions which do nothing and won't result in anything in the binary.

Anyway, it seems the overhead is due to std::vector zero-initializing the char values.

If you want to avoid that happening, or just to level the playing field for benchmarking, use a struct which wraps a char and doesn't initialize it as the vector element type, and run your tests again. I wouldn't recommend actually using such a silly struct in production code though - use the types you actually need.

And speaking of using what you need - it's perfectly ok to use:

auto buffer_ptr = std::make_unique<char[]>(MAX_PACKET_LENGTH);

and then maybe:

auto buffer = std::span<char>(raw_buffer.get(), MAX_PACKET_LENGTH);

and you can use spans almost everywhere you can use std::vectors. (Note, though, that std::span is only coming in C++20 and for now you'll need a GSL implementation).

einpoklum
  • 118,144
  • 57
  • 340
  • 684
-1

You are comparing the cost of constructing a std::vector with the cost of allocating an array on the heap. When you create a std::vector, it internally allocates an array on the heap, but there is additional overhead since the std::vector is itself an object that needs to be constructed and stored (possibly on the stack or on the heap). Therefore, it should take longer to create a std::vector than it takes to create a new char[].

That being said, there is another difference between BM_RawInit and BM_VectorInit. When you construct a std::vector with one integer argument, as in std::vector<char> a(MAX_PACKET_LENGTH), two things happen. Space will be allocated on the heap for MAX_PACKET_LENGTH items and those items will also be default constructed. On the other hand, when you do new char[MAX_PACKET_LENGTH], only allocation occurs.

For a better comparison, try to create a third benchmark that only allocates space, like this:

static void BM_VectorReserve(benchmark::State &state)
{
    for (auto _ : state) 
    {
        std::vector<char> a;
        a.reserve(MAX_PACKET_LENGTH);
    }
}

This isn't a perfect comparison, though, because a small amount of memory will be allocated on the first line where we declare a, and then when we call reserve, that initial memory will be released. After that, the std::vector will allocate enough space for MAX_PACKET_LENGTH items. In real code, this is often negligible, but for the sake of your benchmark, it would partially explain why BM_VectorReserve takes longer than BM_RawInit.

Tim Johns
  • 540
  • 3
  • 13
  • 3
    No, a vector is really not at all a wrapper around arrays. – einpoklum May 15 '18 at 21:34
  • 3
    VectorInit 1426ns, VectorReserve 229ns – Damian Kalinowski May 15 '18 at 21:36
  • @DamianKalinowski Cool, so you're seeing that VectorReserve is much closer to RawInit, but you still pay some penalty for using the STL container, rather than the raw array – Tim Johns May 15 '18 at 21:39
  • Any reason for the down votes? As the OP just showed, I provided useful feedback – Tim Johns May 15 '18 at 21:41
  • @einpoklum How would you implement a vector without dynamic arrays (or malloc)? – Tim Johns May 15 '18 at 21:42
  • @TimJohns: 1. There isn't really such a thing as "dynamic arrays" in C++. Arrays are not dynamic. The fact that arrays decay to pointers doesn't mean pointers are arrays. 2. Arrays exist on the stack. 3. `std::vector` uses new, not `malloc()`, although granted - behind the scenes it could well be `malloc()`. – einpoklum May 15 '18 at 23:26
  • @einpoklum Sorry, I used the wrong term. I meant to say "dynamically allocated array", rather than "dynamic array". I will update the post to make that clear. And actually, `std::vector` is itself a "dynamic array", according to the Wikipedia definition [here](https://en.wikipedia.org/wiki/Dynamic_array). – Tim Johns May 16 '18 at 17:36
  • @TimJohns: Ok, you can think of an `std::vector` as a "dynamic array". But C and C++ arrays, as such, are never dynamic in that way. Remember that the type of `new int[whatever]`, is `int *`, not `int[100]`. – einpoklum May 16 '18 at 17:52
  • @einpoklum Just updated the post. Hopefully it's clearer now – Tim Johns May 16 '18 at 17:56
  • @TimJohns: Still not quite there. You can't allocate arrays on the heap. Arrays live on the stack (unless they're inside another structure that gets allocated on the heap), and their size is always fixed. Also, the space for the actual `std::vector` may well be optimized away, and if it isn't it's still inconsequential compared to the initialization of the elements. – einpoklum May 16 '18 at 18:03
  • 1
    @einpoklum: I think you're being overly pedantic. It's totally normal to talk about using `malloc` to allocate an array, or to say that a function accepts an array when the args are really pointer,length and you don't care where the array lives. (At least that's the case in C, where you don't have the option of wrapping it up in an STL container). And BTW, arrays can live in static storage, too, not automatic storage. If you're going to be super-pedantic about C++ terminology, don't assume there's a stack or a heap. – Peter Cordes May 16 '18 at 18:10
  • 1
    @einpoklum You and I have a fundamental difference of opinion here. You're saying that `char a[5]` is an array, but that `char* b = new char[5]` is not an array. – Tim Johns May 16 '18 at 18:14
  • C++ calls `new type[99]` an array new expression, so using the term array is appropriate here. Think of it as creating an array on the free store and returning a pointer to the start of it. – BeeOnRope May 17 '18 at 05:55