백준 문제를 풀다가 알게 된 사실입니다.
https://www.acmicpc.net/problem/1753
해당 1753 문제를 풀던 도중이었죠.
해당 문제는 edge 가 최대 300000 개로 다른 문제들에 비해 입출력이 상당히 많은 문제입니다.
대충 코드를 작성하고 제출을 했는데 시간 초과가 발생했습니다.
그래서 내가 뭔가 알고리즘을 잘못 짠 건가 해서 구글링을 하던 중 코드가 구조상 거의 동일한데
저 코드는 되고 내 코드는 안 되는 이유를 찾았습니다.
결론부터 말하자면 바로 3줄의 차이였습니다.
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
우선 C/C++ 의 여러 가지 입출력의 수행 시간 차이를 알아보겠습니다.
위의 사진을 보면 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 을 빠르게 혼용 시 순서가 뒤죽박죽
'컴퓨터공학 > 간단한 읽을거리' 카테고리의 다른 글
std::vector / std::stack / std::deque 의 속도 차이 (0) | 2021.07.16 |
---|