728x90
반응형

여러 가지 문제를 풀다 보면 stack 자료구조를 이용해야 할 때가 많이 있습니다.

그런데 vector 도 마치 stack처럼 사용할 수 있는데 혹시 vector를 사용하면 안 될까 라는 생각에

vector를 사용해 보았는데 stack 보다 더 빠른 결과가 나왔습니다.

그래서 이번에는 c++ 의 여러 가지 자료구조들의 속도를 한번 측정해 보았습니다.

 

측정에 사용된 코드입니다.

#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <time.h>
using namespace std;

#define looper 1000000

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	clock_t start, end;
	double result;


	vector <int> v;

	start = clock();
	for (int i = 0; i < looper; ++i) {
		v.push_back(i);
	}
	while (!v.empty()) {
		v.pop_back();
	}
	end = clock();
	result = (double)(end - start);
	cout << "vector : " << result << "\n";
	

	stack<int> stk;

	start = clock();
	for (int i = 0; i < looper; ++i) {
		stk.push(i);
	}
	while (!stk.empty()) {
		stk.pop();
	}
	end = clock();
	result = (double)(end - start);
	cout << "default stack : " << result << "\n";


	stack<int, vector<int>> v_stk;

	start = clock();
	for (int i = 0; i < looper; ++i) {
		v_stk.push(i);
	}
	while (!v_stk.empty()) {
		v_stk.pop();
	}
	end = clock();
	result = (double)(end - start);
	cout << "vector stack : " << result << "\n";


	deque<int> dqu;

	start = clock();
	for (int i = 0; i < looper; ++i) {
		dqu.push_back(i);
	}
	while (!dqu.empty()) {
		dqu.pop_back();
	}
	end = clock();
	result = (double)(end - start);
	cout << "deque : " << result << "\n";


	return 0;
}

백만번씩 push pop 한 실행 결과입니다.

 

일단 vector 와 stack을 비교해보면 무려 1.2 초 가량 차이가 나는 것을 볼 수 있습니다.

왜 이런 결과가 나오는 것일까요?

보통 생각대로라면 vector 보다 stack 이 훨씬 단순한데 stack 이 더 빨라야 하는 것 아닐까요?

 

이 이유를 알려면 stack 에 대해 알고 있어야 합니다.

stack 은 사실 따로 stack이라는 내부 컨테이너가 있는 것이 아닙니다.

vector, deque, list container 같은 내부 컨테이너들을 그대로 사용하되 마치 stack처럼 동작하게 해 둔 것입니다.

그러니까 사실 stack을 선언하면 기본값으로 deque로 선언이 됩니다.

 

그래서 결과를 보시면 default stack과 deque의 시간 차이가 그렇게 크지 않습니다.

stack의 내부 컨테이너로 vector를 사용하게 되면 vector와 거의 비슷한 결과가 나옵니다.

 

그런데 같은 내부 컨테이너를 사용해도 그냥 vector와 vector stack , deque와 deque stack을 보면 stack 이 뭔가 조금 더 느립니다.

그래서 알고리즘 문제를 풀 때는 그냥 vector를 사용해서 푸는 게 훨씬 빠릅니다.

그러나 나중에 어떤 프로젝트에 참여하게 되시거나 프로그램을 만들게 될 때 vector를 사용하게 되면 사용 용도가 모호해지는 현상이 발생할 수 도 있으므로 vector stack을 사용하시는 것을 추천드립니다.

 

결론

1. 알고리즘 문제를 풀 때는 vector 가 제일 빠르다.

2. 그러나 대형 프로그램을 짤 때는 용도가 모호해질 수 있으므로 vector stack을 사용하자.

 

 

728x90
반응형
728x90
반응형

백준 문제를 풀다가 알게 된 사실입니다.

https://www.acmicpc.net/problem/1753

해당 1753 문제를 풀던 도중이었죠.

해당 문제는 edge 가 최대 300000 개로 다른 문제들에 비해 입출력이 상당히 많은 문제입니다.

 

대충 코드를 작성하고 제출을 했는데 시간 초과가 발생했습니다.

그래서 내가 뭔가 알고리즘을 잘못 짠 건가 해서 구글링을 하던 중 코드가 구조상 거의 동일한데

저 코드는 되고 내 코드는 안 되는 이유를 찾았습니다.

 

결론부터 말하자면 바로 3줄의 차이였습니다.

ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);

 

우선 C/C++ 의 여러 가지 입출력의 수행 시간 차이를 알아보겠습니다.

출처 : https://algospot.com/forum/read/2496/

위의 사진을 보면 getchar(*) 가 0.39초로 가장 빠르고

그다음으로 흔히 사용하는 scanf 가 0.798 ,  cin이 2.051로 2배가 넘는 속도 차이를  보여주고 있습니다.

이 느린 cin에 sync_with_stdio 를 사용하면 scanf 와 비슷한 속도를 보여줍니다.

 

그러면 왜 이런 현상이 일어나는 걸까요?

 

우선 C++는 기본적으로 입출력 시 자신만의 표준 스트림을 사용하지 않습니다.

그럼 어떻게 하느냐? 바로 C의 표준스트림을 사용합니다.

C++ 의 표준 스트림 cin, cout 등과 C의 표준스트림 stdin, stdout 이 다 연결돼있다는 소립니다.

그래서 cin 과 printf 를 혼합해서 사용해도 입출력의 순서가 보장되는 것입니다.

 

하지만 그 동기화를 끊어버리면? 입출력의 순서가 보장되지 않는 대신 속도가 빨라집니다.

또한 C++ 의 입출력은 쓰레드 안전성이 보장되지만 동기화를 끊어버리면 보장되지 않습니다.

 

아래의 두 코드는 동기화를 한 경우와 하지 않은 경우의 차이입니다.

ubuntu 환경에서 테스트하였고 (비주얼스튜디오에서는 다르게 나올 수 있습니다!!)

두 코드의 차이점은 동기화의 차이밖에 없습니다.

 

1. 동기화를 풀지 않을 경우

#include <iostream>
using namespace std;

int main()
{	
	for(int i = 0; i < 10; ++i) {
		cout << "cout\n";
		printf("printf\n");
	}
}

 

2. 동기화를 풀 경우

#include <iostream>
using namespace std;

int main()
{
	ios_base::sync_with_stdio(false);

	for (int i = 0; i < 10; ++i) {
		cout << "cout\n";
		printf("printf\n");
	}
}

위와 같이 같은 코드인데도 출력이 다릅니다.

 


다음으로

cin.tie(NULL);
cout.tie(NULL);

이 두줄은 cin 과 cout 의 묶음을 풀어버리는 코드입니다.

기본적으로 cin 과 cout은 묶여있고 묶여있으면 스트림이 각 입출력을 하기 전에 자동으로 버퍼를 비워줍니다.

코드를 예로 들어보겠습니다.

#include <iostream>
using namespace std;

int main()
{
	cin.tie(NULL);
	cout.tie(NULL);
	int num;
	cout << "enter num : ";
	cin >> num;
}

위의 코드가 묶음을 풀었을 경우에 "enter num : " 이 출력되기도 전에 cin이 먼저 실행될 수도 있다는 것이다.

 

추가적으로 개행 시 보통 endl 을 많이 사용하게 되는데 이 endl 의 경우 버퍼를 비워주는 역할도 수행하므로 "\n"을 사용하는 것보다 속도가 느립니다. 따라서 알고리즘을 할 때는 극한의 속도(?)를 위해 "\n" 사용을 생활화합시다.

 

결론

보통 알고리즘 문제를 풀 경우 싱글 쓰레드 환경이므로 sync_with_stdio(false) 를 해도 cin, scanf  cout, printf 를 섞어 사용하지 않는 이상 결과에 영향이 없고 속도가 매우 빨라진다.

cin.tie(NULL) cout.tie(NULL) 또한 알고리즘 문제는 화면에 보이는 순서가 크게 중요하지 않으므로 크게 지장이 없다.

 

 

요약

1. sync_with_stdio 사용할 거면 cout 과 printf 혼용 금지와 싱글쓰레드에서만 사용

2. cin.tie(NULL) cin.tie(NULL) 시에 cin cout 을 빠르게 혼용 시 순서가 뒤죽박죽

728x90
반응형

+ Recent posts