-1

C++' vector<> guarantees an amortized constand ovehead if you append any data. For me this actually would work only if you have a 100% capacity increment if the vector<> is full; libstdc++ and libc++ have these size increments. But I had some strange behaviour with MSVC++:

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

int main()
{
    vector<size_t> vi;
    for( size_t i = 0x100000; i--; )
    {
        size_t before = vi.capacity();
        vi.emplace_back( i );
        if( double incr = ((double)vi.capacity() / (double)before - 1.0) * 100.0; !isinf( incr ) && incr )
            cout << (int)(incr + 0.5) << "%" << endl;
    }
}

MSVC++ reports a 50% size-increment. Is this really according to the standard ?

This code calculates the overhead of insertion at different size-increment percentages:

#include <iostream>
#include <cstdint>

using namespace std;
int main()
{
    auto time1M = []( double pctIncrement ) -> double
    {
        double increment = 1.0 + pctIncrement / 100.0;
        int64_t time = 0;
        constexpr size_t MAX_SIZE = 1'000'000;
        for( size_t capacity = 0, size = 0; size != 1'000'000; ++size )
            if( size == capacity )
                capacity = (size_t)(size * increment),
                capacity += capacity == size,
                time += size;
        return time / (double)MAX_SIZE;
    };
    for( double pctIncrement = 10.0; pctIncrement <= 100.0; pctIncrement += 10.0 )
        cout << pctIncrement << "%: " << time1M( pctIncrement ) << endl;
}

It prints the following:

10%: 10.1707
20%: 5.65891
30%: 4.32914
40%: 3.46261
50%: 2.09975
60%: 1.78124
70%: 2.09618
80%: 1.35964
90%: 1.91513
100%: 1.04858

A constant overhead of 1.0 can only be reached at a 100% size increment.
Any questions ?

Bonita Montero
  • 2,817
  • 9
  • 22
  • 2
    I would say 100%, 50%, or whatever x%, does not really change the complexity of being amortized constant. It only impacts the hidden time constant/coefficient. – Lingxi May 23 '22 at 05:35
  • 2
    Discussed in detail at https://cs.stackexchange.com/questions/9380/why-is-push-back-in-c-vectors-constant-amortized – Clifford May 23 '22 at 05:44
  • You might also find this of use in understanding what amortized complexity actually is. https://stackoverflow.com/questions/15079327/amortized-complexity-in-laymans-terms – Peter May 23 '22 at 05:47
  • @Lingxi: That's not true. Imagine an increment of 1 ! – Bonita Montero May 23 '22 at 05:56
  • @Peter: No, that's not the same discussion. I'm about the 50% increase of MSVC and if this is conforming. – Bonita Montero May 23 '22 at 05:57
  • @BonitaMontero `1` is a constant. I'm talking about `x%` a ratio to the current size. – Lingxi May 23 '22 at 06:17
  • @Lingxi: I addes some source that does the math for you. – Bonita Montero May 23 '22 at 06:47
  • "constant amortized time" does not specify what the constant should be, it just requires that there is one. All those values, from 1.04@100% up to 10.17@10%, are constants (that approximate the reciprocal of the incremental increase, as described in my answer). All those cases run in constant amortized time, and are acceptable according to the standard. – comingstorm May 23 '22 at 06:55
  • @comingstorm: According to what you say an increment of one would be sufficient according to the standard. But the number of copies per append grows lineary with the size then and the overall time is cubic to the number of appends - whereas with an increase of 100% the overhead is constant and the overall time is appends * log2( appends ). – Bonita Montero May 23 '22 at 07:04
  • No, it is not. As I (and others) have previously mentioned, an increment of one is not a fixed ratio, and therefore is not sufficient to yield a constant amortized cost. However, any fixed-ratio increase will. – comingstorm May 23 '22 at 07:10
  • @comingstorm: That doesn't matter. I only wanted to give an _example_ of that everyhting unlike 100% doesn't satisfy the constraints of constant insertion overhead. – Bonita Montero May 23 '22 at 07:13
  • It doesn't matter that the example I've given is not practically relevant. I only wanted to say why everything different than a 100% increase doesn't guarantee amortized constant overhead on appending. The second program above calculates this overhead according to a increase percentage and you get only an average overhead of one copy-operation per append if the increase is 100%. – Bonita Montero May 23 '22 at 09:03

1 Answers1

4

I believe the standard specifies amortized constant time, not any particular increase. Any fixed percentage increase will still give you constant amortized performance for push_back(). The MSVC++ library is trading a larger constant factor for more efficient memory usage.

For a 100% increase, a resize goes from N to 2N capacity. So, N moves of existing elements can be amortized among the N additional elements, for an amortized overhead of N/N = 1 move per allocation.

For a 50% increase, a resize goes from N to 1.5N capacity. So, N moves of existing elements can be amortized among the 0.5N additional elements, for an amortized overhead of N/(0.5N) = 2 moves per allocation.

More generally, the amortized cost should vary as the reciprocal of the fixed incremental resize factor: a sequence of push_back() calls requires (1 / incremental_resize_ratio) amortized old-element moves per push-back allocation.

comingstorm
  • 25,557
  • 3
  • 43
  • 67
  • If that would be true you would also get an amortized constant overhead if the capacity increase of 1. But with 1 the overhead would grow cubic. – Bonita Montero May 23 '22 at 05:58
  • "capacity increase of 1" is not a percentage increase. – comingstorm May 23 '22 at 06:04
  • Edit above, which may clarify this issue – comingstorm May 23 '22 at 06:08
  • Of course the standard doesn't specify a certain increase. But an amortized constant overhead is only possible at a 100% capacity increase. – Bonita Montero May 23 '22 at 06:16
  • I'm afraid you're mistaken about this. Do you have a problem with my analysis above? – comingstorm May 23 '22 at 06:18
  • I didn't really do the calculation. Intuition tells me any x% will just do without doubt. – Lingxi May 23 '22 at 06:20
  • @Look at my second source I've added. – Bonita Montero May 23 '22 at 06:44
  • @BonitaMontero , amortized complexity guarantees that you have *constant overhead* instead of *constant overhead which it's value is 1*. No doubt that there is a higher constant overhead when the increasing percentage is lower. – Louis Go May 23 '22 at 07:05
  • 1
    I have added a comment to your question, but I will elaborate here. Amortized analysis is an asymptotic analysis. Asymptotic analysis is intentionally insensitive to the exact constant factor: it doesn't care what the constant factor is, just that there is one. A "constant overhead of 1.0" is meaningless here; for example, you have no way to know if moving the element takes the same time as constructing it. – comingstorm May 23 '22 at 07:05
  • @comingstorm: According to what you say an increment of one would be sufficient according to the standard. But the number of copies per append grows lineary with the size then and the overall time is cubic to the number of appends - whereas with an increase of 100% the overhead is constant and the overall time is appends * log2( appends ) – Bonita Montero May 23 '22 at 07:07
  • No: for a fixed-ratio size increase, the number of copies per append remains constant with size. The total time is O(appends); dividing by the number of appends is what gets you O(1) amortized time. Where did this factor of log2 come from? If the total time were O(appends * log(appends)), then the amortized time would be O(log N) – comingstorm May 23 '22 at 14:15