std :: vector가 일반 배열보다 훨씬 느립니까?


212

저는 항상 std::vector"배열로 구현되는" 일반적인 지혜라고 생각했습니다 . 오늘 나는 그것을 내려 가서 테스트했지만 그렇지 않은 것 같습니다.

테스트 결과는 다음과 같습니다.

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

약 3-4 배 느립니다! " vector몇 나노초 동안 속도가 느려질 수있다"라는 의견을 실제로 정당화하지는 않는다 .

그리고 내가 사용한 코드 :

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

내가 잘못하거나 뭔가를하고 있습니까? 아니면 방금이 공연 신화를 파괴 했습니까?

Visual Studio 2005 에서 릴리스 모드를 사용하고 있습니다.


에서는 카메라 C ++ , #define _SECURE_SCL 0감소 UseVector절반 (4 초로 낮추기). 이것은 정말 거대합니다.


23
디버그 모드에있을 때 일부 버전의 벡터는 추가 명령을 추가하여 배열 끝 이상으로 액세스 할 수 없는지 확인합니다. 실제 타이밍을 얻으려면 릴리스 모드에서 빌드하고 최적화를 켜야합니다.
Martin York

40
인터넷에서들은 것을 믿는 대신에 측정하는 것이 좋습니다.
P Shved

51
벡터 배열로 구현됩니다. 그것은 "전통적인 지혜"가 아니라 진실입니다. 이것이 vector범용 크기 조정 가능 어레이라는 것을 발견했습니다 . 축하합니다. 모든 범용 도구와 마찬가지로 최적이 아닌 특수한 상황을 생각해 낼 수 있습니다. 그렇기 때문에 기존의 지혜는 a부터 시작 하여 vector필요한 경우 대안을 고려해야합니다.
Dennis Zickefoose 5

37
lol, "더러운 접시를 싱크대에 던지고"와 "더러운 접시를 싱크대에 던지고 깨지지 않았는지 확인"의 속도 차이는 무엇입니까?
Imre L

9
VC2010에서 적어도 주요 차이점은 malloc ()이 resize ()보다 빠르다는 것입니다. 타이밍에서 메모리 할당을 제거하고 _ITERATOR_DEBUG_LEVEL == 0으로 컴파일하면 결과는 동일합니다.
Andreas Magnusson

답변:


260

다음을 사용하여 :

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseArray 2.196 초에 완료
4.412 초에 완료 UseVector
8.017 초에 완료 UseVectorPushBack
모든 것은이 14.626 초에 완료

따라서 배열은 벡터보다 두 배 빠릅니다.

그러나 코드를보다 자세히 살펴본 후에는 이것이 예상됩니다. 벡터를 두 번, 배열을 한 번만 실행하면 참고 : resize()벡터를 사용하면 메모리를 할당 할뿐만 아니라 벡터를 통해 실행하고 각 멤버에서 생성자를 호출합니다.

벡터가 각 객체를 한 번만 초기화하도록 코드를 약간 다시 정렬합니다.

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

이제 동일한 타이밍을 다시 수행하십시오.

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseVector가 2.216 초 안에 완료되었습니다.

벡터는 이제 어레이보다 약간 나빠질뿐입니다. IMO는이 차이가 중요하지 않으며 테스트와 관련되지 않은 많은 것들로 인해 발생할 수 있습니다.

또한 UseArrray()생성자 / 소멸자가 호출되지 않았으므로 메소드 에서 Pixel 객체를 올바르게 초기화 / 파괴하지 않는다는 것을 고려할 것입니다 (이 간단한 클래스에는 문제가되지 않지만 약간 더 복잡한 것은 (예 : 포인터 또는 멤버) 포인터가 있으면 문제가 발생합니다.


48
@ kizzx2 : reserve()대신 사용해야 합니다 resize(). 이것은 객체를위한 공간을 할당합니다 (즉 , 벡터 의 용량 을 변경합니다 ). 그러나 객체를 만들지 않습니다 (즉 , 벡터 의 크기 는 변경되지 않습니다).
James McNellis

25
1,000,000 000 개의 어레이 액세스를 수행하고 있습니다. 시차는 0.333 초입니다. 또는 어레이 액세스 당 0.000000000333의 차이. 어레이 액세스 당 0.7 개의 명령 파이프 라인 단계 인 광산과 같은 2.33GHz 프로세서를 가정합니다. 따라서 벡터는 액세스마다 하나의 추가 명령을 사용하는 것처럼 보입니다.
Martin York

3
@ 제임스 McNellis : 당신은 그냥 대체 할 수 resize()reserve()이 자신의 크기의 벡터의 내부 아이디어를 조정하지 않기 때문에 기술적으로 "끝을지나 작성"하는 요소로, 그래서 이후의 쓰기를하고 UB를 생성합니다. 실제로 모든 STL 구현은 이와 관련하여 "자체적으로"작동하지만 벡터 크기를 어떻게 다시 동기화합니까? 벡터를 채운 resize() 호출하면 기본 구성 값으로 모든 요소를 ​​덮어 쓸 수 있습니다!
j_random_hacker

8
@ j_random_hacker : 내가 정확히 말한 것이 아닙니까? reserve크기가 아닌 벡터의 용량 만 변경 한다는 것이 매우 명확하다고 생각 했습니다.
James McNellis

7
알았어 벡터 방법에는 예외 관련 크래프트가 많이있었습니다. /EHsc컴파일 스위치에 추가하면 정리가 완료되고 assign()실제로는 배열을 능가합니다. 예
Pavel Minaev

55

좋은 질문입니다. 나는 벡터 테스트를 빠르게 할 수있는 간단한 수정을 기대하기 위해 여기에왔다. 예상대로 작동하지 않았습니다!

최적화가 도움이되지만 충분하지 않습니다. 최적화를 통해 UseArray와 UseVector의 성능 차이는 여전히 2 배입니다. 흥미롭게도 UseVector는 최적화없이 UseVectorPushBack보다 상당히 느 렸습니다.

# 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

아이디어 # 1-malloc 대신 new [] 사용

객체가 생성되도록 UseArray로 변경 malloc()을 시도했습니다 new[]. 개별 필드 할당에서 Pixel 인스턴스 할당으로 변경 내부 루프 변수의 이름을로 바꿉니다 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;
    }
}

놀랍게도 (나에게), 그러한 변화들 중 어느 것도 변화를 일으키지 않았습니다. new[]기본적으로 변경되어 모든 픽셀이 구성되는 것은 아닙니다 . gcc는를 사용할 때 기본 생성자 호출을 최적화 할 수 new[]있지만 사용할 때는 최적화 하지 않는 것 같습니다 vector.

아이디어 # 2-반복되는 연산자 [] 호출 제거

또한 트리플 operator[]조회를 제거 하고에 대한 참조를 캐시 하려고 했습니다 pixels[j]. 실제로 UseVector 속도가 느려졌습니다! 죄송합니다.

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

아이디어 # 3-생성자 제거

생성자를 완전히 제거하는 것은 어떻습니까? 그러면 gcc는 벡터가 생성 될 때 모든 객체의 구성을 최적화 할 수 있습니다. Pixel을 다음과 같이 변경하면 어떻게됩니까?

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

결과 : 약 10 % 더 빠릅니다. 배열보다 여전히 느립니다. 흠.

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

아이디어 # 4-루프 인덱스 대신 반복자를 사용

어떻게 사용에 대한 vector<Pixel>::iterator대신 루프 인덱스의?

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

결과:

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

아뇨, 다르지 않습니다. 적어도 느리지 않습니다. 나는 이것이 Pixel&참조를 사용한 # 2와 비슷한 성능을 가질 것이라고 생각했다 .

결론

일부 스마트 쿠키는 벡터를 배열만큼 빠르게 루프하는 방법을 알아 내더라도의 기본 동작을 잘 설명하지 못합니다 std::vector. 컴파일러는 모든 C ++를 최적화하고 STL 컨테이너를 원시 배열만큼 빠르게 만들 수있을만큼 똑똑합니다.

결론은 컴파일러가를 사용할 때 no-op 기본 생성자 호출을 최적화 할 수 없다는 것 std::vector입니다. 일반을 사용하면 잘 new[]최적화됩니다. 그러나와는 아닙니다 std::vector. 코드를 다시 작성하여 만트라에 직면하는 생성자 호출을 제거 할 수 있다고해도 "컴파일러는 당신보다 똑똑합니다. STL은 평범한 C만큼 빠릅니다. 걱정하지 마십시오."


2
실제로 코드를 실행 해 주셔서 감사합니다. 누군가가 대중의 의견에 이의를 제기하려고 할 때 이유없이 구제되기 쉽습니다.
kizzx2

3
"컴파일러가 모든 C ++를 최적화하고 STL 컨테이너를 원시 배열만큼 빠르게 만들 수있을 정도로 똑똑했습니다." 좋은 의견. 나는이 "컴파일러가 똑똑하다"는 신화 일 뿐이라는 이론을 가지고있다. C ++ 파싱은 매우 어렵고 컴파일러는 기계 일 뿐이다.
kizzx2

3
난 몰라 물론, 그는 어레이 테스트 속도늦출 수 있었지만 벡터 속도는 향상 시키지 못했습니다 . 위에서 Pixel에서 생성자를 제거하고 간단한 구조체로 만든 곳에서 편집했지만 여전히 느 렸습니다. 간단한 유형을 사용하는 사람에게는 나쁜 소식입니다 vector<int>.
John Kugelman

2
나는 당신의 답을 두 번 올릴 수 있기를 바랍니다. 내가 생각조차 할 수없는 똑똑한 아이디어 (실제로는 효과가 없었지만)를 시도해보십시오!
kizzx2

9
C ++ 구문 분석의 복잡성 (정말 복잡하지만 예)은 최적화 품질과 관련이 없습니다. 후자는 일반적으로 구문 분석 결과가 이미 여러 번 저수준 표현으로 변환되는 단계에서 발생합니다.
Pavel Minaev

44

이것은 오래되었지만 인기있는 질문입니다.

이 시점에서 많은 프로그래머들이 C ++ 11에서 작업 할 것입니다. 그리고 C ++ 11에서 작성된 OP 코드는 UseArrayor에 대해 동일하게 빠르게 실행됩니다 UseVector.

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

근본적인 문제는 동안이었다 Pixel구조가 초기화되지 않은 한, std::vector<T>::resize( size_t, T const&=T() )기본 건설 소요 Pixel복사합니다 . 컴파일러는 초기화되지 않은 데이터를 복사하라는 요청을받지 않았으므로 실제로 복사를 수행했습니다.

C ++ 11 std::vector<T>::resize에는 두 가지 과부하가 있습니다. 첫 번째는 std::vector<T>::resize(size_t)이고 다른 하나는 std::vector<T>::resize(size_t, T const&)입니다. 이것은 resize두 번째 인수없이 호출 할 때 단순히 기본 구성이며 컴파일러는 기본 구성이 아무 것도 수행하지 않는다는 것을 인식 할 수있을 정도로 똑똑하므로 버퍼를 통과하지 않습니다.

(이동식, 구성 가능 및 복사 불가능한 유형을 처리하기 위해 추가 된 두 개의 과부하-초기화되지 않은 데이터 작업시 성능 향상은 보너스입니다).

push_back솔루션은 펜스 포스트 검사도 수행하므로 속도가 느려지므로 malloc버전 보다 느립니다 .

라이브 예 (타이머를로 교체했습니다 chrono::high_resolution_clock).

일반적으로 초기화가 필요한 구조가 있지만 버퍼를 확장 한 후에 처리하려는 경우 사용자 지정 std::vector할당기로이 작업을 수행 할 수 있습니다 . 그런 다음 더 정상적인 것으로 옮기려면 std::vector신중하게 사용 allocator_traits하고 재정의하면 ==이를 벗어날 수 있다고 확신하지만 확실하지 않습니다.


또한 여기 emplace_back대 방법을 보는 것이 흥미로울 push_back것입니다.
Daniel

1
결과를 재현 할 수 없습니다. 코드 clang++ -std=c++11 -O3를 컴파일하면 UseArray completed in 2.02e-07 seconds및이 UseVector completed in 1.3026 seconds있습니다. 나는 또한 UseVectorEmplaceBack약 버전을 추가했습니다 . 2.5 배 빠른 속도로 UseVectorPushBack.
Daniel

1
@daniel odds는 옵티마이 저가 배열 버전에서 모든 것을 제거했습니다. 마이크로 벤치 마크에서는 항상 위험합니다.
Yakk-Adam Nevraumont

4
예, 그렇습니다. 어셈블리를 보았을 때 (혹은 부족한 것입니다.) 아마도 ~ 6448514x의 차이를 고려했을 것입니다! 왜 벡터 버전이 같은 최적화를 할 수 없는지 궁금합니다. 크기를 조정하지 않고 치수로 구성하면 그렇게됩니다.
다니엘

34

공정하게 말하면 malloc 버전이라고 부르는 것처럼 C ++ 구현과 C 구현을 비교할 수 없습니다. malloc은 객체를 생성하지 않습니다-원시 메모리 만 할당합니다. 그런 다음 생성자를 호출하지 않고 해당 메모리를 객체로 취급한다는 것은 C ++이 좋지 않습니다 (아마도 유효하지 않습니다-언어 변호사에게 맡길 것입니다).

즉, malloc을 new Pixel[dimensions*dimensions]무료로 변경 하고 무료로 변경하는 delete [] pixels것은 간단한 Pixel 구현과 크게 다르지 않습니다. 내 상자의 결과는 다음과 같습니다 (E6600, 64 비트).

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

그러나 약간의 변화로 테이블이 바뀌 었습니다.

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

이 방법으로 컴파일 :

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

우리는 매우 다른 결과를 얻습니다.

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

std :: vector는 Pixel에 대해 인라인되지 않은 생성자를 사용하여 원시 배열보다 우선합니다.

std :: vector 및 std : allocator를 통한 할당의 복잡성이 너무 단순하여 효과적으로 최적화되지 않는 것으로 보입니다 new Pixel[n]. 그러나 루프 외부로 이동하여 벡터 / 배열을 한 번 생성하기 위해 몇 가지 테스트 함수를 조정하여 벡터 액세스가 아닌 할당에 문제가 있음을 알 수 있습니다.

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

결과는 다음과 같습니다.

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

이것으로부터 배울 수있는 것은 std :: vector는 액세스를 위해 원시 배열과 비교할 수 있지만 벡터 / 배열을 여러 번 작성하고 삭제해야하는 경우 복잡한 객체를 작성하면 간단한 배열을 작성하는 데 더 많은 시간이 소요됩니다 요소의 생성자가 인라인되지 않은 경우 나는 이것이 매우 놀라운 것이라고 생각하지 않습니다.


3
여전히 인라인 생성자가 있습니다-복사 생성자.
벤 Voigt

26

이것으로 시도하십시오 :

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

배열과 거의 동일한 성능을 얻습니다.

중요한 것은 vector배열보다 훨씬 일반적인 도구라는 것입니다. 그리고 그것은 당신이 그것을 어떻게 사용 하는지 고려해야한다는 것을 의미 합니다. 어레이에는없는 기능을 제공하여 다양한 방식으로 사용될 수 있습니다. 목적에 "잘못"사용하면 많은 오버 헤드가 발생하지만 올바르게 사용하면 기본적으로 오버 헤드가없는 데이터 구조입니다. 이 경우 문제는 벡터를 개별적으로 초기화 한 후 (모든 요소가 기본 ctor를 호출하도록 함) 정확한 값으로 각 요소를 개별적으로 덮어 쓰는 것입니다. 배열에서 같은 일을 할 때보 다 컴파일러가 최적화하기가 훨씬 어렵습니다. 이것이 바로 벡터가 생성자를 제공하는 이유입니다.NX.

그리고 그것을 사용할 때 벡터는 배열만큼 빠릅니다.

그래서, 당신은 성능 신화를 파열시키지 않았습니다. 그러나 벡터를 최적으로 사용하는 경우에만 해당되는 것으로 나타났습니다. :)

밝은면에서는 실제로 가장 간단한 사용법이 가장 빠릅니다. 내 코드 스 니펫 (한 줄)을 John Kugelman의 답변과 비교하면 힙과 힙의 조정 및 최적화가 포함되어 성능 차이가 여전히 제거되지는 않지만 결국에는 vector영리하게 디자인 된 것이 분명 합니다. 어레이와 동일한 속도를 얻기 위해 후프를 뛰어 넘을 필요가 없습니다. 반대로 가장 간단한 솔루션을 사용해야합니다.


1
나는 이것이 이것이 공정한 비교인지 여전히 의문이다. 내부 루프를 제거하면 동등한 배열이 단일 Pixel 객체를 구성한 다음 전체 배열에서 블리트합니다.
John Kugelman

1
Using new[]는 동일한 기본 구성을 수행 vector.resize()하지만 훨씬 빠릅니다. new[]+ 내부 루프는 + 내부 루프와 속도가 같아야 vector.resize()하지만, 그렇지는 않습니다. 거의 두 배 빠릅니다.
John Kugelman

@ 존 : 그것은 공정한 비교입니다. 원래 코드에서 배열로 할당 malloc이 때문에, 초기화 또는 구조 아무것도하지 않는 것입니다 효과적으로 그냥 내 같은 단일 패스 알고리즘 vector샘플. 그리고 new[]대답은 분명히 두 가지 패스가 필요하지만 new[]컴파일러는 추가 오버 헤드를 최적화 할 수 있습니다.이 경우에는 그렇지 않습니다 vector. 그러나 왜 차선책에서 일어나는 일이 흥미로운 지 모르겠습니다. 성능에 관심이 있다면 그런 코드를 작성하지 마십시오.
jalf

@ 존 : 흥미로운 의견. 전체 배열을 가로 질러보고 싶다면 배열이 다시 최적의 솔루션이라고 생각 vector::resize()합니다. 쓸모없는 생성자를 호출하는 데 시간을 낭비하지 않으면서도 메모리 덩어리를 줄 수 없기 때문에 배열입니다 .
kizzx2

@ kizzx2 : 그렇습니다. 배열은 일반적으로 C ++에서도 초기화됩니다. C에서는 malloc초기화를 수행하지 않지만 비 POD 유형의 C ++에서는 작동하지 않는을 사용 합니다. 따라서 일반적인 경우 C ++ 배열도 나쁩니다. 아마도이 블리 팅을 자주 수행한다면 동일한 배열 / 벡터를 재사용하지 않습니까? 그리고 그렇게하면, 처음에는 "무용 생성자"의 비용을 한 번만 지불하면됩니다. 실제 블리 팅은 결국 빠릅니다.
jalf

22

내가 당신의 코드를 처음 보았을 때 그것은 공정한 비교가 아니 었습니다. 나는 당신이 사과와 사과를 비교하지 않았다고 생각했습니다. 그래서 모든 테스트에서 생성자와 소멸자가 호출되도록했습니다. 그런 다음 비교하십시오.

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

내 생각은이 설정을 사용하면 정확히 동일 해야한다는 것 입니다. 내가 틀렸다는 것이 밝혀졌다.

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

그렇다면이 30 %의 성능 손실이 발생한 이유는 무엇입니까? STL에는 헤더에 모든 것이 포함되어 있으므로 컴파일러가 필요한 모든 것을 이해할 수 있어야합니다.

내 생각은 루프가 모든 값을 기본 생성자로 초기화하는 방법에 있다는 것입니다. 그래서 테스트를 수행했습니다.

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

결과는 내가 의심 한 것입니다.

Default Constructed: 1
Copy Constructed: 300

이것은 벡터가 복사 생성자를 사용하여 기본 생성 된 객체에서 요소를 초기화한다는 사실의 감속 원인입니다.

이는 벡터를 구성하는 동안 다음 의사 연산 순서가 발생 함을 의미합니다.

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

컴파일러에 의해 작성된 암시 적 복사 생성자로 인해 다음과 같이 확장됩니다.

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

따라서 기본값 Pixel은 초기화되지 않은 상태로 유지되고 나머지 기본값 Pixel초기화되지 않은으로 초기화 됩니다.

New[]/로 대체 상황과 비교 Delete[]:

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

그것들은 모두 초기화되지 않은 값으로 남겨졌으며 시퀀스에 대한 이중 반복이 없습니다.

이 정보로 무장하면 어떻게 테스트 할 수 있습니까? 암시 적 복사 생성자를 덮어 쓰겠습니다.

Pixel(const Pixel&) {}

그리고 결과는?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

요약하면 수백 개의 벡터를 매우 자주 만드는 경우 알고리즘을 다시 생각하십시오 .

어쨌든 STL 구현은 알려지지 않은 이유로 느리게 진행되지 않으며 요청한 내용 만 수행합니다. 더 잘 알기를 바라고 있습니다.


3
우리가 과장하고 내가 읽은 희망한다는 결론을 내릴 수, P 기본적와 우리가 (당신과 내가 여기에 다른 똑똑한 사람들이) 있었다 재미으로 판단, STL의 된 구현의 "희망"는 다소 까다로운 하나 참으로 분석 모든 소스를 암호. 어쨌든 : P
kizzx2

1
대박! VS 2013에서는 벡터가 배열보다 빠릅니다. 성능이 중요한 시스템의 경우 STL을 효과적으로 사용하려면 STL을 많이 테스트해야합니다.
rozina

7

확인 된 반복자를 비활성화 하고 릴리스 모드에서 빌드하십시오. 성능 차이가 크지 않아야합니다.


1
시도했다 #define _SECURE_SCL 0. 그것은 UseVector약 4 초 어딘가에 만들었지 만 ( gcc아래와 비슷 ) 여전히 두 배 느립니다.
kizzx2

이것은 거의 확실하게 원인입니다. Microsoft는 디버그와 릴리스 모두에 대해 기본적으로 반복자 디버깅을 제공합니다. 우리는 이것이 2003 년에서 2008 년으로 업그레이드 한 후 엄청난 속도 저하의 근본 원인이라는 것을 알았습니다. Visual Studio의 가장 치명적인 단점 중 하나입니다.
Doug T.

2
@ kizzx2 비활성화 할 다른 매크로가 있습니다 : HAS_ITERATOR_DEBUGGING 또는 이와 유사한 것.
Doug T.

@Martin과 내 답변이 보여주는 것처럼 gcc는에서 최적화를하더라도 동일한 패턴을 보여줍니다 -O3.
John Kugelman

1
@Doug :이 문서를 보면 _HAS_ITERATOR_DEBUGGING릴리스 빌드에서 비활성화 된 것 같습니다 : msdn.microsoft.com/en-us/library/aa985939(VS.80).aspx
kizzx2

4

주어진 GNU의 STL (및 기타) vector<T>(n), 기본값은 프로토 타입 객체를 구축 T()컴파일러는 빈 생성자를 최적화 할 것입니다 - -하지만 어떤 쓰레기의 사본은 STL의에 의해 촬영되어 이제 개체에 대해 예약 된 메모리 주소에 우연히 __uninitialized_fill_n_aux, 어느 해당 객체의 사본을 벡터의 기본값으로 채우는 루프를 반복합니다. 따라서 "my"STL은 루프 구성이 아니라 루프 / 복사를 구성하는 것입니다. 직관적이지 않지만,이 점에 대한 최근 stackoverflow 질문에 대해 언급 한 것처럼 기억해야합니다. 구문 / 복사본은 참조 횟수를 계산하는 객체 등에 더 효율적일 수 있습니다.

그래서:

vector<T> x(n);

또는

vector<T> x;
x.resize(n);

많은 STL 구현에서 다음과 같은 것입니다.

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

문제는 현재 세대의 컴파일러 최적화 프로그램이 temp가 초기화되지 않은 가비지라는 통찰력에서 작동하지 않고 루프 및 기본 복사 생성자 호출을 최적화하지 못하는 것입니다. 위의 글을 쓴 프로그래머는 쓰레기가 있더라도 모든 객체가 루프 후에 동일 할 것이라는 합리적인 기대를 가지고 있기 때문에 컴파일러가 이것을 절대로 최적화해서는 안된다는 것을 믿을 수 있다고 주장 할 수 있습니다 ( '동일한'/ 연산자 == 대 memcmp / operator = 등이 적용됩니다). 컴파일러는 std :: vector <>의 더 큰 컨텍스트 또는이 최적화 안전을 제안하는 이후의 데이터 사용에 대한 추가 통찰력을 기대할 수 없습니다.

이것은보다 명백하고 직접적인 구현과 대조 될 수 있습니다.

for (int i = 0; i < n; ++i)
    x[i] = T();

우리는 컴파일러가 최적화 할 것으로 기대할 수 있습니다.

벡터 동작의이 측면에 대한 정당성을 조금 더 명확하게하려면 다음을 고려하십시오.

std::vector<big_reference_counted_object> x(10000);

10000 개의 독립적 인 객체를 만드는 것과 10000 개의 동일한 데이터를 참조하는 것은 분명히 큰 차이입니다. 우연한 C ++ 사용자가 실수로 너무 비싼 것을 수행하지 못하도록 보호하는 이점은 실제적으로 최적화하기 어려운 복사 구성의 아주 작은 실제 비용보다 훨씬 비쌉니다.

원본 답변 (참조 / 의견 이해) : 기회가 없습니다. 공간을 현명하게 예약하면 벡터는 배열만큼 빠릅니다. ...


6
나는이 답변이 어느 누구에게나 유용한 곳이라고 정당화 할 수 없다. 나는 두 번 downvote 수 있기를 바랍니다.
kizzx2

-1, kizzx2에 대한 지원이 있습니다. 벡터가 제공하는 추가 기능, 우주의 법칙으로 인해 배열만큼 빠르지 않습니다.
YeenFei

토니가 빠졌어요. 인공 벤치 마크의 한 예이지만, 그것이 무엇을 목표로 하는지를 증명합니다.
Potatoswatter

장미는 녹색, 제비꽃은 주황색, 다운 보트는 쓰라린 것이지만 대답은 그들을 구걸합니다.
Pavel Minaev

3

Martin York의 대답 은 카펫 아래에서 초기화 문제를 해결하려는 것처럼 보이기 때문에 나를 귀찮게합니다. 그러나 그는 중복 기본 구성을 성능 문제의 원인으로 식별하는 것이 옳습니다.

[편집 : Martin의 대답은 더 이상 기본 생성자를 변경하지 않아도됩니다.]

당면한 문제는 확실히 vector<Pixel>ctor 의 2- 파라미터 버전을 대신 호출 할 수 있습니다 .

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

상수 값으로 초기화하려는 경우 일반적으로 작동합니다. 그러나 더 일반적인 문제는 상수 값보다 복잡한 것을 어떻게 효율적으로 초기화 할 수 있습니까?

이를 위해 back_insert_iterator반복자 어댑터 인을 사용할 수 있습니다 . 다음 int은 일반적인 아이디어 가 s에도 적용되지만 s 벡터를 사용한 예입니다 Pixel.

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

copy()또는 transform()대신 또는 을 사용할 수 있습니다 generate_n().

단점은 초기 값을 구성하는 논리를 별도의 클래스로 이동해야한다는 것입니다 .C ++ 1x의 람다는이를 훨씬 좋게 만들지 만 적절하지 않습니다. 또한 이것이 여전히 malloc()STL이 아닌 비 기반 버전 만큼 빠르지는 않을 것으로 예상하지만 각 요소에 대해 하나의 구성 만 수행하기 때문에 가까운 것으로 예상합니다.


2

벡터는 추가로 Pixel 생성자를 호출합니다.

각각은 거의 백만 번의 ctor 실행을 발생시킵니다.

편집 : 외부 1 ... 1000 루프가 있으므로 10 억 ctor를 호출하십시오!

편집 2 : UseArray 케이스의 디스 어셈블리를 보는 것이 흥미로울 것입니다. 옵티마이 저는 CPU를 굽는 것 외에 다른 효과가 없으므로 모든 것을 최적화 할 수 있습니다.


맞습니다. 그러나 문제는이 무의미한 ctor 호출을 어떻게 끌 수 있습니까? 비 STL 방식은 쉽지만 STL 방식은 어렵습니다.
j_random_hacker

1

push_back벡터 의 메소드 작동 방식은 다음과 같습니다 .

  1. 벡터는 초기화 될 때 X 공간을 할당합니다.
  2. 아래에 언급 된 것처럼 항목의 현재 기본 배열에 공간이 있는지 확인합니다.
  3. push_back 호출에서 항목의 사본을 만듭니다.

push_backX 항목을 호출 한 후 :

  1. 벡터는 kX 공간을 두 번째 배열로 재 할당합니다.
  2. 첫 번째 배열의 항목을 두 번째 배열에 복사합니다.
  3. 첫 번째 배열을 버립니다.
  4. 이제 두 번째 어레이가 kX 항목에 도달 할 때까지 스토리지로 사용합니다.

반복. reserving공간 이 없다면 분명히 느려질 것입니다. 그 이상으로, 항목을 복사하는 데 비용이 많이 든다면 'push_back'과 같이 살아 먹을 것입니다.

vectorvs 배열에 관해서 는 다른 사람들과 동의해야합니다. 릴리스에서 실행하고 최적화를 켜고 플래그를 몇 개 더 추가하면 Microsoft의 친절한 사람들이 나중에 # @ % $ ^하지 않아도됩니다.

한 가지 더, 크기를 조정할 필요가 없으면 Boost.Array를 사용하십시오.


나는 사람들이 구두로 게시 할 때 많은 코드를 읽는 것을 좋아하지 않는다는 것을 알고 있습니다. 그러나 나는 reserve내가 해야하는 것처럼 사용했습니다 .
kizzx2

미안해 내가 거기에 올려 놓은 아무것도 전혀 도움이되지 않았습니까?
wheaties

push_back일정한 시간을 상각했다. O (N) 프로세스를 설명하는 것 같습니다. (1 단계와 3 단계는 완전히 벗어난 것처럼 보입니다.) push_backOP 를 느리게 만드는 것은 재 할당이 필요한지 여부를 확인하기위한 범위 검사, 포인터 업데이트, NULL 내부 배치 검사 new및 일반적으로 익사하는 기타 작은 것들입니다. 프로그램의 실제 작업.
Potatoswatter

reserve모든에 대해 (재 할당 해야하는지 여부를) 확인해야 하기 때문에 속도가 느려질 것입니다 push_back.
Pavel Minaev

모든 좋은 점. 내가 설명하는 것은 O (N) 프로세스처럼 들리지만 그렇지 않습니다. 내가 아는 대부분의 사람들 vector은 기능이 어떻게 크기 조정 기능을 수행 하는지 이해하지 못합니다 . 그것은 단지 "마법"입니다. 여기 좀 더 설명하겠습니다.
wheaties

1

일부 프로파일 러 데이터 (픽셀은 32 비트로 정렬 됨) :

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

블라

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

에서 allocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

정렬

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

오버 헤드의 대부분은 복사 생성자에 있습니다. 예를 들어

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

어레이와 동일한 성능을 갖습니다.


2
불행히도, 당신이 준 "솔루션"이 pixels.size()깨지면
kizzx2

1
이것은 잘못되었습니다. reserve를 호출 할 수 없으며 요소를 사용할 수 있습니다. 여전히 push_back을 사용하여 항목을 추가해야합니다.
paulm

1

내 노트북은 Lenova G770 (4GB RAM)입니다.

OS는 Windows 7 64 비트 (노트북 포함)

컴파일러는 MinGW 4.6.1입니다.

IDE는 Code :: Blocks 입니다.

첫 번째 게시물의 소스 코드를 테스트합니다.

결과

O2 최적화

2.841 초 안에 UseArray 완료

2.548 초 만에 벡터 사용 완료

11.95 초 안에 UseVectorPushBack 완료

모든 것이 17.342 초 만에 완료되었습니다.

시스템 일시 정지

O3 최적화

1.452 초 안에 UseArray 완료

2.514 초 만에 벡터 사용 완료

12.967 초 안에 UseVectorPushBack 완료

모든 것이 16.937 초 안에 완료되었습니다

O3 최적화에서 벡터의 성능이 떨어지는 것처럼 보입니다.

루프를 다음으로 변경하면

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

O2와 O3에서 배열과 벡터의 속도는 거의 같습니다.


O3의 첫 번째 테스트 사례에서 malloc을 new로 변경하더라도 벡터의 성능은 여전히 ​​배열보다 느리지 만 할당 값을 (255, 0, 0)에서 (i, i, i)로 변경하면 벡터와 배열은 O2와 O3에서 거의 동일합니다. 매우 이상합니다
StereoMatching

죄송합니다, 삭제 무료로 변경하는 것을 잊었습니다. 삭제 무료로 변경 한 후 벡터와 배열의 O3에서 성능이 동일합니다. 할당자가 주된 이유는 무엇입니까?
StereoMatching 2015 년

1

할당 된 벡터 / 배열의 결과가 어디에도 사용되지 않기 때문에 최적화로 인한 더 나은 벤치 마크 (제 생각에 ...) 컴파일러는 코드를 변경할 수 있습니다. 결과 :

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

컴파일러:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

CPU :

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

그리고 코드 :

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

1

나는 잠시 동안 원했던 몇 가지 광범위한 테스트를 수행했습니다. 이것을 공유 할 수도 있습니다.

이것은 Windows 8.1 및 Ubuntu 16.04의 이중 부팅 시스템 i7-3770, 16GB Ram, x86_64입니다. 자세한 내용 및 결론은 아래를 참조하십시오. MSVS 2017 및 g ++ (Windows 및 Linux 모두)에서 테스트되었습니다.

테스트 프로그램

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

결과

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

노트

  • 평균 10 회 실행으로 조립됩니다.
  • 나는 처음 std::sort()에도 테스트를 수행 했지만 (댓글이 주석 처리 된 것을 볼 수는 있지만) 상대적으로 큰 차이가 없으므로 나중에 제거했습니다.

나의 결론과 말

  • 전역 c 스타일 배열이 힙 c 스타일 배열만큼 시간이 얼마나 걸리는지 확인
  • 모든 테스트에서 std::array연속 실행 사이의 시간 변화 가 현저히 안정된 반면 다른 표준은 특히 std :: 데이터 구조가 크게 다릅니다.
  • O3 최적화는 주목할만한 시차를 보이지 않았다
  • Windows cl (-O2 없음) 및 g ++ (Win / Linux no -O2, no -march = native)에서 최적화를 제거하면 시간이 크게 증가합니다. 특히 std :: data 구조체의 경우. g ++보다 MSVS에서 전반적으로 더 높은 시간이지만 std::array최적화없이 Windows에서 더 빠른 c 스타일 배열
  • g ++는 Microsoft의 컴파일러보다 더 빠른 코드를 생성합니다 (분명히 Windows에서도 더 빨리 실행됩니다).

평결

물론 이것은 최적화 된 빌드를위한 코드입니다. 그리고 질문은 std::vector그때부터 그렇습니다! 그렇습니다! 일반 배열보다 느립니다 (최적화 / 최적화되지 않음). 그러나 벤치 마크를 수행 할 때는 자연스럽게 최적화 된 코드를 생성하려고합니다.

나를위한 쇼의 스타는 그렇다 std::array.


0

올바른 옵션을 사용하면 벡터와 배열이 동일한 asm을 생성 할 수 있습니다 . 이 경우 동일한 실행 파일을 어느 쪽이든 얻을 수 있기 때문에 물론 동일한 속도입니다.


1
이 경우 동일한 어셈블리를 생성하지 않는 것 같습니다. 특히 벡터를 사용하여 생성자에 대한 호출을 억제 할 방법이없는 것 같습니다. 해당 문제에 대한 답변을 여기에서 참조 할 수 있습니다 (긴 읽기이지만 당신이 증명 한 링크의 단순 테스트 사례 이외의 경우에 성능 차이가있는 이유를 설명합니다) (실제로, 방법이있는 것 같습니다- -제안한대로 커스텀 STL 할당자를 작성 개인적으로, malloc을 사용하는 것보다 불필요하게 더 많은 작업을 발견했습니다)
kizzx2

1
@ kizzx2 : 구성 되지 않은 객체 를 사용하려면 그러한 길이로 가야한다는 것이 좋은 것입니다. 시간의 99 % (심하게 과소 평가 할 수 있음) 때문입니다. 다른 답변을 읽었으며 특정 상황을 해결하지 못하고 (필요하지 않고 다른 답변이 정확함) 알고 있지만 벡터와 배열이 정확히 동일하게 작동하는 방법에 대한이 예제를 제공하고 싶었습니다.

@Roger : 훌륭합니다! 링크 주셔서 감사합니다
kizzx2

0

그런데 벡터를 사용하는 클래스에서 보는 속도가 느려지면 int와 같은 표준 유형에서도 발생합니다. 다음은 멀티 스레드 코드입니다.

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

코드의 동작은 벡터의 인스턴스화가 코드의 가장 긴 부분임을 나타냅니다. 병목을 뚫고 나면 나머지 코드는 매우 빠르게 실행됩니다. 실행중인 스레드 수에 관계없이 마찬가지입니다.

그건 그렇고 절대적으로 미친 수의 포함을 무시하십시오. 나는이 코드를 사용하여 프로젝트를 테스트하기 때문에 포함 수가 계속 증가하고 있습니다.


0

벡터 (및 smart_ptr)는 원시 배열 (및 원시 포인터) 위에 얇은 계층 추가라고 언급하고 싶습니다. 실제로 연속 메모리에서 벡터의 액세스 시간은 배열보다 빠릅니다. 다음 코드는 초기화 및 액세스 벡터 및 배열의 ​​결과를 보여줍니다.

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

출력은 다음과 같습니다.

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

따라서 올바르게 사용하면 속도는 거의 같습니다. (reserve () 또는 resize ()를 사용하여 언급 한 다른 사람들).


0

vector :: resize ()는 일반 메모리 할당 (malloc)보다 훨씬 많은 처리를 수행하기 때문입니다.

복사 생성자에 중단 점을 넣으십시오 (중단 점을 지정할 수 있도록 정의하십시오). 추가 처리 시간이 걸립니다.


0

나는 C ++의 전문가가 아니라고 말해야합니다. 그러나 몇 가지 실험 결과를 추가하려면 다음을 수행하십시오.

컴파일 : gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp

기계:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

운영체제 :

2.6.32-642.13.1.el6.x86_64

산출:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

여기서 이상하게 생각되는 것은 "UseConstructor"와 비교하여 "UseFillConstructor"성능입니다.

코드:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

따라서 제공된 "값"을 추가하면 성능이 상당히 느려집니다. 여러 번의 복사 복사 생성자 때문이라고 생각합니다. 그러나...

엮다:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

산출:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

따라서이 경우 gcc 최적화는 매우 중요하지만 값이 기본값으로 제공되는 경우에는 도움이되지 않습니다. 이것은 실제로 내 수업료에 위배됩니다. 바라건대 어떤 벡터 초기화 형식을 선택할 때 새로운 프로그래머에게 도움이되기를 바랍니다.


0

컴파일러 플래그에 의존하는 것 같습니다. 벤치 마크 코드는 다음과 같습니다.

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

다른 최적화 플래그는 다른 답변을 제공합니다.

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

정확한 결과는 다양하지만 내 컴퓨터에서는 매우 일반적입니다.


0

내 경험상 때로는 때로는 때로는 vector<int>여러 번 느려질 수 있습니다 int[]. 명심해야 할 것은 벡터의 벡터가 매우 다르다는 것 int[][]입니다. 요소가 메모리에서 연속적이지 않기 때문에. 즉, 기본 벡터 내부에서 다른 벡터의 크기를 조정할 수 있지만의 경우뿐만 아니라 CPU도 요소를 캐시하지 못할 수 있습니다 int[][].

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.