vector와 array의 처리 성능 높이는 방법 (실험)

"Roman sculpture reacting to bitcoin crash, full body, 4k, realistic." from DALL-E 2

C++에서 일반적으로 크기가 정해지지 않은 배열을 사용하기 위해 vector<type>를 사용하고, 크기가 정해진 배열을 사용하기 위해 array<type, size>를 사용합니다. 그리고 vector와 array의 원소를 for문을 통해 접근할 때 다음과 같은 코드를 작성합니다.

vector<int8_t> vec;
int length = vec.size();

for (int i = 0; i < length; i++) {
	vec[i] = i;
}

그리고 문득 vector의 인덱스 접근 방식과 일반 배열 []의 처리 성능이 얼마나 차이나는지 궁금하여 반복문으로 값을 할당하는 실험을 진행하였습니다.

시간 측정 도구는 제가 이전에 작성한 HourMeter 클래스 객체를 사용하여 진행하였습니다.

vector와 array 그리고 일반 배열[]을 크기 1,000,000 만큼 할당한 후 각 원소 i에 값 i를 할당하는 작업을 20번 반복하는 실험을 진행하였습니다.

이에 대한 소스코드는 아래와 같습니다.

void main() {
	constexpr int N = 1'000'000;
	constexpr int M = 20;
	HourMeter hourMeter;

	vector<int8_t> vec;
	vec.resize(N);
	array<int8_t, N> arr;
	int8_t* arr2 = new int8_t[N];

	try {
		printf("vector - 인덱스 직접 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = vec.size();

			for (int i = 0; i < length; i++) {
				vec[i] = i;
			}
		}
		hourMeter.endMeasure();

		printf("array - 인덱스 직접 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = arr.size();
			for (int i = 0; i < length; i++) {
				arr[i] = i;
			}
		}
		hourMeter.endMeasure();

		printf("일반 배열 - 포인터 할당 후 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = N;

			for (int i = 0; i < length; i++) {
				arr2[i] = i;
			}
		}
		hourMeter.endMeasure();
	}
	catch (exception& ex) {
		printf("EXCEPTION: %s\n", ex.what());
		return;
	}

	delete[] arr2;
}

실험 결과는 아래와 같습니다.

vector의 인덱스 접근 방식이 3.95초로 가장 느렸으며, array 가 0.41초로 두 번째, 일반 배열[]이 0.04초로 가장 빠른 성능을 보였습니다. 특히, 일반 배열은 vector보다 약 100배 빠른 성능을 보여주었습니다.

​ vector와 array에는 data()라는 멤버함수가 있는데 이는 vector와 array가 실제 데이터에 대한 메모리 주소를 반환하는 함수입니다. 저는 이를 포인터로 받아서 일반 배열처럼 처리하면 성능이 어떨지 궁금하여 마찬가지로 실험을 진행해보았습니다.

​이에 대한 소스코드는 아래와 같습니다.

void main() {
	constexpr int N = 1'000'000;
	constexpr int M = 20;
	HourMeter hourMeter;

	vector<int8_t> vec;
	vec.resize(N);
	array<int8_t, N> arr;
	int8_t* arr2 = new int8_t[N];

	try {
		printf("vector - 인덱스 직접 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = vec.size();

			for (int i = 0; i < length; i++) {
				vec[i] = i;
			}
		}
		hourMeter.endMeasure();

		printf("vector - 포인터 할당 후 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = vec.size();
			int8_t* temp_arr = vec.data();

			for (int i = 0; i < length; i++) {
				temp_arr[i] = i;
			}
		}
		hourMeter.endMeasure();

		printf("array - 인덱스 직접 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = arr.size();
			for (int i = 0; i < length; i++) {
				arr[i] = i;
			}
		}
		hourMeter.endMeasure();

		printf("array - 포인터 할당 후 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = arr.size();
			int8_t* temp_arr = arr.data();
			for (int i = 0; i < length; i++) {
				temp_arr[i] = i;
			}
		}
		hourMeter.endMeasure();

		printf("일반 배열 - 포인터 할당 후 접근 방식\n");
		hourMeter.startMeasure();
		for (int j = 0; j<M; j++)
		{
			int length = N;

			for (int i = 0; i < length; i++) {
				arr2[i] = i;
			}
		}
		hourMeter.endMeasure();
	}
	catch (exception& ex) {
		printf("EXCEPTION: %s\n", ex.what());
		return;
	}

	delete[] arr2;
}

실험 결과는 아래와 같습니다.

실험 결과, vector 또는 array를 data() 멤버 함수를 통해 메모리 주소를 포인터로 받은 후 일반 배열처럼 사용하면 성능 상 일반 배열과 차이가 없었습니다.

결론적으로, 성능을 중요시 여긴다면 vector 또는 array를 data로 접근하여 사용하는 것도 고려해볼 만 합니다.