Buffer와 std::endl 그리고 \n
상황
N과 M 시리즈를 복기하던 중,
5번에서 시간초과가 발생했다
기존 풀이와 차이점은 std::endl
과 \n
밖에 없는데
그렇게 차이가 많이날까? 궁금해서 조사해봤다
스트림(Stream)과 버퍼(Buffer)
둘이 비교하기에 앞서, 먼저 버퍼(Buffer)에 대한 이해가 필요하다
이전 cin과 getline 글에서 스트림과 버퍼에 대해 간략하게 설명했지만
더 자세히 설명해보면
C++은 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림이라는 객체를 통해 흐름을 다룬다
스트림은 운영체제에 의해 생성되는 가상의 연결고리를 의미하며,
프로그램과 운영체제의 중간 매개자 역할을 한다
모든 입출력을 바이트의 흐름으로 간주하여 처리한다
즉, 어떤 장치와 프로그램 사이를 잇는 파이프와 같은 존재임
이 스트림 내부에는 버퍼라는 임시 메모리 공간을 가지고 있음
버퍼 사용 장점
문자를 키 하나를 누를때마다 바로 전달하는 것이 아니라, 한번에 묶어서 전달하므로,
전송시간이 적게 걸려 성능이 향상됨사용자가 문자를 잘못 입력한 경우 수정할 수 있음
입력 버퍼와 출력 버퍼
임시적으로 버퍼에 저장하는 것을 버퍼링(Buffering)이라 한다
버퍼는 FIFO(First-In First-Out) 방식으로 동작한다
사용자가 키를 누르면 해당 키의 입력 데이터가 입력 버퍼의 맨 뒤에 추가됨
프로그램에서 입력 함수를 호출할 때 마다, 입력 버퍼의 가장 앞에 있는 데이터가 제거됨
입출력 함수를 호출하면, 버퍼에서 데이터가 제거되므로, 같은 데이터를 두번 이상 읽을 수 없음
입출력 버퍼의 크기는 기본적으로 운영체제에 의해 설정되지만,
std::streambuf::setbuf()
함수과 같이 프로그램에서 직접 버퍼 크기를 설정할 수도 있음버퍼는 입력 버퍼와 출력 버퍼로 나뉜다
입력 버퍼
키보드와 같은 입력 장치로 부터 들어오는 데이터를 저장하는 공간
표준 입력 스트림인
std::cin
은 키보드 입력에 대한 버퍼링을 수행
1
2
3
cin.get(): 버퍼에서 하나의 문자를 읽어들입니다
cin.getline(): 버퍼에서 개행 문자까지 포함한 문자열을 읽어들입니다
cin.ignore(): 버퍼에서 특정 개수의 문자를 무시합니다
출력 버퍼
프로그램에서 출력하는 데이터를 일시적으로 저장하는 공간
std::cout
은 출력 버퍼를 가지고 있으며,std::endl
나std::flush
를 사용하거나, 버퍼가 가득 찼을 때 자동으로 버퍼를 비워서 출력
1
2
3
cout.put(): 버퍼에 하나의 문자를 저장합니다
cout.flush(): 버퍼에 저장된 데이터를 출력 장치로 전송합니다
cout.setf(): 버퍼링 방식 등 출력 형식을 설정합니다
std::ios_base::sync_with_stdio
알고리즘 문제 풀 때
std::ios_base::sync_with_stdio(false)
를 해주는 이유도기본적으로 C++ 표준 라이브러리는
iostream
라이브러리와C의
stdio
입출력 기능을 함께 사용할 수 있도록 동기화 되어있다std::ios_base::sync_with_stdio(false)
를 작성해주면C++의 입출력 스트림과 C표준 입출력 함수의 버퍼가 분리된다
C의 버퍼와 병행하여 사용할 수 없지만, 사용하는 버퍼의 수가 줄어 실행 속도가 빨라지게 되는 것!
그래서 이 문장을 사용하면, stdio를 사용하면 안되는 것임
그래서 std::endl과 ‘\n’의 차이는?
1. endl을 사용하여 버퍼를 비움
1
2
3
4
5
int main(void) {
std::cout << "hello" << std::endl;
sleep(5);
return (0);
}
위 예제는 1. hello를 출력하고, 2. 5초 대기 후 3. 프로그램이 종료된다
endl 에 의해 출력 버퍼가 비워진 후, sleep 에 들어가는 것
2. endl을 사용하지 않아 버퍼를 비우지 않음
1
2
3
4
5
int main(void) {
std::cout << "hello";
sleep(5);
return (0);
}
두번째 예제는 1. 5초 대기후, 2. hello를 출력후, 3. 프로그램이 종료된다
프로그램이 종료되면서 커널에 의해 버퍼를 강제로 비워지게 된다
즉, sleep 들어가기전에 버퍼에는 “hello”가 들어있지만 출력 함수가 호출되지 않아 버퍼에 남아있는 상태
std::endl
은 출력 버퍼를 비우고,\n
은 출력 버퍼를 비우지 않는다
버퍼를 비운다는 의미는, buffer에 저장 되었던 내용을 내보내는 것을 의미한다
즉, 버퍼를 비우지 않는다는게, 출력을 안한다는게 아니라,
버퍼에 가지고있느냐, 바로 출력하느냐의 차이였던 것이였다
예제 1번처럼 std::endl`을 통해 바로바로 flush 하느냐
예제 2번처럼 \n
처럼 버퍼에 차곡차곡 쌓아뒀다가,
프로그램이 종료시 커널에 의해 한번에 출력하느냐 차이였다
당연히 여러번 출력 함수를 호출하는것 보다는
한번 출력 함수를 호출하는것이 시간적으로 효율적이다
알고리즘 문제같은 경우, 입출력이 큰 상황에서 반복 수행해야할 때,
출력 버퍼를 비우는 작업이 많이 발생하여 시간 초과가 발생할 수 있는 것이였다