c++로 문제를 풀 때 입출력을 무작정 cin, cout만 사용하면 시간제한에 걸리는 경우가 더러 있다.
자바에서 BufferedReader를 사용해 입력을 받아서 시간초과를 해결하듯 c++에도 입출력에서 시간을 줄이는 방법이 존재하며, 이 것을 정리하고자 한다.
1. ios_base :: sync_with_stdio(false); 사용
기본적으로 c++에서는 입출력 시 cin, cout을 사용하는데, c언어 스타일의 printf, puts, putchar, scanf, gets, getchar도 사용 가능하다.
이유는 간단하다. 우리가 맨 위에 항상 적는 #include <iostream>에 다 있기 때문이다.
객체지향 스타일을 갖추며 c언어와의 호환성을 중요시하는 언어가 c++이기에 그런것으로 보인다.
기본적으로는 c++에서 c언어의 stdio와 c++의 iostream이 동기화되어있다.
stdio와 iostream의 버퍼를 모두 사용하기에 딜레이가 더 생기는 것이다.
그러므로 해당 구문을 사용해서 false시켜주면 stdio와 iostream의 동기화가 비활성화 되게 된다.
비활성화하게 되면 c++의 iostream 버퍼만 사용하므로 사용하는 버퍼가 줄어들어서 속도가 빨라지게 되는 것이다.
내가 청소를 한다고 하면 넓은곳을 할 때와 상대적으로 좁은곳을 할 때 어느것이 빠른지와 비슷한 맥락이다.
하지만 사용하는 버퍼가 줄어들면서 얻는 장점과 단점도 존재한다.
장점: 동기화를 해제하며 c++만의 독립적인 버퍼를 사용하기에 입출력 속도가 많이 빨라진다.
단점: 동기화된 상태에서는 입출력 순서가 예상한것과 일치하게 되지만, 비활성화 상태에서는 순서를 보장할 수 없다.
정확히는 해당 현상이 멀티스레드에서 나타나는 현상이다.
알고리즘 문제를 푸는 상황은 멀티스레드가 아닌 싱글 스레드이기에 속도 향상을 위해서 사용하는 것이다.
해당 구문을 사용할 때의 주의점도 존재한다.
c스타일과 c++스타일의 버퍼가 분리되었기에, c++의 cin, cout과 c의 scanf, gets, getchar, printf, puts, putchar를 혼용해서 사용하면 예상한 것과 다른 이상한 결과가 나올 수 있다.
2. cin.tie(NULL);
해당 구문을 사용하면 cin과 cout의 묶음을 풀어준다.
입출력 작업을 수행할 때 데이터의 효율적인 이동과 관리를 위해 버퍼라는 임시 저장소를 사용한다.
만약 어떤 변수에 문자값을 입력받는다고 하면 바로 변수에 저장되는게 아니라 입력한 데이터가 입력버퍼에 저장되고, 이 저장된 값을 버퍼에서 읽어서 변수에 저장하게 된다.
기본적으로 cin과 cout은 서로 묶여있고, 묶여있는 스트림들은 각 스트림에서 각 입출력 작업을 하기 전에 자동으로 버퍼를 비워주는 것을 보장한다.
스트림이란 입력이나 출력 시 해당 데이터가 흘러가는 모양새를 말한다.
예시를 하나 들어보자
int input;
cout << "번호를 입력하세요: ";
cin >> input;
cin과 cout이 서로 묶여있다면 입, 출력 시 자동으로 버퍼를 비운다.
즉, cout으로 "번호를 입력하세요: "라는 데이터를 버퍼에 담고
다음 줄에서 cin으로 input에 입력을 받는다는 코드가 있기에,
입력을 받기 전에 버퍼의 "번호를 입력하세요: "라는 내용을 버퍼에서 자동으로 비워버린다.(flush)
이후 input에 사용자가 입력한 값을 입력받게 된다.
이렇게 입력과 출력이 번갈아서 자주 등장한다면 등장할 때마다 버퍼를 자동으로 비우게 되는데, 이 버퍼를 비우는 작업이 속도가 오래 걸린다.
따라서 cin.tie(NULL);구문을 추가하게 된다면 cin과 cout의 묶음이 풀리게 되며 코드의 순서대로 출력과 입력이 되지 않고 입력을 받은 후에 "번호를 입력하세요: "가 출력된다.
(출력 명령을 따로 내리거나 버퍼가 가득 찬 경우에 버퍼를 비우고 출력)
(입력의 경우 입력 한 문자마다 버퍼에 넣고 버퍼가 가득 차거나 개행문자가 들어오면 한 번에 프로그램으로 전달)
순서대로 작업이 자동으로 이뤄지지도 않는데 왜 저걸 쓰냐? 라고 하면 위에서도 말했듯이 버퍼를 자동으로 비우는데, 이 버퍼를 비우는 작업이 속도를 많이 잡아먹는다.
알고리즘 문제를 풀다보면 입력받고 해당 값으로 문제를 해결하고 출력하는 것을 여러 번 반복하는 문제가 있다.
한 예시로
왼쪽이 실행 결과이고, 오른쪽이 코드이다.
처음 반복횟수를 입력받고 그 횟수만큼 계속 숫자 2개를 입력받아서 출력하는 코드이다.
실행 결과를 보면 반복횟수 이후의 입력이 3 3, 6 6, 7 7, 10 32, 1 1인데, 입력 한 번 할 때마다 출력된다.
이럴 경우 입력받고 출력하고 자동으로 버퍼 비우고(flush, 출력) 입력받고 출력하고 자동으로 버퍼비우고~~
이렇게 반복되는데, 이렇게 자동으로 버퍼를 비우는 것이 시간을 잡아먹는 것이다.
(사실 입력에서 버퍼에 들어가는건 문자이다. 숫자를 입력받으면 버퍼를 거치지 않고 바로 들어가기에 해당 코드에서는 출력문 이후 입력문일 때 자동으로 버퍼를 비운다.)
그럼 해결하려면 어떻게 해야하느냐?
위에서 언급한 구문 두 개를 추가하기만 하면 된다.
그럼 입력받은대로 출력하지 않고 리턴을 만나면 프로그램이 종료하며 모두 출력하고 종료된다.
이렇게 되면 자동으로 버퍼를 비우는 과정을 확 줄였기에 시간이 단축되는 것이다.
3. 결론
알고리즘 문제를 풀 때는 보통 멀티스레드를 구현하지도 않기 때문에 왠만하면 첫 번째 구문을 추가하는것이 좋다.
하지만 c스타일과 c++스타일의 동기화를 해제하기에 둘의 입출력문을 혼용하지 않도록 주의한다.
입력과 출력을 번갈아가면서 수행하는 문제의 경우 두 번째 구문을 추가해서 사용하면 시간단축의 효과가 있다.
4. 추가사항
cout을 사용하면서 개행(줄바꿈)을 할 때 간편하게 사용할 수 있는데 endl이다.
endl을 사용하면 개행을 함과 동시에 출력 버퍼를 비우는 역할도 같이 수행하는데, 버퍼를 비우는 것이 시간을 잡아먹는다고 위에서 설명했다.
그렇기에 예시 코드처럼 endl보다는 '\n'을 사용하는 것이 좋다.
만약 printf와 scanf를 사용한다면 위의 내용은 해당하지 않는다.
c언어 스타일의 입출력 구문은 그 자체만으로 사용해도 속도가 빠르기 때문이다.
'개발 > C, C++' 카테고리의 다른 글
[C++] 입력받기(cin, get, getline) (0) | 2022.04.18 |
---|---|
[C++] 배열 최대값, 최소값 구하기 (0) | 2022.04.18 |
[C++] 배열 정렬하기 (0) | 2022.04.18 |
온라인저지(백준) 문제 풀이 시 주의사항 (0) | 2022.04.08 |
[C++] 클래스와 생성자 (0) | 2022.04.04 |