programing

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

javamemo 2023. 5. 20. 00:24
반응형

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

저는 항상 일반적인 지혜라고 생각해왔습니다.std::vector" 열로표 현된다배▁bl, blah blah blah blah.오늘 제가 내려가서 테스트를 해봤는데 그렇지 않은 것 같습니다.

다음은 몇 가지 테스트 결과입니다.

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에서 릴리스 모드를 사용하고 있습니다.


Visual C++에서,#define _SECURE_SCL 0감소합니다.UseVector반으로 줄였습니다(4초로 단축).이건 정말 거대해, IMO.

다음을 사용합니다.

g++ -O3 Time.cpp -I <이부스트마>
./a.아웃
2 (2.196 초 안에 완료됨)
4.412는 4.412라는 이름을 가지고 있습니다.
UseVectorPushBack은 8.017초 만에 완료되었습니다.
이 14되었습니다. 이든것이모다 14.626초에습니었완되료만다▁the니습▁in▁whole었▁completed▁thing되완료▁14이에만.

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

그러나 코드를 좀 더 자세히 살펴보면 벡터를 두 번, 배열을 한 번만 실행할 수 있기 때문에 이러한 현상이 예상됩니다.참고: 사용자가resize()메모리를 할당할 뿐만 아니라 벡터를 통해 실행되고 각 멤버의 생성자를 호출하는 벡터.

벡터가 각 개체를 한 번만 초기화하도록 코드를 약간 재정렬:

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

이제 동일한 타이밍을 다시 수행합니다.

g++ -O3 Time.cpp -I <이부스트마>
./a.아웃
2.216초 만에 벡터 사용 완료

이제 벡터의 성능이 배열보다 약간 저하되었습니다.IMO 이 차이는 미미하며 검정과 관련되지 않은 많은 것들로 인해 발생할 수 있습니다.

또한 Pixel 개체를 올바르게 초기화/파괴하고 있지 않다는 점도 고려합니다.UseArrray()생성자/파괴자 둘 다 호출되지 않는 메서드(이 간단한 클래스에서는 문제가 되지 않을 수 있지만 포인터 또는 포인터가 있는 멤버)는 문제를 일으킬 수 있습니다.

좋은 질문입니다.벡터 테스트 속도를 높일 수 있는 간단한 해결책을 찾을 수 있을 것으로 기대하고 왔습니다.그것은 제가 기대했던 것처럼 잘 되지 않았습니다!

최적화는 도움이 되지만 충분하지 않습니다.최적화 기능을 사용해도 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[]를 사용합니다.

가 .malloc()new[]UseArray에서 개체를 생성할 수 있습니다.그리고 개별 필드 할당에서 픽셀 인스턴스 할당으로 변경합니다.을 아, 그고내루변프이수름다변다니경합로으음을의부.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가 모든 객체의 구성을 최적화할 수 있습니다.픽셀을 다음으로 변경하면 어떻게 됩니까?

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

아니요, 다르지 않습니다.적어도 더 느리지는 않습니다.저는 이것이 제가 사용했던 #2와 비슷한 성능을 가질 것이라고 생각했습니다.Pixel&언급.

결론

일부 스마트 쿠키가 벡터 루프를 어레이 루프만큼 빠르게 만드는 방법을 알아낸다 하더라도, 이것은 기본 동작에 대해 잘 말해주지 않습니다.std::vector컴파일러가 모든 C++ness를 최적화하고 STL 컨테이너를 원시 어레이만큼 빠르게 만들 만큼 충분히 똑똑하다는 것은 여기까지입니다.

가 no-op 기본 할 때 기본 생성자 호출을 할 수 입니다.std::vector일반을 사용하는 경우new[]그것은 그것들을 아주 잘 최적화합니다.하지만 그렇지 않습니다.std::vector당신이 코드를 다시 작성하여 이 주변의 만트라에 맞서는 생성자 호출을 제거할 수 있다고 해도: "컴파일러는 당신보다 똑똑합니다.STL은 플레인 C만큼 빠릅니다.걱정하지 마세요."

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

이 시점에서 많은 프로그래머들이 C++11에서 작업할 것입니다.그리고 C++11에서 작성된 OP의 코드는 똑같이 빠르게 실행됩니다.UseArray또는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_backchecking도 때문에 fence post checking보다 느린 상태로 됩니다.malloc판본

라이브 예제(타이머를 다음으로 교체하기도 했습니다.chrono::high_resolution_clock).

일반적으로 초기화가 필요한 구조를 가지고 있지만 버퍼를 확장한 후 이를 처리하려면 사용자 정의를 사용하여 이 작업을 수행할 수 있습니다.std::vector할당자만약 당신이 원한다면 그것을 좀 더 일반적인 것으로 옮기세요.std::vector신중하게 사용할 것으로 생각합니다.allocator_traits 의우 순위선iding의 우선 .==잘 될 수도 있지만 확신할 수 없습니다.

공정하게 말하자면, 제가 당신의 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

하지만 약간의 변화로 상황은 바뀌었습니다.

픽셀.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가 원시 배열을 능가합니다.

를 통한 std::처럼.벡터와 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는 액세스를 위한 원시 배열과 비슷하지만 벡터/배열을 여러 번 만들고 삭제해야 하는 경우 복잡한 개체를 만드는 것이 요소의 생성자가 인라인이 아닐 때 단순 배열을 만드는 것보다 시간이 더 많이 걸린다는 것입니다.저는 이것이 별로 놀랍지 않다고 생각합니다.

제가 처음 당신의 코드를 봤을 때는 공정한 비교가 아니었습니다. 저는 분명히 당신이 사과와 사과를 비교하는 것이 아니라고 생각했습니다.그래서 저는 모든 테스트에서 생성자와 파괴자를 호출하고 비교하자고 생각했습니다.

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 구현은 알 수 없는 이유로 더딘 것이 아니라, 여러분이 요청한 대로 정확히 수행할 뿐입니다. 여러분이 더 잘 알기를 바랍니다.

사용해 보십시오.

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배열보다 훨씬 더 일반적인 도구입니다.그리고 그것은 여러분이 그것을 어떻게 사용하는지 고려해야 한다는 것을 의미합니다.어레이에는 없는 기능을 제공하여 다양한 방식으로 사용할 수 있습니다.또한 "잘못된" 용도로 사용하면 오버헤드가 많이 발생하지만 올바르게 사용하면 오버헤드가 거의 없는 데이터 구조입니다.이 경우 벡터를 개별적으로 초기화한 다음(모든 요소가 기본 cctor를 호출하도록 함) 각 요소를 올바른 값으로 개별적으로 덮어쓰는 것이 문제입니다.배열로 동일한 작업을 수행할 때보다 컴파일러가 최적화하는 것이 훨씬 어렵습니다.이것이 바로 벡터가 정확하게 초기화할 수 있는 생성자를 제공하는 이유입니다.N이 가가있요인 X.

그리고 그것을 사용하면, 벡터는 배열만큼 빠릅니다.

아니요, 당신은 성과 신화를 깨지 않았습니다.하지만 당신은 벡터를 최적으로 사용할 때만 그것이 사실이라는 것을 보여주었고, 이것도 꽤 좋은 점입니다.:)

긍정적인 측면에서는 가장 단순한 사용법이 가장 빠른 것으로 나타났습니다.성능 차이를 제거하지 못하는 수정 및 최적화의 산더미를 포함하는 제 코드 조각(단일 줄)과 John Kugelman의 답변을 비교해 보면,vector꽤나 교묘하게 설계된 것 같습니다.어레이와 동일한 속도를 얻기 위해 후프를 통과할 필요가 없습니다.반대로, 당신은 가능한 가장 간단한 해결책을 사용해야 합니다.

선택한 반복기를 비활성화하고 릴리스 모드에서 빌드를 시도합니다.성능에 큰 차이가 있어서는 안 됩니다.

및 기타), GNU의 STL (및 기타)vector<T>(n) 객체 default를 생성합니다.T()할 것입니다. 가비지의 의 컴파는빈최것에 의해 . 그러나 이제 객체에 대해 예약된 메모리 주소에 발생한 모든 가비지의 복사본은 STL에 의해 만들어집니다.__uninitialized_fill_n_aux이는 해당 개체의 복사본을 벡터의 기본값으로 채우는 루프입니다.그래서 "나의" STL은 루프 생성이 아니라 루프/복사를 생성하는 것입니다.반직관적이지만 바로 이 점에 대한 최근 스택 오버플로 질문에 대해 언급하면서 기억했어야 했습니다. 구조/복사는 참조 카운트된 객체 등에 대해 더 효율적일 수 있습니다.

그래서:

vector<T> x(n);

또는

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

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

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

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

이는 보다 명확하고 직접적인 구현과는 대조적일 수 있습니다.

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

우리는 컴파일러가 최적화될 것이라고 기대할 수 있습니다.

벡터 동작의 이러한 측면에 대한 정당성에 대해 조금 더 명확하게 설명하기 위해 다음을 고려합니다.

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

동일한 데이터를 참조하는 10000개의 독립 개체와 10000개의 독립 개체를 만든다면 분명 큰 차이가 있습니다.일상적인 C++ 사용자를 실수로 그렇게 비싼 일을 하지 않도록 보호하는 이점이 최적화하기 어려운 복사 구성의 매우 작은 실제 비용을 능가한다는 합리적인 주장이 있습니다.

원본 답변(참고용/댓글 이해용):어림도 없어요.벡터는 배열만큼 빠릅니다. 적어도 공간을 현명하게 예약한다면 말이죠...

Martin York의 답변은 초기화 문제를 은폐하려는 시도처럼 보이기 때문에 신경이 쓰입니다.그러나 중복된 기본 구성을 성능 문제의 원인으로 식별한 것은 맞습니다.

[EDIT: Martin의 답변은 더 이상 기본 생성자를 변경할 것을 제안하지 않습니다.]

버전을 수 .vector<Pixel>◦검사기:

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

이는 일반적인 경우인 상수 값으로 초기화하려는 경우에 사용할 수 있습니다.그러나 더 일반적인 문제는 다음과 같습니다.상수 값보다 복잡한 값으로 어떻게 효율적으로 초기화할 수 있습니까?

이를 위해 다음을 사용할 수 있습니다.back_insert_iterator반복기 어댑터입니다.다음은 다음과 같은 벡터를 사용한 예제입니다.ints, 비록 일반적인 생각이 잘 먹히긴 하지만.Pixels:

#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의 람다가 이를 훨씬 더 좋게 만듭니다). A보다 빠르지 않을 것이라고 예상합니다.malloc()- 비합니다. - STL 버 전 비 하 구 수 하 기 행 때 에 예 다 근 문 것 니 합 상 로 으 할 접 만 성 의 나 대 해 요 에 소 기 반 을 으 지 하 로 만 각 ▁- , , ▁close 비 ▁st ▁but 다 ▁i - ▁non ▁it l 니 합 ▁be ▁will 예 ▁it

벡터는 추가적으로 픽셀 생성자를 호출합니다.

각각은 당신이 타이밍을 맞춰 거의 백만 개의 CT를 찍고 있습니다.

편집: 그리고 바깥쪽 1...1,000 루프, 그러니까 10억 개의 cctor 호출을 하라!

편집 2: UseArray 사례의 분해를 보는 것은 흥미로울 것입니다.옵티마이저는 CPU를 굽는 것 외에는 다른 효과가 없기 때문에 전체를 최적화할 수 있습니다.

다음과 같은 방법이 있습니다.push_back벡터 작업의 방법:

  1. 벡터는 초기화될 때 X개의 공간을 할당합니다.
  2. 아래와 같이 현재 기본 배열에 해당 항목에 대한 공간이 있는지 확인합니다.
  3. 이것은 push_back 호출의 항목을 복사합니다.

에 전화한 에.push_backX개 항목:

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

않다면. 만약 그렇지 않다면.reserving우주는 확실히 더 느릴 것입니다.게다가, 아이템을 복사하는 데 비용이 많이 든다면, 그렇게 '밀어내기'하는 것은 당신을 산 채로 잡아먹을 것입니다.

에 대해서는vector다른 사람들의 의견에 동의해야 할 것 같습니다.릴리스에서 실행하고 최적화 기능을 설정한 후 몇 개의 플래그를 추가하여 Microsoft의 친절한 사용자가 #@%$^에 만족하지 않도록 하십시오.

크기를 조정할 필요가 없으면 Boost를 사용합니다.배열.

일부 프로파일러 데이터(픽셀이 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;
    }

이것은 배열과 동일한 성능을 가지고 있습니다.

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

OS는 Windows 7 64비트(노트북이 있는 OS)입니다.

컴파일러는 MinGW 4.6.1입니다.

IDE는 코드::블록.

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

그 결과들

O2 최적화

2.841초 만에 어레이 사용 완료

2.548초 만에 벡터 사용 완료

UseVectorPushBack은 11.95초 만에 완료되었습니다.

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

시스템 일시 중지

O3 최적화

어레이 사용이 1.452초 만에 완료

2.514초 만에 벡터 사용 완료

UseVectorPushBack은 12.967초 만에 완료됩니다.

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

O3 최적화 시 벡터의 성능이 더 떨어지는 것 같습니다.

루프를 다음으로 변경하는 경우

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

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

할당된 벡터/어레이의 결과가 어디에도 사용되지 않기 때문에 최적화로 인한 컴파일러는 코드를 변경할 수 있습니다.결과:

$ 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;
}

저는 잠시 동안 제가 하고 싶었던 광범위한 테스트를 몇 가지 했습니다.같이 쓰는 게 낫겠어요.

이것은 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개의 런으로 조립됩니다.
  • 에 저는처음테수행다니습했를스트에▁with다▁tests니로 테스트를 진행했습니다.std::sort()설명된 내용을 볼 수 있지만 중요한 상대적 차이가 없기 때문에 나중에 제거했습니다.

나의 결론과 발언

  • 글로벌 c-style 배열이 힙 c-style 배열과 거의 같은 시간이 소요된다는 점에 주목합니다.
  • 모든 테스트 중에서 저는 놀라운 안정성을 발견했습니다.std::array, 실행 의 시간 이 심했습니다. 비교 , std:: 데이터 구조는
  • O3 최적화는 주목할 만한 시간 차이를 보이지 않았습니다.
  • g++에서 Windows cl(no -O2) 및(Win/Linux no -O2, no -march=native)에서 최적화를 제거하면 시간이 크게 증가합니다.특히 std:: 데이터 구조의 경우.에서 g으로 더 이지만 MSVS에서는 g+++보다 더 높은 시간,std::array 없이 에서 더 를 사용할 수 . Windows에서는 C-style 어레이를 사용할 수 있습니다.
  • g++는 마이크로소프트 컴파일러보다 더 빠른 코드를 생성합니다(분명히 윈도우에서도 더 빠르게 실행됩니다).

평결

물론 이것은 최적화된 빌드를 위한 코드입니다.그리고 그 질문은 다음과 같은 것이었기 때문에.std::vector일반 어레이(최적화/비최적화)보다 느립니다.그러나 벤치마크를 수행할 때는 자연스럽게 최적화된 코드를 생성해야 합니다.

하지만 나에게 그 쇼의 스타는.std::array.

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

그런데 벡터를 사용하는 클래스에서 당신의 시각이 느려지는 것은 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;
}

코드의 동작은 벡터의 인스턴스화가 코드의 가장 긴 부분임을 보여줍니다.일단 당신이 그 병 목을 통과하면요.나머지 코드는 매우 빠르게 실행됩니다.이것은 실행 중인 스레드 수에 관계없이 사실입니다.

그건 그렇고, 완전히 미친 숫자의 포함을 무시하세요.저는 이 코드를 사용하여 프로젝트에 필요한 것들을 테스트해왔기 때문에 포함되는 것의 수가 계속 증가하고 있습니다.

저는 벡터(및 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()를 사용하여 언급한 것처럼)

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

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

저는 C++ 전문가가 아닙니다.그러나 몇 가지 실험 결과를 추가하려면 다음과 같이 하십시오.

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

기계:

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

OS:

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

여기서 내가 유일하게 이상하게 느끼는 것은 "UseFillConstructor" 성능이 "UseConstructor"와 비교된다는 것입니다.

코드:

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 최적화는 매우 중요하지만 값이 기본값으로 제공될 경우에는 큰 도움이 되지 않습니다.이것은 사실 제 수업료에 반한 것입니다.벡터 초기화 형식을 선택할 때 새로운 프로그래머에게 도움이 되기를 바랍니다.

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

#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

당신의 정확한 결과는 다양하겠지만, 이것은 제 기계에서 꽤 전형적인 것입니다.

가끔, , 가끔, 경로는으험, 가끔가, 가끔끔, 가끔제, 가,,,vector<int>보다 몇 배 더 느릴 수 있습니다.int[]한 가지 명심해야 할 것은 벡터의 벡터가 매우 다르다는 것입니다.int[][]요소들이 메모리에서 연속적이지 않을 수 있기 때문입니다., 벡터 할 수 못할 수 . 예를 들어 즉, 기, 벡의 경우에는 가 요소를 캐시하지 수 있습니다.int[][].

언급URL : https://stackoverflow.com/questions/3664272/is-stdvector-so-much-slower-than-plain-arrays

반응형