입력-출력 속도 최적화
문제상황
C++에서 cin, cout을 사용해 대량의 데이터를 처리할 때, 예상보다 시간이 오래 걸릴 수 있다. 특히 백준사이트같이 시간제한이 빡빡히 걸려있는경우 시간초과와 연결되기때문에 입출력 속도를 개선하는 방법이 중요하다.
이번 포스팅에서는 입출력속도가 느려지는 이유와 최적화 하는 방법에대해 포스팅한다.
0. C / C++에서 시간차이가 발생하는 이유
c와 c++에서는 비슷한 기능을하는 출력함수 printf()
와 cin()
가 있다. 당장 사용해보면 사용편의성 외에는 큰점을 느끼지못하지만 막상 백준에서 알고리즘 문제를 푸는과정에서 cin()
으로는 시간 초과가 나는 문제가 printf()
로 해결되는 경우가 있다. 두 함수를 비교해서 어디서 속도 차이가 있는지 비교해보려한다.
int printf ( const char * format, ... ); // Proto type
printf ("Characters: %c %c \n", 'a', 65); // Usage
printf()
의 동작은 매우 단순하게 이루어진다, 그저 사용자가 지정한 포맷대로 출력할 뿐이다. cin()
처럼 사용자의 편의성을 위해 타입을 추론하는 과정들이 없다.
그렇게 때문에 매우 빠르게 동작할 수 있다. 위의 예시 코드를 보면 타입에 대해 사용자가 하나하나 포맷을 지정하는걸 볼 수 있다.
이제 아래 c++의 cout()
의 코드를 봐보자
extern ostream cout;; // Proto type
int a = 1; // 사용자가 입력한 값을 a에 저장
cout << a << endl; // a의 값을 출력
위의 printf()
와 비교하였을때, 사용 방법이 더 간단하고 직관적인걸 알 수 있다. 그만큼 사용편의를 위해 타입을 추론하는 과정이 들어가 있기 때문에 비용이 소모된다.
이러한 추론과정때문에 당연히 더 많은 자원이 소모되지만 이는 컴파일 타임에서 이루어지기 때문에 포스팅하려는 입출력 속도 최적화와는 관계가 없다.
속도가 차이나는 이유들을 많지만 이번 포스팅에서는 속도최적화가 가능한 요소들을 소개하려한다.
0-1. 동기화 여부
C++의 입출력함수는 C의 입출력함수와 자유롭게 혼재해서 사용이 가능하도록 동기화 되어있다.
동기화 되어있다는것은 C++ iostream과 stdio가 같은 버퍼를 사용하도록 강제것이다. 이러한 동기화과정에서 추가적인 처리가 발생한다.
- C++ stream을 호출할때 C의 stdio와 연동확인
- 버퍼를 공유하기위해 스트림을 잠그고 동기화 관리
0-2. flush
버퍼를 비우는 flush 과정에서도 속도차이가 발생할 수 있다.
C++ 입출력함수에서 자주일어나는 flush 작업은 크게 2가지가 있다.
cin()
이 동작시 입출력이 꼬이는것을 방지하기위해 flush가 일어나는경우std::endl
입력시 flush가 일어난다. 줄바꿈과 같이 flush가 작동한다.
1. 동기화를 임의로 끊어주는 함수
bool sync_with_stdio (bool sync = true); // Proto type
ios::sync_with_stdio(0); // Usage
C의 stdio와 C++의 stream의 동기화를 끊어주는 함수이다.
동기화 작업은 추가적인 오버헤드를 발생시키므로 동기화를 끊어 독립적인 버퍼를 사용하게하면 성능이 향상된다.
특히 많은 입출력작업이 있는경우 이러한 동기화를 끊어주면 속도가 많이 향상된다.
단, 동기화를 끄고 여러 스레드에서 동일한 스트림에 접근할때, 데이터레이스가 발생할 수 있다.
2. Flush
cin.tie(0); // Usage
cin()
과cout()
은 서로 묶여있어cin()
이 호출될때마다 자동으로cout()
을 flush하게 되어있는데, 이것을 임의적으로 해제하여 성능을 개선할 수 있다.std::endl
대신 줄바꿈문자(\n)를 사용함으로써 flush를 줄여 속도를 높일 수 있다.