Now Reading
Easy Efficiency Enhancements in C++: std::vector

Easy Efficiency Enhancements in C++: std::vector

2023-03-08 14:30:37

That is the third submit within the sequence about easy efficiency
enhancements in C++. Examine the first
post
to get the
full story!

Last
time

we switched from std::map to std::unordered_map and bought 30%
efficiency enhancements. After these modifications we noticed that the
tokenize operate was a prime hotspot, principally on account of
std::vector::push_back. How can we enhance the efficiency of this
operate?

Briefly, std::vector is a dynamically allotted array whose
capability will increase as wanted. Internally, this container manages a
dynamically allotted storage buffer the place the gadgets might be
saved. When the storage is full and a brand new merchandise have to be inserted, a
new (bigger) buffer is allotted and the gadgets are moved from the outdated
buffer to the brand new.

There’s an std::vector::dimension() operate to get the variety of
components saved within the vector, and std::vector::capability() to get
the variety of components it may include with out having to
reallocate. Let’s unroll a small instance to raised perceive what’s
happening below the hood. Right here int* m_data is the interior buffer in
a vector of ints.

  1. Initially there’s nothing within the vector: m_data == nullptr,
    capability() == 0, and dimension() == 0.
  2. When the primary merchandise is inserted, m_data is assigned a
    dynamically allotted buffer of dimension 1, then the worth 10 is
    saved within the buffer.
  3. When the second worth is available in, there’s not sufficient room to retailer
    it. A buffer of dimension 2 is allotted and the content material of m_data
    is transferred into it. Then the worth 20 is saved in second
    place within the new buffer. The reminiscence initially assigned to
    m_data is freed.
  4. When the third worth is inserted, there’s once more not sufficient room
    to retailer it. A buffer of dimension 4 is allotted (assuming a progress
    issue of two) and the content material of m_data is transferred into
    it. Then the worth 30 is saved in third place within the new
    buffer. The reminiscence initially assigned to m_data is launched.
  5. Lastly a fourth worth is inserted. This time there’s sufficient
    room in m_data to retailer the worth so it’s simply appended within the
    buffer.

So right here we’ve 4 values to push within the vector and it prices us 6 calls
to the reminiscence allocator (3 malloc and three free) and three copies. If we
may allocate the reminiscence upfront it might save us many
operations. Fortunately there’s the API for it in std::vector within the
type of the std::vector::reserve. Let’s see how we are able to use it within the
earlier instance.

std::vector<int> v; // v is empty, m_data is nullptr.

v.reserve(4);       // assign a buffer for 4 ints to m_data.

v.push_back(10);    // append 10 in m_data; no allocation.
v.push_back(20);    // append 20 in m_data; no allocation.
v.push_back(30);    // append 30 in m_data; no allocation.
v.push_back(40);    // append 40 in m_data; no allocation.

It’s so much much less work. Sounds promising, isn’t it?

The end result vector in our tokenize() operate is utilized in a manneer
much like the vector within the above part: we create the vector, then
we push a brand new entry for every token from the enter. Moreover, the
value of copying the information throughout reallocation is a bit increased since we
are transferring situations of std::string, not easy ints. We
are going to use a really minor change in our operate:

 -3,6 +3,8
 std::vector<std::string> tokenize(const std::string& s)
 {
   std::vector<std::string> end result;
+  // Anticipate 4 fields or much less in our enter.
+  end result.reserve(4);
   std::string::size_type f = 0;
   std::string::size_type p = s.discover(':');

Let’s see the way it modifications the efficiency of our program.

See Also

We at the moment are saving as much as 37% of the processing time comparatively to the
preliminary implementation, and between 10 to fifteen% comparatively to the
earlier implementation! Does it change one thing to our bottlenecks?

  • 18% in std::unordered_map<std::string, std::string>::operator[].
  • 14% in std::unordered_map<std::string, std::string>::discover.
  • 10% in tokenize.
    • Largely in std::string(const std::string&) and
      std::string::substr().
  • 7% in exchange.
    • With std::string::discover as the highest offender.
  • 7% in std::getline.

Regardless that we made tokenize sooner, it’s nonetheless consuming an excellent
share of the processing time by creating substrings from the enter and
storing them within the end result vector.
We’ll see if we are able to do one thing about it in subsequent week’s submit. Keep
tuned!

Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top