programing

C++에서 어레이 또는 std:: 벡터를 사용하면 성능 차이가 어떻게 됩니까?

lovejava 2023. 7. 18. 21:22

C++에서 어레이 또는 std:: 벡터를 사용하면 성능 차이가 어떻게 됩니까?

C++ 과정에서는 새로운 프로젝트에 C++ 어레이를 더 이상 사용하지 말 것을 제안합니다.제가 알기로는 스트라우스 그룹은 배열을 사용하지 말 것을 제안합니다.하지만 성능에 상당한 차이가 있습니까?

를 C++와 함께 new(즉, 동적 배열을 사용하는 것)은 피해야 합니다.크기를 계속 파악해야 하는 문제가 있고, 수동으로 삭제하고 온갖 살림을 다 해야 합니다.

범위 검사가 없기 때문에 스택에서 어레이를 사용하는 것도 권장되지 않으며 어레이를 전달하면 크기(어레이에서 포인터로 변환)에 대한 정보가 손실됩니다.당신은 야합다니해를 사용해야 .std::array 그경, 작클에서 C++ 합니다.size반복할 수 있는 기능 및 반복기.

이제 std:: 벡터 네이티브 C++ 어레이(인터넷에서 가져온 것):

// Comparison of assembly code generated for basic indexing, dereferencing, 
// and increment operations on vectors and arrays/pointers.

// Assembly code was generated by gcc 4.1.0 invoked with  g++ -O3 -S  on a 
// x86_64-suse-linux machine.

#include <vector>

struct S
{
  int padding;

  std::vector<int> v;
  int * p;
  std::vector<int>::iterator i;
};

int pointer_index (S & s) { return s.p[3]; }
  // movq    32(%rdi), %rax
  // movl    12(%rax), %eax
  // ret

int vector_index (S & s) { return s.v[3]; }
  // movq    8(%rdi), %rax
  // movl    12(%rax), %eax
  // ret

// Conclusion: Indexing a vector is the same damn thing as indexing a pointer.

int pointer_deref (S & s) { return *s.p; }
  // movq    32(%rdi), %rax
  // movl    (%rax), %eax
  // ret

int iterator_deref (S & s) { return *s.i; }
  // movq    40(%rdi), %rax
  // movl    (%rax), %eax
  // ret

// Conclusion: Dereferencing a vector iterator is the same damn thing 
// as dereferencing a pointer.

void pointer_increment (S & s) { ++s.p; }
  // addq    $4, 32(%rdi)
  // ret

void iterator_increment (S & s) { ++s.i; }
  // addq    $4, 40(%rdi)
  // ret

// Conclusion: Incrementing a vector iterator is the same damn thing as 
// incrementing a pointer.

로 할당하는 : 배열: 배열을 할당합니다.new 객체: 플레인 객체)를 할당합니다.int) 또는 사용자 정의 생성자가 없는 클래스이며, 사용자 정의 생성자를 사용하여 요소를 초기화하지 않습니다.new는 -allocated arrays로 인해 이점이 있을 수 .std::vector생성 시 모든 요소를 기본값(예: int의 경우 0)으로 초기화합니다(알려주기 위해 @로 표시됨).

마이크로 옵티마이저 사용자를 위한 프리앰블

기억:

"프로그래머들은 프로그램의 중요하지 않은 부분의 속도에 대해 생각하거나 걱정하는 데 엄청난 시간을 낭비합니다. 이러한 효율성 시도는 디버깅과 유지보수를 고려할 때 실제로 매우 부정적인 영향을 미칩니다.우리는 작은 효율성에 대해 잊어야 합니다. 약 97%의 시간을 두고 말하자면, 조기 최적화는 모든 악의 근원입니다.하지만 우리는 그 중요한 3%에서 기회를 놓쳐서는 안 됩니다."

(전체 견적에 대한 변형 덕분)

더 낮은 수준이어야 하므로 더 빠르다고 믿는다고 해서 벡터(또는 무엇이든) 대신 C 배열을 사용하지 마십시오.당신은 틀릴 것입니다.

기본 벡터(또는 필요에 맞게 조정된 안전 컨테이너)를 사용한 다음 프로파일러가 문제라고 말할 경우 더 나은 알고리즘을 사용하거나 컨테이너를 변경하여 최적화할 수 있는지 확인합니다.

이 말은, 우리는 원래의 질문으로 돌아갈 수 있다는 것입니다.

정적/동적 어레이?

C++ 배열 클래스는 낮은 수준의 C 배열보다 동작이 더 낫습니다. C 배열이 할 수 없는 질문에 대답할 수 있기 때문입니다.그들은 스스로 청소할 수 있습니다.더 중요한 것은 대개 템플릿 및/또는 인라인을 사용하여 작성된다는 것입니다. 즉, 디버그에서 많은 코드에 표시되는 문제가 릴리스 빌드에서 생성되는 코드가 거의 또는 전혀 해결되지 않으므로 안전성이 떨어지는 기본 경쟁 제품과 차이가 없습니다.

전체적으로 두 가지 범주에 속합니다.

동적 배열

malloc-ed/new-ed 배열에 포인터를 사용하는 것은 기껏해야 std:: vector 버전만큼 빠르며 훨씬 덜 안전합니다(litb의 게시물 참조).

따라서 std:: 벡터를 사용합니다.

정적 배열

정적 배열을 사용하는 것이 가장 좋습니다.

  • std::array 버전만큼 빠름
  • 그리고 훨씬 덜 안전합니다.

따라서 std::array를 사용합니다.

초기화되지 않은 메모리

때로기, 사하용을 합니다.vector 대신 버퍼가 발생하여 인 비용이 합니다. 왜냐하면vector버니가 답변에서 언급한 것처럼, 버퍼는 구성 시 초기화되지만, 버퍼가 교체하는 코드는 초기화되지 않았습니다.

이 경우 다음을 사용하여 처리할 수 있습니다.unique_ptr신에대 vector코드라인에서 buffer_owner과 같은 수 .realloc?) 또는 필요한 것은 무엇이든.

벡터는 후드 아래의 배열입니다.성능은 동일합니다.

성능 문제가 발생할 수 있는 한 가지 방법은 우선 벡터의 크기를 올바르게 조정하지 않는 것입니다.

벡터가 채워지면 자체 크기가 조정되며, 이는 새 배열 할당, 다음으로 n개의 복사 생성자, 다음으로 약 n개의 소멸자 호출, 다음으로 배열 삭제를 의미합니다.

구성/파기 비용이 많이 드는 경우 벡터를 처음부터 정확한 크기로 만드는 것이 훨씬 좋습니다.

이를 입증할 수 있는 간단한 방법이 있습니다.생성/삭제/복사/할당된 시간을 보여주는 단순 클래스를 만듭니다.이런 것들의 벡터를 만들고, 벡터의 뒤쪽 끝에 밀어넣기 시작합니다.벡터가 채워지면 벡터 크기가 조정됨에 따라 일련의 활동이 발생합니다.그런 다음 예상되는 요소 수에 맞게 벡터 크기를 지정하여 다시 시도합니다.차이를 알게 될 것입니다.

메흐다드가 한 말에 대한 대답은 다음과 같습니다.

그러나 여전히 어레이가 필요한 경우가 있을 수 있습니다.낮은 수준의 코드(즉, 어셈블리) 또는 배열이 필요한 이전 라이브러리와 인터페이스할 때 벡터를 사용하지 못할 수 있습니다.

전혀 사실이 아닙니다.다음을 사용할 경우 벡터가 배열/포인터로 잘 저하됩니다.

vector<double> vector;
vector.push_back(42);

double *array = &(*vector.begin());

// pass the array to whatever low-level code you have

이것은 모든 주요 STL 구현에 적용됩니다.다음 표준에서는 작동해야 합니다(오늘은 괜찮지만).

C++11에서는 일반 배열을 사용해야 하는 이유가 훨씬 적습니다.

기능에 따라 가장 빠른 것부터 가장 느린 것까지 3가지 종류의 어레이가 있습니다(물론 구현 품질은 목록의 사례 3에서도 매우 빠르게 처리할 수 있습니다).

  1. 정적. 컴파시크정적의기진려알일▁---정▁static-. ---std::array<T, N>
  2. 실행 시 알려진 크기를 가진 동적이며 크기는 조정되지 않습니다.여기서 일반적인 최적화는 배열을 스택에 직접 할당할 수 있는지 여부입니다. -- 사용할 수 없습니다.아마도요.dynarrayC++ 14 뒤에 C++ TS가 있습니다. C는 VLA .
  3. 이고 런타임에 를 조정할 수 --- 동적고런크다니가합능조정기에타임이▁---▁dynamic▁--다-. ---std::vector<T>

1. 요소 수가 고정된 일반 정적 배열의 경우,std::array<T, N>C++11로.

2. 런타임에 지정되었지만 크기가 변경되지 않는 고정 크기 배열의 경우, C++14에서 논의가 있지만 기술 사양으로 이동되어 마침내 C++14로 만들어졌습니다.

3명이서. std::vector<T> 일반적으로 힙에서 메모리를 요청합니다.이는 성능에 영향을 미칠 수 있습니다.std::vector<T, MyAlloc<T>>사용자 지정 할당자를 통해 상황을 개선할 수 있습니다. T mytype[] = new MyType[n];크기를 조정할 수 있으며 일반 배열처럼 포인터로 감쇠하지 않습니다.

배열이 포인터로 축소되는 것을 방지하려면 언급된 표준 라이브러리 유형을 사용합니다.디버깅 시간을 절약할 수 있으며 성능은 동일한 기능 집합을 사용하는 경우 일반 배열과 정확히 동일합니다.

사용 시 성능에 미치는 영향은 분명합니다.std::vector초기화되지 않은 버퍼(예: 대상으로 사용)를 원할 때 원시 어레이 대비memcpy() . . . . . . . . . . . . . . . . ..std::vector기본 생성자를 사용하여 모든 요소를 초기화합니다.원시 배열은 그렇지 않습니다.

의 c++ 사양은 다음과 같습니다.std:vector가 생자를수니다합행가를 사용합니다.count인수(세 번째 형식)는 다음과 같습니다.

"선택적으로 사용자 제공 할당자를 사용하여 다양한 데이터 소스에서 새 컨테이너를 구성합니다.

  1. T의 개수 기본 삽입 인스턴스로 컨테이너를 구성합니다.복사본이 생성되지 않습니다.

복잡성

2-3) 리니어 계수

원시 어레이에는 이 초기화 비용이 발생하지 않습니다.

사용자 지정 할당자를 사용하면 벡터 요소의 "초기화"를 피할 수 있습니다(즉, 값 초기화 대신 기본 초기화 사용).자세한 내용은 다음 질문을 참조하십시오.

STL로 하세요.이행강제금은 없습니다.알고리즘은 매우 효율적이고 우리 대부분이 생각하지 못할 세부사항들을 잘 처리합니다.

STL은 매우 최적화된 라이브러리입니다.실제로 고성능이 필요한 게임에서는 STL을 사용하는 것이 좋습니다.어레이는 오류가 발생하기 쉬우므로 일상적인 작업에 사용할 수 없습니다.오늘날의 컴파일러는 또한 매우 영리하며 STL로 훌륭한 코드를 생성할 수 있습니다.작업 내용을 알고 있는 경우 일반적으로 STL에서 필요한 성능을 제공할 수 있습니다.예를 들어 벡터를 필요한 크기로 초기화하면(처음부터 알고 있는 경우) 기본적으로 어레이 성능을 달성할 수 있습니다.그러나 여전히 어레이가 필요한 경우가 있을 수 있습니다.낮은 수준의 코드(즉, 어셈블리) 또는 배열이 필요한 이전 라이브러리와 인터페이스할 때 벡터를 사용하지 못할 수 있습니다.

둘리가 내 치수에 기여한 것에 대해.

결론은 정수 배열이 정수 벡터보다 빠르다는 것입니다(이 예에서는 5배).그러나 배열과 벡터는 더 복잡하거나 정렬되지 않은 데이터의 속도가 동일합니다.

디버그 모드에서 소프트웨어를 컴파일할 경우 많은 컴파일러가 벡터의 접근자 함수를 인라인화하지 않습니다.이렇게 하면 성능이 문제가 되는 상황에서 stl 벡터 구현 속도가 훨씬 느려집니다.또한 디버거에서 할당된 메모리 양을 볼 수 있기 때문에 코드를 디버그하기가 더 쉽습니다.

최적화된 모드에서는 stl 벡터가 배열의 효율성에 근접할 것으로 예상합니다.이는 많은 벡터 방법이 인라인화되었기 때문입니다.

둘 사이의 성능 차이는 구현에 따라 매우 다릅니다. 잘못 구현된 std::vector를 최적의 어레이 구현과 비교하면 어레이가 이기지만 어레이가 역전되고 벡터가 이기게 됩니다.

사과와 사과를 비교하는 한(어레이와 벡터 모두 고정된 수의 요소가 있거나 동적으로 크기가 조정됨) STL 코딩 연습을 따르는 한 성능 차이는 무시할 수 있다고 생각합니다.표준 C++ 컨테이너를 사용하면 표준 C++ 라이브러리의 일부인 사전 롤링 알고리즘을 사용할 수 있으며 대부분의 알고리즘은 직접 구축한 동일한 알고리즘의 평균 구현보다 더 나은 성능을 발휘할 수 있습니다.

즉, 적절한 디버그 모드를 사용하는 대부분의 STL 구현은 적어도 표준 컨테이너로 작업할 때 사람들이 하는 전형적인 실수를 강조/캐시할 수 있기 때문에 IMHO는 디버그 STL이 있는 디버그 시나리오에서 승리합니다.

아, 그리고 배열과 벡터가 동일한 메모리 레이아웃을 공유하므로 벡터를 사용하여 데이터를 기본 배열이 필요한 레거시 C 또는 C++ 코드로 전달할 수 있습니다.하지만 그 시나리오에서는 대부분의 베팅이 취소되고 원시 메모리를 다시 처리한다는 것을 기억하십시오.

벡터를 사용하여 다차원 동작을 표현하는 경우 성능이 저하됩니다.

2d+ 벡터가 성능 저하를 유발합니까?

요점은 크기 정보를 가진 각 하위 벡터에 대한 오버헤드가 적으며, 반드시 데이터의 직렬화가 필요하지는 않다는 것입니다(다차원 어레이의 경우와 마찬가지로).이러한 직렬화의 부족은 마이크로 최적화 이상의 기회를 제공할 수 있습니다.다차원 배열을 사용하는 경우 std:: 벡터를 확장하고 자체 get/set/resize 비트 기능을 롤하는 것이 가장 좋습니다.

크기를 동적으로 조정할 필요가 없는 경우 용량을 절약할 수 있는 메모리 오버헤드가 있습니다(포인터/size_t 하나).바로 그겁니다.

인라인 함수 안에 있는 인라인 함수 안에 벡터 액세스 권한이 있는 에지 케이스가 있을 수 있습니다. 여기서 컴파일러가 인라인하는 것을 넘어서 함수 호출을 강제합니다.그것은 걱정할 가치가 없을 정도로 매우 드문 일일 것입니다 - 일반적으로 저는 lib에 동의할 것입니다.

아직 아무도 이에 대해 언급하지 않은 것은 놀라운 일입니다. 문제가 있다는 것이 입증될 때까지 성능에 대해 걱정하지 말고 벤치마크하십시오.

가장 중요한 관심사는 성능이 아니라 안전이라고 생각합니다.벡터를 사용하면 많은 고통을 줄일 수 있는 배열에서 실수를 많이 할 수 있습니다(예: 크기 조정을 고려).

벡터는 배열의 크기를 포함하기 때문에 배열보다 약간 더 많은 메모리를 사용합니다.또한 프로그램의 하드 디스크 크기와 프로그램의 메모리 설치 공간을 늘립니다.이러한 증가는 미미하지만 임베디드 시스템으로 작업하는 경우에는 중요할 수 있습니다.이러한 차이가 중요한 대부분의 장소는 C++보다는 C를 사용하는 장소입니다.

다음과 같은 간단한 테스트:

C++ 어레이 대 벡터 성능 테스트 설명

"벡터 및 어레이/포인터의 기본 인덱싱, 참조 해제 및 증분 작업을 위해 생성된 어셈블리 코드 비교"의 결론과 모순됩니다.

배열과 벡터 간에 차이가 있어야 합니다.검사 결과는...그냥 해보세요, 코드는 저기 있어요...

때때로 배열이 벡터보다 더 좋습니다.고정 길이의 객체 집합을 항상 조작하는 경우 배열이 더 좋습니다.다음 코드 스니펫을 고려해 보십시오.

int main() {
int v[3];
v[0]=1; v[1]=2;v[2]=3;
int sum;
int starttime=time(NULL);
cout << starttime << endl;
for (int i=0;i<50000;i++)
for (int j=0;j<10000;j++) {
X x(v);
sum+=x.first();
}
int endtime=time(NULL);
cout << endtime << endl;
cout << endtime - starttime << endl;

}

여기서 X의 벡터 버전은

class X {
vector<int> vec;
public:
X(const vector<int>& v) {vec = v;}
int first() { return vec[0];}
};

X의 어레이 버전은 다음과 같습니다.

class X {
int f[3];

public:
X(int a[]) {f[0]=a[0]; f[1]=a[1];f[2]=a[2];}
int first() { return f[0];}
};

main()의 어레이 버전은 내부 루프에서 매번 "new"의 오버헤드를 피하고 있기 때문에 더 빠릅니다.

(이 코드는 저에 의해 comp.lang.c++에 게시되었습니다.)

고정 길이 어레이의 경우 릴리스 빌드에서는 성능이 동일하지만(벡터<> 대비) 디버그 빌드에서는 하위 레벨 어레이가 20배 차이로 승리합니다(MS Visual Studio 2015, C++ 11).

따라서 STL에 유리한 "시간 절약 디버깅" 인수는 사용자(또는 동료)가 어레이 사용에 버그를 도입하는 경향이 있는 경우에는 유효할 수 있지만 디버깅 시간이 현재 작업 중인 지점까지 실행될 때까지 대부분 대기하고 있으면 유효하지 않을 수 있습니다.

수치 집약적인 코드를 연구하는 경험이 많은 개발자들은 때때로 두 번째 그룹에 속합니다(특히 벡터를 사용하는 경우).

배열 고길배가예정니다합을열이정)을 합니다.int* v = new int[1000];std::vector<int> v(1000);의 크기로v1000으로 고정됨), 실제로 중요한 유일한 성능 고려 사항(또는 적어도 비슷한 딜레마에 빠졌을 때 나에게 중요함)은 요소에 대한 액세스 속도입니다.STL의 벡터 코드를 찾아보니 다음과 같습니다.

const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }

이 함수는 컴파일러에 의해 인라인화될 것입니다.여러분이 서래그로 할 인 한, 이계있고는유일것이한획하당신▁so이것▁thing▁only한,.v를 사용하여 해당 요소에 액세스합니다.operator[]성능에 차이가 있어서는 안 될 것 같습니다.

오래 전에 C 스타일 어레이가 std:: vector보다 더 빠르다고 생각했던 것을 기억합니다.

하지만 저는 성능에 차이가 없거나 차이가 미미하다는 게시물을 많이 읽었고 (정말 문제가 되지 않는 곳에서) 그래서 저의 전제가 틀렸습니다.

제가 배운 또 다른 교훈 중 하나는 항상 테스트/벤치마크/프로파일이었습니다. 그래서 저는 여기 있는 몇몇 사람들이 했던 것처럼 직접 테스트를 했고, 실제로 C 스타일 어레이가 std:::vector보다 더 빠르다는 것을 알게 되었습니다.

그러나 컴파일러 최적화에 대해 몰랐기 때문에 이러한 테스트는 바닥에 떨어졌습니다. clang++ g++ 모두 컴파일러 최적화 플래그가 있으며, (-O0은 디버깅용입니다.)플래그 -O2 std:: 벡터는 실제로 C 스타일 배열만큼 빨라졌습니다.제가 정말 잘못했어요.몇몇 사람들은 플래그 없이 컴파일하는 것이 정상적이라고 말했습니다. 저는 그 진술에 상당히 동의하지 않습니다. -O2를 사용하는 것은 다른 많은 플래그와 함께 흔한 일입니다. 제 경험상 -O3를 사용하는 것은 아마도 이례적인 일입니다.

C++에서 어레이 또는 std:: 벡터를 사용하면 성능 차이가 어떻게 됩니까?-O2 최적화 플래그를 사용하면 성능 차이가 거의 없거나 전혀 없습니다. (같은 작업을 좋아하는 작업과 비교하는 경우) 컴파일러를 잘 알고 있거나 비정상적인 작업을 수행하면 시나리오를 엔지니어링할 수 있습니다.

그들 중 어느 것이 사용하기에 가장 좋고 좋은 것인지에 대한 논쟁은 없습니다.둘 다 사용 사례가 있고 장단점이 있습니다.두 컨테이너의 동작은 장소에 따라 다릅니다.어레이의 주요 어려움 중 하나는 일단 정의되거나 초기화되면 값을 변경할 수 없고 반대쪽 벡터는 어레이처럼 크기가 고정되지 않을 때마다 벡터 값을 변경할 수 있다는 것입니다.어레이는 정적 메모리 할당을 가지고 있고 벡터는 동적 메모리 또는 힙 메모리 할당을 가지고 있기 때문에(벡터로 요소를 밀어넣고 팝업할 수 있음), c++ Bjarne Stroustrup의 개발자는 벡터가 어레이 이상을 사용할 수 있는 유연성이 있다고 말했습니다.

C++ 어레이를 새 어레이(즉, 동적 어레이 사용)와 함께 사용하는 것은 피해야 합니다.크기를 파악해야 하는 문제가 있고, 수동으로 삭제하고 모든 종류의 하우스키핑을 해야 합니다.

또한 배열에서는 쉽게 불가능한 벡터에 값을 삽입, 푸시 및 풀링할 수 있습니다.

성능 측면에서 작은 값으로 작업하는 경우 어레이를 사용하고 큰 스케일 코드로 작업하는 경우 벡터를 사용해야 합니다(벡터는 큰 값을 처리하는 데 능숙합니다).

언급URL : https://stackoverflow.com/questions/381621/using-arrays-or-stdvectors-in-c-whats-the-performance-gap