Is std::vector so much slower than plain arrays?

后端 未结 22 2740
南方客
南方客 2020-11-22 12:00

I\'ve always thought it\'s the general wisdom that std::vector is \"implemented as an array,\" blah blah blah. Today I went down and tested it, and it seems to

22条回答
  •  北恋
    北恋 (楼主)
    2020-11-22 12:16

    Great question. I came in here expecting to find some simple fix that would speed the vector tests right up. That didn't work out quite like I expected!

    Optimization helps, but it's not enough. With optimization on I'm still seeing a 2X performance difference between UseArray and UseVector. Interestingly, UseVector was significantly slower than UseVectorPushBack without optimization.

    # g++ -Wall -Wextra -pedantic -o vector vector.cpp
    # ./vector
    UseArray completed in 20.68 seconds
    UseVector completed in 120.509 seconds
    UseVectorPushBack completed in 37.654 seconds
    The whole thing completed in 178.845 seconds
    # g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
    # ./vector
    UseArray completed in 3.09 seconds
    UseVector completed in 6.09 seconds
    UseVectorPushBack completed in 9.847 seconds
    The whole thing completed in 19.028 seconds
    

    Idea #1 - Use new[] instead of malloc

    I tried changing malloc() to new[] in UseArray so the objects would get constructed. And changing from individual field assignment to assigning a Pixel instance. Oh, and renaming the inner loop variable to j.

    void UseArray()
    {
        TestTimer t("UseArray");
    
        for(int i = 0; i < 1000; ++i)
        {   
            int dimension = 999;
    
            // Same speed as malloc().
            Pixel * pixels = new Pixel[dimension * dimension];
    
            for(int j = 0 ; j < dimension * dimension; ++j)
                pixels[j] = Pixel(255, 0, 0);
    
            delete[] pixels;
        }
    }
    

    Surprisingly (to me), none of those changes made any difference whatsoever. Not even the change to new[] which will default construct all of the Pixels. It seems that gcc can optimize out the default constructor calls when using new[], but not when using vector.

    Idea #2 - Remove repeated operator[] calls

    I also attempted to get rid of the triple operator[] lookup and cache the reference to pixels[j]. That actually slowed UseVector down! Oops.

    for(int j = 0; j < dimension * dimension; ++j)
    {
        // Slower than accessing pixels[j] three times.
        Pixel &pixel = pixels[j];
        pixel.r = 255;
        pixel.g = 0;
        pixel.b = 0;
    }
    
    # ./vector 
    UseArray completed in 3.226 seconds
    UseVector completed in 7.54 seconds
    UseVectorPushBack completed in 9.859 seconds
    The whole thing completed in 20.626 seconds
    

    Idea #3 - Remove constructors

    What about removing the constructors entirely? Then perhaps gcc can optimize out the construction of all of the objects when the vectors are created. What happens if we change Pixel to:

    struct Pixel
    {
        unsigned char r, g, b;
    };
    

    Result: about 10% faster. Still slower than an array. Hm.

    # ./vector 
    UseArray completed in 3.239 seconds
    UseVector completed in 5.567 seconds
    

    Idea #4 - Use iterator instead of loop index

    How about using a vector::iterator instead of a loop index?

    for (std::vector::iterator j = pixels.begin(); j != pixels.end(); ++j)
    {
        j->r = 255;
        j->g = 0;
        j->b = 0;
    }
    

    Result:

    # ./vector 
    UseArray completed in 3.264 seconds
    UseVector completed in 5.443 seconds
    

    Nope, no different. At least it's not slower. I thought this would have performance similar to #2 where I used a Pixel& reference.

    Conclusion

    Even if some smart cookie figures out how to make the vector loop as fast as the array one, this does not speak well of the default behavior of std::vector. So much for the compiler being smart enough to optimize out all the C++ness and make STL containers as fast as raw arrays.

    The bottom line is that the compiler is unable to optimize away the no-op default constructor calls when using std::vector. If you use plain new[] it optimizes them away just fine. But not with std::vector. Even if you can rewrite your code to eliminate the constructor calls that flies in face of the mantra around here: "The compiler is smarter than you. The STL is just as fast as plain C. Don't worry about it."

提交回复
热议问题