Logo
(追記) (追記ここまで)

[C++] tie 와 sync_with_stdio

머릿말

C++ 프로그램을 작성할 때 main 함수에 다음과 같은 문장을 쓰면 속도가 빨라진다고 알고 있는 사람이 많을 겁니다.

int main() {
 cin.tie(nullptr);
 ios_base::sync_with_stdio(false);
 /* 실제 프로그램 */
}

하지만 왜 빨라지는지 정확하게 알고 있는 사람은 생각보다 많지 않은 것 같습니다. 이 글에서는 이 두 문장이 하는 일에 대해 알아보려고 합니다.

basic_ios::tie(basic_ostream*)

tie는 자기 자신과 "묶여있는" 출력 스트림을 관리합니다.

  • 인자 없이 호출하면 현재 자신과 묶여있는 스트림을 반환합니다.
  • 인자를 넣고 호출하면 인자로 받은 스트림을 자신과 새로 묶고, 그 전에 자신과 묶여있었던 스트림을 반환합니다.

tie로 스트림을 묶었으면 자기 자신을 입출력 하기 전에 항상 묶여있는 스트림에 flush 연산을 수행합니다. flush 연산은 출력 버퍼에 보관하고 있었던 내용을 모두 출력하고 출력 버퍼를 비우는 연산으로, 매우 무거운 연산입니다.

기본적으로 cin 은 cout 와 묶여있습니다. 즉, cin 을 사용할 때 마다 cout 에 flush 연산이 발생합니다.

이 연산이 특히 문제가 되는 경우는 다음과 같습니다.

int main() {
 int t;
 cin >> t;
 for (int i = 0; i < t; i++) {
 int n;
 cin >> n;
 cout << calculate(n) << '\n';
 }
}

이 프로그램은 반복문 안에서 cin 과 cout 를 번갈아가며 사용하고 있습니다. 이럴 경우 결과값 하나를 출력할 때 마다 매번 flush 연산을 수행하게 됩니다. t의 값이 5자리수 정도만 되어도 이 프로그램은 시간 초과를 받기 쉽습니다.

BOJ의 채점 프로그램은 프로그램 수행이 모두 끝난 후 전체 출력 결과를 답안지와 비교합니다. 따라서 매 출력마다 flush 연산을 수행하는 것은 의미 없이 매우 비효율적인 연산을 반복하는 셈입니다. 기본적으로는 프로그램이 끝나기 전 맨 마지막에 한 번만 flush 를 수행하는 것으로 충분하며, 그나마 cout는 프로그램 종료 직전 flush 를 자동적으로 수행하기 때문에 따로 flush 를 수행할 필요가 전혀 없습니다.

(예외: 인터렉티브 태그가 붙어 있는 문제는 매 출력마다 반드시 flush 연산을 수행해야 합니다)

위와 같은 프로그램의 main 함수의 맨 앞에 cin.tie(nullptr); 를 넣으면 cin과 cout 를 번갈아가며 사용해도 flush 연산을 수행하지 않게 되면서 수행 시간을 상당히 줄일 수 있습니다.

ios_base::sync_with_stdio(bool)

C++ 에는 두 종류의 입출력 기능이 있습니다.

  • C로부터 물려받은 입출력: scanf, printf, getchar, putchar, ...
  • C++ 에서 추가된 입출력: cin, cout

기본적으로는 두 입출력 기능의 내부 버퍼를 동기화하고 있습니다. 내부 버퍼를 동기화하면 cin 과 scanf 를 번갈아가며 사용하더라도 항상 올바른 순서대로 읽을 수 있습니다. 단, 동기화 기능도 공짜는 아닌지라 약간의 연산 시간을 소모하게 됩니다.

ios_base::sync_with_stdio(false); 는 두 입출력 기능의 내부 버퍼 동기화를 비활성시키는 명령입니다. 이를 수행하면 더 이상 버퍼를 동기화하지 않기 때문에 수행 시간에 약간의 이득을 볼 수 있습니다.

다만, 이 명령을 수행한 후 cin 과 scanf 를 번갈아가며 사용하면 안 되며, cout 와 printf 도 마찬가지입니다. 내 컴퓨터에서는 멀쩡하게 보여도 다른 컴퓨터에서는 입출력이 엉망으로 뒤섞여 버릴 수 있습니다.

번외: endl

 cout << result << '\n';
 cout << result << endl;

화면에 보이는 결과는 똑같습니다: 결과를 출력한 뒤 줄을 넘깁니다. 하지만 내부적으로는 큰 차이가 있습니다.

'\n' 은 그냥 줄만 넘깁니다. 반면, endl 은 줄을 넘긴 후 flush 연산을 수행합니다.

앞에서 설명했듯이 flush 연산은 매우 무거운 연산이며, (인터렉티브 문제를 제외하면) 시간 낭비일 뿐입니다. 따라서 대부분의 경우 endl 대신 '\n'이 적당합니다.


(追記) (追記ここまで)

댓글 댓글 쓰기


검색

(追記) (追記ここまで)

출처

대학교 대회

  • 사업자 등록 번호: 541-88-00682
  • 대표자명: 최백준
  • 주소: 서울시 서초구 서초대로74길 29 서초파라곤 412호
  • 전화번호: 02-521-0487 (이메일로 연락 주세요)
  • 이메일: contacts@startlink.io
  • 통신판매신고번호: 제 2017-서울서초-2193 호

AltStyle によって変換されたページ (->オリジナル) /