-2

I am trying to speed vector::push_back when capacity cant be predicted

When reserve is available a vector push_back writes the new element at the end of the container, and then the end marker is moved. After all reserve is used, push_back may trigger reallocation which is a slow process.
To speed this up, reserve is regenerated for several coming push_back without reallocation when empty. How do you think this code assist in achieving that goal ?

#ifndef __VECTOR_HPP
#define __VECTOR_HPP
#include <exception>
#include "Concept.hpp" //Concept::RESA constant
#include <vector>
template <typename T>
class Vector : public std::vector<T> {
public :
  void push_back (T t) {
    if (std::vector<T>::size () == std::vector<T>::capacity ()) {
      std::vector<T>::reserve ((size_t) Concept::RESA);
    }
    std::vector<T>::push_back (t);
  }
};
#endif

Test program :

#include "Vector.hpp"

int main (int argc, char* argv []) {
  {
    std::vector<size_t> v0;
    clock_t t (clock ());
    size_t duration (0);
    for (size_t i (0); i != 10000000; i++) {
      v0.push_back (i);
    }
    duration = (size_t) (clock () -t);
    std::cout << "duration old push_back == " << duration << " ticks" << std::endl;
  }
  {
    size_t duration (0);
    Vector<size_t> v1;
    clock_t t (clock ());
    for (size_t i (0); i != 10000000; i++) {
      v1.push_back (i);
    }
    duration = (size_t) (clock () -t    );
    std::cout << "duration new push_back == " << duration << " ticks" << std::endl;
  }
}

Results :

with a Concept::RESA == 8192, and applying suggestions, here are the results on a Lenovo ThinkCentre icore5 (Linux Debian, g++) :

duration old push_back == 105317 ticks

duration new push_back == 87156 ticks

Saint-Martin
  • 306
  • 3
  • 16
  • 2
    What exactly are you trying to achieve with this code? – iehrlich Jun 02 '17 at 10:39
  • 1
    What is the problem statement? – Passer By Jun 02 '17 at 10:51
  • As explained in title : speeding push_back to a vector after all reserve is used – Saint-Martin Jun 02 '17 at 10:52
  • I'll ask in a slightly different way: How do *you* think this code does, or perhaps does *not*, assist in achieving that goal ? Frankly I don't see how this is any better, and in-fact is likely worse, than simply putting in a `val_.reserve(val_.capacity() + Concept::RESA)` if you know size == capacity prior to an insert. – WhozCraig Jun 02 '17 at 11:00
  • 2
    @WhozCraig calling reserve directly with constant increments would make insertion O(n^2). By doing insert he still gets exponential growth because it is handled by std::vector. But that does not change the fact that this code is more than pointless and does something else than he thinks it does. – Sopel Jun 02 '17 at 11:37
  • 1
    I tried to fix the question, but rolled back when I realized the poor wording is matched by the poor understanding of `std::vector`. Fixing the question makes it pointless. In particular, while the second sentence talks about "reserve is available", this does _not_ mean `vector::reserve`. It probably means `vector::capacity() greater than vector::size()`. – MSalters Jun 02 '17 at 12:02
  • Can you explain a little more about what `val_.insert (val_.end (), resa_.begin (), resa_.end ());` is really supposed to be achieving? I'm having trouble understanding how it helps you be more efficient. – AndyG Jun 02 '17 at 12:06
  • @AndyG coupled with `erase` it's used as a poor man's `reserve` here :) – Ap31 Jun 02 '17 at 12:07
  • 1
    @Ap31: I think I see what you mean. I suppose the resulting comment for the OP is that the resulting reallocation for vector will not be the distance between `resa_.begin()` and `resa_.end()`, but whatever the `vector`'s reallocation strategy is (likely a simple doubling of the underlying buffer) – AndyG Jun 02 '17 at 12:28

2 Answers2

3

Indeed push_back may trigger reallocation, which is a slow process.
It will not do so on every single push_back though, instead it will reserve exponentially more memory each time, so explicit reserve only makes sense if you have a good approximation of resulting vector size beforehand.

In other words, std::vector already takes care of what you are suggesting in your code.

Another point: there is a reserve method that serves the purpose much better than inserting and erasing elements, most notably it does not create and destroy actual objects.

Ironically as @Sopel mentioned replacing insert/erase with reserve in your class would disable vector's growth amortization, making your code a nice example of several mistakes (somewhat) cancelling each other.

Ap31
  • 3,244
  • 1
  • 18
  • 25
  • If the vector already "takes care of what my code suggest", how do you explain the speed improved by 30% ? – Saint-Martin Jun 03 '17 at 10:53
  • @Saint-Martin perhaps by lack of proper measuring - you didn't mention your OS, compiler, compiler flags, it's not clear what your `clock()` does, there's no warmup, the measurements are sequential => asymmetric etc. – Ap31 Jun 03 '17 at 14:16
  • Also my measurements (same source, MSVC14 -O2) show no difference – Ap31 Jun 03 '17 at 14:31
2

There are several issues with your code.

  • You're essentially defining a type very similar to std::vector with std::vector as its only member. Why not using a std::vector in the first place?
  • Your push_back() function is just terrible. Let me first explain what std::vector<>::push_back() actually does.

    1. if size()<capacity(): it just copies the new element at the end of the block and increments the end marker. This is the most common situation.
    2. only if size()==capacity(), re-allocation is needed and

      1. it allocates a new block of memory, typically twice the current capacity
      2. it moves all data to the begin of the new block
      3. it de-allocates the old block of memory
      4. it finally constructs a new element at the end of the data

    Let's now see what your

    void push_back (const T& t) {
      if (val_.size () == val_.capacity ()) {
        val_.insert (val_.end (), resa_.begin (), resa_.end ());
        auto i = val_.end();
        i -= (size_t) Concept::RESA;
        val_.erase (i, val_.end ());
      }
      val_.push_back (t);
    }
    

    does if val_.size()==val_.capacity():

    1. it insert()s default-constructed element at val_.end(). To this end, std::vector::insert() does the following:
      1. it allocates a new block of memory, sufficiently large to hold the old data plus the data to be inserted, but possibly larger.
      2. it moves all data to the begin of the new block
      3. it de-allocates the old block of memory
      4. it copies the elements to be inserted at the end of the old data
    2. it the destructs all the newly inserted elements.
    3. if finally constructs a new element at the end of the data (which needs no re-allocation).

    Thus, your function too requires re-allocation about as frequent as plain std::push_back() and completely unnecessarily copy-constructs and then destroys a whole chunk of elements. There is no way of avoiding re-allocation when you want contiguous memory layout (as promised by std::vector) and don't know the final size in advance. If either of these requirements can be dropped, you can avoid re-allocation: either by std::vector<>::reserve() or by using a container with non-contiguous memory, such as std::deque.

Walter
  • 44,150
  • 20
  • 113
  • 196
  • My Vector dont avoid reallocations : its just grouping them. A vector is faster than a deque. – Saint-Martin Jun 02 '17 at 12:43
  • @Saint-Martin: Can you elaborate on what you mean about how your vector is grouping its reallocations? – AndyG Jun 02 '17 at 13:00
  • @Saint-Martin *A vector is faster than a deque* depends for what! If you have lots of `push_back()`, `insert()`, or even `push_front()`, `std::deque` is faster ... – Walter Jun 02 '17 at 15:07
  • @Saint-Martin Your code does the re-allocation about as often as plain `std::vector<>::push_back()`. In particular, there no *grouping* and no benefit. – Walter Jun 02 '17 at 15:16
  • check test result. Not so sure deque is the fastest for push_back (a vector having a reserve should be faster). Is it necessary to redo the test and compare Vector to deque ? – Saint-Martin Jun 03 '17 at 15:07