sync_with_stdio, cin.tie, '\n'
코딩 문제를 풀다 보면 알고리즘도 빠른 거 잘 썼고 특별히 느릴 이유가 없어 보이는데 채점 결과를 보면 누군가는 막 10 ms 미만인데 내 코드는 수십 ms가 나오는데 이유를 모르겠다며 고통을 호소하는 분들이 가끔 있습니다.
혹시 C++를 쓰신다면 가장 먼저 의심해야 하는 건 스트림 관련된 문제입니다. 프로그램과 환경(운영체제) 사이에는 입출력 통신을 위한 표준 스트림이라는 게 정의되어 있습니다. 예를 들어서 C에서는 표준 라이브러리인 stdio.h에 stdin, stdout, stderr라는 입력, 출력, 오류를 처리하기 위한 파일 포인터가 정의되어 있고, 이것들을 표준 스트림으로 사용합니다. C++에는 <iostream> 라이브러리에 std::cin, std::cout, std::cerr라는 입력, 출력, 오류 메시지용 스트림이 정의되어 있습니다. 그 외에 로그 기록을 위한 용도로 쓰이는 std::clog도 있으며, 이 네 개의 스트림의 와이드 문자 버전인 std::wcin, std::wcout, std::wcerr, std::wclog도 있어서 총 8개의 표준 스트림이 있습니다.
C++의 입출력 스트림 라이브러리인 <iostream>에는 ios::sync_with_stdio라는 함수가 있습니다. 이 함수는 C++의 스트림(std::cin, std::cout 등)과 C의 스트림(stdin, stdout 등) 사이의 동기화를 관리합니다. 기본적으로, C++ 프로그램은 시작할 때 C++ 스트림과 C 스트림 사이에 동기화가 설정되어 있습니다. 이는 두 입출력 시스템이 서로 상호작용하고 올바르게 동작하도록 보장합니다. 예를 들어, printf와 std::cout을 혼용하여 사용할 경우, 출력이 예상대로 순서대로 나타나도록 도와줍니다.
그러나 이 동기화를 하려면 성능이 떨어지게 됩니다. 특히, 입출력이 프로그램의 성능에 큰 영향을 미칠 때 (예를 들어, 대량의 데이터를 빠르게 읽거나 쓸 때), 동기화로 인해 성능 저하가 발생할 수 있습니다. 코딩 테스트에서는 테스트 케이스를 입력하는 작업이 꽤 다양하게 일어나는데, 이 때 동기화를 위해 시간을 많이 잡아먹게 되면 그만큼 전체 실행 시간이 느려지게 되고, 감점 요인이 될 수 있습니다.
이런 경우에 ios::sync_with_stdio(false)를 호출하면 C++ 스트림과 C 스트림 사이의 동기화가 해제됩니다. 그래서 속도가 빨라지지만 대신 C++ 스트림과 C 스트림을 혼합하여 사용하는 경우에는 동기화가 안 되기 때문에 입력/출력 순서가 뒤바뀔 위험이 있습니다.
또한 cin과 cout을 서로 묶어두는 게 기본인데, 이것도 std::cin.tie(NULL) 구문으로 풀어주면 강제로 버퍼를 플러시하지 않아도 되어서 속도가 많이 빨라집니다.
한 가지 더 덧붙이자면, 줄바꿈 문자를 출력할 때 std::endl을 쓰면 줄을 바꾼 다음 버퍼를 강제로 비우는데요, 이 작업을 처리하기 위해 시간이 걸리기 때문에 성능이 중요할 때는 std::endl보다는 그냥 '\n'을 쓰는 게 훨씬 낫습니다.
그래서 C++로 코딩 문제를 풀 때는 다음과 같이 세 가지를 기억해 주세요.
1. 다음 두 줄의 코드를 추가해서 스트림 동기화를 해제하고 버퍼를 강제로 플러시하지 않도록 설정합니다.
ios::sync_with_stdio(false);
std::cin.tie(NULL);
2. C 스트림과 C++ 스트림을 혼용하지 않습니다.
cin, cout만 씁니다. scanf, printf를 섞어쓰면 순서가 뒤죽박죽이 될 수 있습니다.
3. 줄을 바꿀 때는 std::endl 말고 '\n'을 씁니다.
이러면 C++에서 cin, cout을 쓰면서도 그냥 C에서 scanf, printf를 쓰는 것 못지 않게 빠르게 입출력할 수 있습니다.