stdout을 플러시하지 않으려는 이유는 무엇입니까?

Codereview 에서 질문을 우연히 발견했으며 한 답변에서 피드백은 std::endl는 스트림을 플러시하므로 피하세요. 전체 인용문은 다음과 같습니다.

일반적으로 std::endl를 피하는 것이 좋습니다. 개행을 원하지만 스트림을 플러시하지 않으려는 경우가 많으므로 일반적으로 \ n을 작성하는 것이 좋습니다. 드물지만 실제로 플러시를 원할 경우 명시 적으로 수행하십시오. std::cout << "\n" << std::flush;.

포스터는 게시물이나 댓글에서 설명하지 마세요. 제 질문은 간단합니다.

왜 플러싱을 피하고 싶습니까?

저를 더욱 궁금하게 만든 것은 포스터에 “당신이 플러시하고 싶은 것은 매우 드물다”라고 쓰여 있다는 것이 었습니다. 나는 당신이 플러싱을 피하고 싶은 상황을 상상하는 데 문제가 없지만 일반적으로 당신이 원할 것이라고 생각했습니다. 줄 바꿈을 인쇄 할 때 플러시됩니다. 결국 “std::endl가 처음에 플러시되는 이유가 아닌가요?

마지막 투표에 대해 미리 댓글을 달기 만하면됩니다.

나는이 의견을 근거로 생각하지 않습니다. 당신이 선호하는 것은 의견에 근거 할 수 있지만 고려해야 할 객관적인 이유가 있습니다. 지금까지의 답변이이를 증명합니다. 플러싱은 성능에 영향을줍니다.

댓글

답변

간단하고 간단한 답변 std::endl를 사용하면 출력이 크게 느려질 수 있습니다. 사실 저는 C ++ iostream이 C 스타일 I / O보다 상당히 느리다는 개념의 대부분 std::endl에 있다고 합리적으로 확신합니다.

예를 들어 다음과 같은 프로그램을 생각해보십시오.

#include <iostream> #include <string> #include <sstream> #include <time.h> #include <iomanip> #include <algorithm> #include <iterator> #include <stdio.h> char fmt[] = "%s\n"; static const int count = 3000000; static char const *const string = "This is a string."; static std::string s = std::string(string) + "\n"; void show_time(void (*f)(), char const *caption) { clock_t start = clock(); f(); clock_t ticks = clock()-start; std::cerr << std::setw(30) << caption << ": " << (double)ticks/CLOCKS_PER_SEC << "\n"; } void use_printf() { for (int i=0; i<count; i++) printf(fmt, string); } void use_puts() { for (int i=0; i<count; i++) puts(string); } void use_cout() { for (int i=0; i<count; i++) std::cout << string << "\n"; } void use_cout_unsync() { std::cout.sync_with_stdio(false); for (int i=0; i<count; i++) std::cout << string << "\n"; std::cout.sync_with_stdio(true); } void use_stringstream() { std::stringstream temp; for (int i=0; i<count; i++) temp << string << "\n"; std::cout << temp.str(); } void use_endl() { for (int i=0; i<count; i++) std::cout << string << std::endl; } void use_fill_n() { std::fill_n(std::ostream_iterator<char const *>(std::cout, "\n"), count, string); } void use_write() { for (int i = 0; i < count; i++) std::cout.write(s.data(), s.size()); } int main() { show_time(use_printf, "Time using printf"); show_time(use_puts, "Time using puts"); show_time(use_cout, "Time using cout (synced)"); show_time(use_cout_unsync, "Time using cout (un-synced)"); show_time(use_stringstream, "Time using stringstream"); show_time(use_endl, "Time using endl"); show_time(use_fill_n, "Time using fill_n"); show_time(use_write, "Time using write"); return 0; } 

표준 출력이 파일로 리디렉션되면 다음과 같은 결과가 생성됩니다.

p>

 Time using printf: 0.208539 Time using puts: 0.103065 Time using cout (synced): 0.241377 Time using cout (un-synced): 0.181853 Time using stringstream: 0.223617 Time using endl: 4.32881 Time using fill_n: 0.209951 Time using write: 0.102781 

std::endl를 사용하면이 경우 프로그램 속도가 약 20 배 느려졌습니다. 더 짧은 문자열을 작성하면 속도 저하가 더 클 수 있습니다.

정말로 스트림을 수동으로 플러시하고 싶은 경우가 몇 가지 있지만 솔직히 거의 그 사이에 있습니다.

대부분 스트림을 플러시해야하는 경우 (예 : 프롬프트를 인쇄 한 다음 입력을 기다립니다) “std::tie 및 / 또는 std::sync_with_stdio이를 방지합니다.

그렇게하면 정말 비정상적인 상황이 극소수에 불과합니다. 스트림을 수동으로 플러시하는 좋은 이유입니다. 이러한 경우는 드물기 때문에 std::flush를 사용할 가치가있을만큼 코드를 읽는 모든 사람이 의도적으로 스트림을 플러시하고 있다는 것을 분명히 알 수 있습니다. 또한 이것이 스트림을 플러시 할 때 드문 경우에 해당하는지에 대한 의견이있을 수 있습니다.

답변

프로세스가 출력을 생성 할 때마다 실제로 작업을 수행하는 함수를 호출해야합니다. 대부분의 경우 해당 함수는 궁극적으로 write(2)입니다. 멀티 태스킹 운영 체제에서 write()에 대한 호출은 커널에 트랩되어 프로세스를 중지하고 I / O를 처리하고 막힘이 해결되는 동안 다른 작업을 수행해야합니다. 준비된 대기열에 저장하고 시간이되면 다시 실행합니다. 총체적으로 모든 활동을 시스템 호출 오버 헤드 라고 부를 수 있습니다. 그게 많이 들리면 그렇습니다.

소량의 데이터를 기록하거나 버퍼가 전혀없는 후 버퍼링 된 스트림 *을 플러시하면 매번 수행 할 때마다 오버 헤드가 발생합니다.

 1\n (System call that writes two bytes) 2\n (System call that writes two bytes) 3\n (System call that writes two bytes) 4\n (System call that writes two bytes) 5\n (System call that writes two bytes)  

이것은 누군가가 그것이 불타고 있다는 것을 알아낼 때까지 아주 초기에 이루어졌습니다. 많은 시스템 시간. 버퍼가 가득 차거나 프로그램이 즉시 전송해야한다고 결정할 때까지 버퍼에 출력을 누적하여 오버 헤드를 줄일 수 있습니다.(보거나 소비해야하는 산발적으로 출력을 생성하는 경우 후자를 수행 할 수 있습니다.) 각 줄 끝에서 플러시를 피하면 시스템 호출 수와 발생하는 오버 헤드가 줄어 듭니다.

 1\n 2\n 3\n 4\n 5\n (Flush) (System call that writes ten bytes)  

* 표준 출력의 개념은 프로세스와 관련된 파일 설명자입니다. C, C ++ 등에서 정의한 stdout와는 다릅니다.이 식별자는 완전히 사용자 영역에 거주하고있는 버퍼링 된 스트림의 구현을위한 식별자입니다. 표준 출력. write() 시스템 호출은 버퍼링되지 않습니다.

댓글

  • 플러시 여부 및시기 출력이 사용되는 컨텍스트에 따라 다릅니다. 처리량 지향 프로그램은 버퍼가 가득 찼을 때만 플러시해야합니다. 지연 시간에 민감한 프로그램은 더 자주 플러시해야합니다. 예를 들어 출력이 콘솔로 이동하는 경우 각 줄 바꿈 후에 플러시됩니다. 일부 입력 프롬프트를 표시하는 대화 형 프로그램은 줄이 아직 가득 차지 않았더라도 즉시 플러시되어야합니다.
  • " 출력이 콘솔로 이동하면 각 줄 바꿈 후에도 계속 플러시됩니다. " 참이지만 출력이 콘솔로 이동하면 각 줄 바꿈 후에 자동 플러시가 있습니다. 명시 적으로 할 필요가 없습니다.
  • @amon 적시에 플러시하는 것, 즉 입력을 요청하기 전에 지연없이 충분한 연속 출력을 합치기를 원할 것입니다. 물론 출력을 표시하는 데 너무 많은 지연이 있거나 최적화에 너무 많은 작업을 투자하는 것보다 너무 자주 플러시하는 것이 좋습니다 …
  • 버퍼 러가 때때로 사용하는 한 가지 트릭은 a를 다시 / 시작하는 것입니다. 새로운 항목이 오면 타이머를, 타이머가 만료되면 플러시됩니다.

답변

플러시를 피해야하는 이유 :

운영 체제가 비교적 많은 양의 데이터를 처리 할 수있을 때 IO가 가장 잘 작동하기 때문입니다. 소량의 데이터를 정기적으로 플러시하면 속도가 매우 느려지는 경우도 있습니다.

수동으로 플러시하면 안되는 이유 :

대부분의 사용 사례를 다루는 자동 플러시가 있습니다. 예를 들어, 프로그램이 콘솔에 쓰는 경우 시스템은 기본적으로 모든 줄 바꿈 후에 플러시합니다. 또는 파일에 쓰는 경우 한 번에 쓸 수있는 충분한 데이터가있을 때와 파일이 닫힐 때 데이터가 기록됩니다.

수동으로 플러시해야합니다.

즉시 출력을 명시 적으로 업데이트해야하는 경우. 예 : 현재 줄을 반복적으로 덮어 쓰는 스피너 또는 진행률 표시 줄을 만드는 경우. 또는 파일로 출력하고 특정 순간에 파일을 업데이트하려는 경우.

코멘트

  • 시스템은 사용자 버퍼를 플러시 할 수 없습니다. 아니면 " 시스템 "에도 라이브러리, 특히 표준 라이브러리를 포함합니까? 물론 stdinstdout는 둘 다 동일한 콘솔을 사용하는 경우 일반적으로 결합됩니다.
  • 예, 저는 ' 사용자 공간 라이브러리와 커널 공간간에 사물이 분할되는 방식에 대해 너무 자세하게 설명하는 것이 좋지 않다고 생각했습니다.

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다