C ++ 과정에서는 더 이상 새로운 프로젝트에서 C ++ 배열을 사용하지 않는 것이 좋습니다. 내가 아는 한 Stroustroup 자신은 배열을 사용하지 말 것을 제안합니다. 그러나 중요한 성능 차이가 있습니까?
int main(int argc, const std::vector<string>& argv)
C ++ 과정에서는 더 이상 새로운 프로젝트에서 C ++ 배열을 사용하지 않는 것이 좋습니다. 내가 아는 한 Stroustroup 자신은 배열을 사용하지 말 것을 제안합니다. 그러나 중요한 성능 차이가 있습니까?
int main(int argc, const std::vector<string>& argv)
답변:
C ++ 배열과 함께 new
(즉, 동적 배열을 사용하여) 사용하지 않아야합니다. 크기를 추적해야하는 문제가 있으며 수동으로 삭제하고 모든 종류의 정리 작업을 수행해야합니다.
범위 검사가 없기 때문에 스택에서 배열을 사용하지 않는 것이 좋습니다. 배열을 전달하면 크기에 대한 정보가 사라집니다 (포인터에서 배열로 변환). 당신은 사용해야 boost::array
작은 클래스의 C ++ 배열을 래핑하고 제공하는 경우에 size
그것을 반복하는 기능과 반복자를.
이제 std :: vector 대 네이티브 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 배열을 std::vector
초기화하고 모든 요소를 시공시 기본값 (예를 들어 int의 경우 0) (나를 상기시키기 위해 @bernie에 크레딧).
생각해 내다:
"프로그래머는 프로그램의 중요하지 않은 부분의 속도에 대해 생각하거나 걱정하는 데 많은 시간을 낭비하며, 이러한 효율성에 대한 시도는 실제로 디버깅 및 유지 관리를 고려할 때 강력한 부정적인 영향을 미칩니다. 97 %의 시간 : 조기 최적화는 모든 악의 근원입니다. 그러나 우리는 그 중요한 3 %의 기회를 포기해서는 안됩니다. "
( 전체 견적 을 위해 변태 에 감사드립니다 )
벡터 (또는 다른 것) 대신 C 배열을 사용하지 마십시오. 단순하다고 생각하기 때문에 더 빠르다고 생각하기 때문입니다. 당신은 틀렸을 것입니다.
기본 벡터 (또는 필요에 맞게 안전한 컨테이너)를 사용하고 프로파일 러에 문제가 있다고 판단되면 더 나은 알고리즘을 사용하거나 컨테이너를 변경하여 최적화 할 수 있는지 확인하십시오.
이것은 우리가 원래의 질문으로 되돌아 갈 수 있다고 말했습니다.
C ++ 배열 클래스는 저수준 C 배열보다 더 잘 동작합니다. 왜냐하면 자신에 대해 많이 알고 C 배열이 할 수없는 질문에 대답 할 수 있기 때문입니다. 그들은 스스로를 청소할 수 있습니다. 더 중요한 것은 일반적으로 템플릿 및 / 또는 인라이닝을 사용하여 작성되는데, 이는 디버그에서 많은 코드에 나타나는 것이 릴리스 빌드에서 생성 된 코드를 거의 또는 전혀 해결하지 않음을 의미합니다.
대체로 두 가지 범주에 속합니다.
malloc-ed / new-ed 배열에 대한 포인터를 사용하면 std :: vector 버전만큼 빠르며 훨씬 안전하지 않습니다 ( litb의 게시물 참조 ).
따라서 std :: vector를 사용하십시오.
정적 배열을 사용하는 것이 가장 좋습니다.
따라서 std :: array를 사용하십시오 .
때로는 vector
원시 버퍼 대신에 버퍼를 사용 vector
하면 생성시 버퍼를 초기화 하기 때문에 눈에 띄는 비용이 발생 하지만 대체 코드는 베르니 가 대답 한 것처럼 .
이 경우 또는 unique_ptr
대신 코드 라인에서 vector
예외가 아닌 경우 를 사용하여 처리 할 수 있습니다 . 실제로 buffer_owner
해당 메모리를 소유 할 클래스 를 작성하고 다음을 포함하여 쉽고 안전하게 액세스 할 수 있습니다. 크기 조정 ( realloc
? 사용 ) 또는 필요한 항목 과 같은 보너스 .
벡터는 후드 아래의 배열입니다. 성능은 같습니다.
성능 문제가 발생할 수있는 한 곳은 벡터의 크기를 올바르게 시작하지 않는 것입니다.
벡터가 채워지면 자체 크기가 조정되므로 새로운 배열 할당, n 개의 복사 생성자, 약 n 개의 소멸자 호출, 배열 삭제가 이어질 수 있습니다.
생성 / 파괴가 비싸면 벡터를 올바른 크기로 만드는 것이 훨씬 좋습니다.
이것을 보여주는 간단한 방법이 있습니다. 생성 / 파기 / 복사 / 할당 될 때 표시되는 간단한 클래스를 만듭니다. 이러한 것들의 벡터를 만들고 벡터의 뒤쪽 끝에 밀어 넣으십시오. 벡터가 채워지면 벡터 크기가 조정될 때 일련의 활동이 발생합니다. 그런 다음 예상 크기의 요소 크기로 벡터를 사용하여 다시 시도하십시오. 차이점을 볼 수 있습니다.
std::vector
소리는 표준과 호환되지 않습니까? 나는 표준이 vector::push_back
일정한 복잡성을 상각 해야한다고 요구하며 , 각각의 용량을 1 씩 증가시키는 push_back
것은 재 할당을 고려한 후 n ^ 2 복잡성이 될 것이라고 믿습니다. - 지수 용량의 증가 어떤 종류의 추정 push_back
과 insert
, 데 실패 reserve
벡터 내용을 복사에서 최대 일정한 요인 증가로 이어질 것이다. 1.5 지수 벡터 성장 인자는 실패한 경우 ~ 3 배 많은 사본을 의미합니다 reserve()
.
Mehrdad가 말한 것에 응답하기 위해 :
그러나 여전히 배열이 필요한 경우가 있습니다. 배열이 필요한 저수준 코드 (예 : 어셈블리) 또는 오래된 라이브러리와 인터페이스 할 때는 벡터를 사용하지 못할 수 있습니다.
전혀 사실이 아닙니다. 다음을 사용하면 벡터가 배열 / 포인터에서 잘 저하됩니다.
vector<double> vector;
vector.push_back(42);
double *array = &(*vector.begin());
// pass the array to whatever low-level code you have
이것은 모든 주요 STL 구현에 적용됩니다. 다음 표준에서는 (현재는 잘 작동하더라도) 작동해야합니다.
&v[n] == &v[0] + n
유효한 n
경우 크기 범위 내에 있다고 말합니다 . 이 문장을 포함하는 단락은 C ++ 11에서는 바뀌지 않았습니다.
C ++ 11에서 일반 배열을 사용해야하는 이유는 훨씬 적습니다.
특성에 따라 가장 빠른 것에서 가장 느린 것까지 세 가지 종류의 배열이 있습니다 (물론 구현 품질은 목록 3의 경우에도 실제로 빠를 수 있습니다).
std::array<T, N>
dynarray
C ++ TS에서 C ++ 14 후. C에는 VLA가 있습니다std::vector<T>
의 경우 1. 고정 된 요소의 수, 사용과 일반 정적 배열 std::array<T, N>
C ++ 십일인치
에 대한 2. 고정 된 크기의 실행시 지정된 배열,하지만 자신의 크기를 변경하지 않습니다, C에서 논의가 ++ 14 있지만 기술적 사양에 이동 된 마지막 14 ++ C 밖으로했다.
들어 3. std::vector<T>
일반적으로 힙 메모리를 요구합니다 . std::vector<T, MyAlloc<T>>
사용자 지정 할당기로 상황을 개선하는 데 사용할 수 있지만 성능에 영향을 줄 수 있습니다 . 에 비해 장점T mytype[] = new MyType[n];
은 크기를 조정할 수 있고 일반 배열처럼 포인터로 붕괴되지 않는다는 것입니다.
언급 된 표준 라이브러리 유형을 사용하여 배열이 포인터로 붕괴 되지 않도록하십시오 . 당신은 디버깅 시간을 절약하고 성능은 정확히 당신이 동일한 기능 세트를 사용하는 경우 일반 배열과 동일합니다.
STL은 매우 최적화 된 라이브러리입니다. 실제로 고성능이 필요한 게임에서 STL을 사용하는 것이 좋습니다. 어레이는 일상적인 작업에 사용하기에 너무 오류가 발생하기 쉽습니다. 오늘날의 컴파일러는 매우 영리하며 실제로 STL을 사용하여 우수한 코드를 생성 할 수 있습니다. 수행중인 작업을 알고있는 경우 STL은 일반적으로 필요한 성능을 제공 할 수 있습니다. 예를 들어 필요한 크기로 벡터를 초기화하면 (시작부터 알고있는 경우) 기본적으로 배열 성능을 얻을 수 있습니다. 그러나 여전히 배열이 필요한 경우가 있습니다. 배열이 필요한 저수준 코드 (예 : 어셈블리) 또는 오래된 라이브러리와 인터페이스 할 때는 벡터를 사용하지 못할 수 있습니다.
vec.data()
데이터 및 vec.size()
크기에 대한 C :와 벡터 또는 인접한 컨테이너와의 인터페이스 그렇게 쉽습니다.
결론은 정수 배열이 정수 벡터보다 빠릅니다 (이 예제에서는 5 배). 그러나 배열과 벡터는 더 복잡하거나 정렬되지 않은 데이터에 대해 동일한 속도로 회전합니다.
소프트웨어를 디버그 모드로 컴파일하면 많은 컴파일러가 벡터의 접근 자 함수를 인라인하지 않습니다. 이것은 성능이 문제가되는 상황에서 stl 벡터 구현을 훨씬 느리게 만듭니다. 또한 디버거에서 할당 된 메모리 양을 확인할 수 있으므로 코드를 쉽게 디버깅 할 수 있습니다.
최적화 된 모드에서 stl 벡터가 배열의 효율성에 접근 할 것으로 기대합니다. 많은 벡터 메소드가 이제 인라인되기 때문입니다.
초기화되지 않은 버퍼 std::vector
를 원할 때 (예 :의 대상으로 사용) 원시 배열과 원시 배열 을 사용하면 성능에 영향을 미칩니다 . 안memcpy()
std::vector
기본 생성자를 사용하여 모든 요소를 초기화합니다. 원시 배열은 그렇지 않습니다.
C ++ 사양 에 std:vector
복용하는 생성자 count
인수 (이 세 번째 폼의) 상태 :
`선택적으로 사용자 제공 할당 자 alloc을 사용하여 다양한 데이터 소스에서 새 컨테이너를 구성합니다.
3) 기본적으로 삽입 된 개수가 T 인 컨테이너를 구성합니다. 복사는 없습니다.
복잡성
2-3) 리니어 카운트
원시 배열에는이 초기화 비용이 발생하지 않습니다.
둘 사이의 성능 차이는 구현에 따라 크게 달라집니다. 잘못 구현 된 std :: vector를 최적의 배열 구현과 비교하면 배열이 이기지 만 돌아 서서 벡터가 이기게됩니다.
사과와 사과를 비교하는 한 (배열과 벡터 모두 고정 된 수의 요소를 갖거나 동적으로 크기가 조정되는 경우) STL 코딩 실습을 따르는 한 성능 차이는 무시할 만하다고 생각합니다. 표준 C ++ 컨테이너를 사용하면 표준 C ++ 라이브러리의 일부인 사전 롤 알고리즘을 사용할 수 있으며 대부분 자체 빌드 한 동일한 알고리즘의 평균 구현보다 성능이 더 우수합니다. .
즉, 적절한 디버그 모드를 사용하는 대부분의 STL 구현은 표준 컨테이너로 작업 할 때 사람들이 저지르는 전형적인 실수를 적어도 강조 표시 / 강화시킬 수 있기 때문에 IMHO는 디버그 STL로 디버그 시나리오에서 벡터가 승리합니다.
아, 그리고 배열과 벡터가 동일한 메모리 레이아웃을 공유하므로 벡터를 사용하여 기본 배열을 기대하는 레거시 C 또는 C ++ 코드로 데이터를 전달할 수 있습니다. 그러나이 시나리오에서는 대부분의 베팅이 해제되어 있으며 원시 메모리를 다시 다루고 있습니다.
std::deque
사용할 수 있습니다.
나는 주요 관심사는 성능이 아니라 안전이라고 주장한다. 벡터가 많은 고통을 덜어주는 배열 (예 : 크기 조정 고려)에 많은 실수를 할 수 있습니다.
벡터는 배열의 크기를 포함하기 때문에 배열보다 약간 더 많은 메모리를 사용합니다. 또한 프로그램의 하드 디스크 크기와 프로그램의 메모리 사용량을 증가시킵니다. 이러한 증가는 미미하지만 임베디드 시스템으로 작업하는 경우 중요 할 수 있습니다. 이러한 차이점이 중요한 대부분의 장소는 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 ++에 게시되었습니다.)
벡터를 사용하여 다차원 동작을 나타내는 경우 성능이 저하됩니다.
요점은 크기 정보를 갖는 각각의 서브 벡터에 대해 적은 양의 오버 헤드가 있고, 데이터가 직렬화 될 필요는 없다는 점이다 (다차원 c 어레이가있는 것처럼). 이러한 직렬화 결여는 마이크로 최적화 기회보다 더 큰 기회를 제공 할 수 있습니다. 다차원 배열을 수행하는 경우 std :: vector를 확장하고 자체 get / set / resize 비트 함수를 롤링하는 것이 가장 좋습니다.
(예를 들어 고정 길이 배열을 가정 int* v = new int[1000];
대 std::vector<int> v(1000);
의 크기, v
정말 중요한 것을 유일한 성능을 고려하여 1000 고정 된 상태로 유지되고) (또는 I 비슷한 딜레마에있을 때 나에게 중요 적어도) 액세스 속도는이다 요소. STL의 벡터 코드를 살펴본 결과 다음과 같습니다.
const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }
이 함수는 컴파일러에 의해 가장 확실하게 인라인됩니다. 따라서으로 수행하려는 유일한 v
요소는로 요소에 액세스하는 operator[]
한 성능에 실제로 차이가 없어야하는 것처럼 보입니다.