왜 C ++에서 stdin에서 행을 읽는 것이 Python보다 속도가 느립니까?


1839

파이썬과 C ++을 사용하여 stdin에서 문자열 입력 줄을 읽는 것을 비교하고 싶었고 C ++ 코드가 동등한 파이썬 코드보다 느린 속도로 실행되는 것을보고 충격을 받았습니다. 내 C ++이 녹슨 상태이고 아직 전문가 Pythonista가 아니기 때문에 내가 잘못하고 있거나 잘못 이해하고 있는지 알려주십시오.


(TLDR 답변 : 진술을 포함 cin.sync_with_stdio(false)하거나 fgets대신 사용하십시오.

TLDR 결과 : 내 질문의 맨 아래로 스크롤하여 표를보십시오.)


C ++ 코드 :

#include <iostream>
#include <time.h>

using namespace std;

int main() {
    string input_line;
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    while (cin) {
        getline(cin, input_line);
        if (!cin.eof())
            line_count++;
    };

    sec = (int) time(NULL) - start;
    cerr << "Read " << line_count << " lines in " << sec << " seconds.";
    if (sec > 0) {
        lps = line_count / sec;
        cerr << " LPS: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

// Compiled with:
// g++ -O3 -o readline_test_cpp foo.cpp

파이썬 동등 물 :

#!/usr/bin/env python
import time
import sys

count = 0
start = time.time()

for line in  sys.stdin:
    count += 1

delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
       lines_per_sec))

내 결과는 다음과 같습니다.

$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889

$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000

Mac OS X v10.6.8 (Snow Leopard) 및 Linux 2.6.32 (Red Hat Linux 6.2)에서이 작업을 시도했습니다. 전자는 MacBook Pro이고 후자는 매우 강력한 서버이며 이것이 너무 적합하지는 않습니다.

$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP:   Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in  1 seconds. LPS: 5570000

초소형 벤치 마크 부록 및 요약

완벽을 기하기 위해 동일한 상자의 동일한 파일에 대한 읽기 속도를 원래 (동기화 된) C ++ 코드로 업데이트한다고 생각했습니다. 다시 한 번, 이것은 빠른 디스크의 100M 라인 파일 용입니다. 다음은 몇 가지 솔루션 / 접근 방식을 사용한 비교입니다.

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714
wc (not fair comparison)  54,644,808

14
테스트를 여러 번 실행 했습니까? 디스크 캐시 문제 일 수 있습니다.
본 카토

9
@ JJC : 두 가지 가능성이 있습니다 (David가 제안한 캐싱 문제를 제거했다고 가정) : 1) <iostream>성능이 저하됩니다. 처음이 아닙니다. 2) 파이썬은 for 루프에서 데이터를 사용하지 않기 때문에 데이터를 복사하지 않을 정도로 영리합니다. 당신은 사용하려고 다시 테스트 할 수 scanfchar[]. 또는 문자열로 무언가를 수행하도록 루프를 다시 작성해 볼 수 있습니다 (예 : 다섯 번째 문자를 유지하고 결과에 연결).
JN

15
문제는 stdio와의 동기화입니다. 내 답변을 참조하십시오.
본 카토

19
아무도 왜 C ++로 추가 라인을 얻는 지 언급하지 않은 것 같습니다 : 테스트하지 마십시오 cin.eof()!! getline전화를 'if` 문에 넣습니다 .
Xeo

21
wc -l한 번에 두 줄 이상 스트림을 읽으므로 빠릅니다 ( fread(stdin)/memchr('\n')조합 일 수 있음 ). 파이썬 결과는 같은 순서로되어 있습니다.wc-l.py
jfs

답변:


1644

기본적 cin으로 stdio와 동기화되므로 입력 버퍼링을 피할 수 있습니다. 이것을 메인 상단에 추가하면 성능이 훨씬 향상됩니다.

std::ios_base::sync_with_stdio(false);

일반적으로 입력 스트림이 버퍼링되면 한 번에 하나의 문자를 읽는 대신 스트림을 더 큰 청크로 읽습니다. 이는 일반적으로 상대적으로 비싼 시스템 호출 수를 줄입니다. 그러나 FILE*기반 stdioiostreams종종 별도의 구현과 별도의 버퍼가 있으므로 둘 다 함께 사용하면 문제가 발생할 수 있습니다. 예를 들면 다음과 같습니다.

int myvalue1;
cin >> myvalue1;
int myvalue2;
scanf("%d",&myvalue2);

cin실제로 필요한 것보다 많은 입력을 읽은 경우 scanf자체 독립 버퍼가 있는 함수에 두 번째 정수 값을 사용할 수 없습니다 . 예기치 않은 결과가 발생할 수 있습니다.

이를 피하기 위해 기본적으로 스트림은와 동기화됩니다 stdio. 이를 달성하는 한 가지 일반적인 방법은 함수를 cin사용하여 필요에 따라 각 문자를 한 번에 하나씩 읽는 stdio것입니다. 불행히도 이로 인해 많은 오버 헤드가 발생합니다. 적은 양의 입력의 경우 큰 문제는 아니지만 수백만 줄을 읽을 때는 성능이 저하됩니다.

다행히도 라이브러리 디자이너는 수행중인 작업을 알고있는 경우 성능을 향상시키기 위해이 기능을 비활성화 할 수 있어야하므로 sync_with_stdio방법 을 제공했습니다 .


142
맨 위에 있어야합니다. 거의 확실합니다. 대답은 읽기를 fscanf호출 로 대체하는 데 거짓말을 할 수 없습니다 . 왜냐하면 파이썬만큼 많은 작업을 수행하지 않기 때문입니다. 파이썬은 문자열에 메모리를 할당해야합니다. 기존 할당이 C ++ 접근 방식과 똑같이 적절하지 않은 것으로 간주되므로 여러 번 가능합니다 std::string. 이 작업은 거의 확실하게 I / O 바운드이며 std::stringC ++에서 객체 를 생성 하거나 <iostream>자체적으로 사용 하는 비용에 대해 너무 많은 FUD 가 있습니다.
Karl Knechtel

51
예, 원래 while 루프 바로 위에이 줄을 추가하면 코드가 파이썬을 능가합니다. 결과를 최종 편집으로 게시하려고합니다. 다시 감사합니다!
JJC

6
예, 이것은 실제로 cout, cerr 및 clog에도 적용됩니다.
본 카토

2
cout, cin, cerr 및 clog를 더 빨리 만들려면 std :: ios_base :: sync_with_stdio (false);
01100110

56
sync_with_stdio()고정 부재 함수이며, 어떤 스트림 오브젝트에서이 함수의 호출 (예를 들어, cin또는 동기화를위한 OFF)를 전환 모든 표준 iostream 개체.
존 즈 빙크

170

호기심 때문에 후드에서 어떤 일이 발생하는지 살펴 보았고 각 테스트에서 dtruss / strace 를 사용 했습니다 .

C ++

./a.out < in
Saw 6512403 lines in 8 seconds.  Crunch speed: 814050

syscalls sudo dtruss -c ./a.out < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            6
pread                                           8
mprotect                                       17
mmap                                           22
stat64                                         30
read_nocancel                               25958

파이썬

./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402

syscalls sudo dtruss -c ./a.py < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            5
pread                                           8
mprotect                                       17
mmap                                           21
stat64                                         29

159

나는 몇 년 뒤에 있지만

원본 게시물의 '편집 4/5/6'에서 구성을 사용하고 있습니다.

$ /usr/bin/time cat big_file | program_to_benchmark

이것은 몇 가지 다른 방식으로 잘못되었습니다.

  1. 실제로 cat벤치 마크가 아닌 의 실행시기를 정하고 있습니다. 표시되는 'user'및 'sys'CPU 사용량 timecat벤치마킹 된 프로그램이 아니라의 CPU 사용량 입니다. 더 나쁜 것은 '실시간'도 반드시 정확하지는 않습니다. cat로컬 OS의 파이프 라인 구현 및 구현에 따라 cat최종 거대한 버퍼 를 작성하고 리더 프로세스가 작업을 마치기 훨씬 전에 종료 될 수 있습니다.

  2. 사용 cat은 불필요하며 실제로는 비생산적입니다. 움직이는 부분을 추가하고 있습니다. 충분히 오래된 시스템 (예 : 단일 CPU를 사용하고 특정 세대 컴퓨터에서는 CPU보다 I / O가 더 빠름) cat을 실행 한 경우 실행중인 사실만으로도 결과가 실제로 색상으로 표시 될 수 있습니다. 또한 입력 및 출력 버퍼링 및 기타 처리 cat가 수행 할 수있는 작업에 종속됩니다 . ( 랜들 슈왈츠 (Randal Schwartz) 였다면 ' 이것은 고양이의 쓸모없는 사용' 상을 받게 될 것입니다.

더 나은 구성은 다음과 같습니다.

$ /usr/bin/time program_to_benchmark < big_file

이 성명에서 그것은이다 (사실에, 당신의 프로그램에 전달, big_file 열리고 time이미 열린 파일 기술자로 다음 서브 프로세스로 프로그램을 실행한다). 파일 읽기의 100 %는 벤치마킹하려는 프로그램의 책임입니다. 이것은 가짜 합병증없이 성능을 실제로 읽습니다.

나는 두 가지 가능성이 있지만 실제로 잘못된 '수정'도 언급 할 수 있다고 언급 할 것입니다 (그러나 원래 게시물에서 잘못된 것이 아니기 때문에 다르게 번호를 매 깁니다).

A. 프로그램의 타이밍 만 지정하여이를 '수정'할 수 있습니다.

$ cat big_file | /usr/bin/time program_to_benchmark

B. 또는 전체 파이프 라인 타이밍 :

$ /usr/bin/time sh -c 'cat big_file | program_to_benchmark'

# 2와 같은 이유로 잘못되었습니다 : 여전히 cat불필요하게 사용 하고 있습니다. 몇 가지 이유로 언급합니다.

  • POSIX 쉘의 I / O 리디렉션 기능에 완전히 익숙하지 않은 사람들에게는 더 '자연적'입니다.

  • 이 경우있을 수 cat 있다 필요 (예 : 읽을 수있는 파일 액세스 권한이 어떤 종류의 필요는, 당신은 벤치 마크 할 수있는 프로그램이 권한을 부여하지 않습니다 sudo cat /dev/sda | /usr/bin/time my_compression_test --no-output)

  • 실제로 현대 기계의 cat경우 파이프 라인에 추가 된 결과는 실제 결과가 아닙니다.

그러나 나는 주저하면서 마지막으로 말합니다. 'Edit 5'의 마지막 결과를 살펴보면-

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU ...

-이것은 cat테스트 중 CPU의 74 % 를 소비 했다고 주장합니다 . 실제로 1.34 / 1.83은 대략 74 %입니다. 아마도 다음과 같은 일이 있습니다.

$ /usr/bin/time wc -l < temp_big_file

남은 .49 초만 걸렸을 것입니다! 아마 아닙니다 : cat여기 read()에서 파일을 '디스크'(실제로는 버퍼 캐시)에서 전송 한 시스템 호출 (또는 동등한)과 파이프가 파일을 전달하기 위해 쓰는 데 대한 비용을 지불 해야했습니다 wc. 올바른 시험은 여전히 ​​그 read()전화 를해야했을 것입니다 . 파이프에 쓰기 및 파이프에서 읽기 호출 만 저장되었을 것이므로 매우 저렴해야합니다.

그래도, 난 당신의 차이를 측정 할 수있을 것이라고 예측 cat file | wc -l하고 wc -l < file그리고 눈에 띄는 (2 자리 비율) 차이를 찾을 수 있습니다. 각각의 느린 테스트는 절대 시간에 비슷한 페널티를 받았습니다. 그러나 전체 시간의 작은 부분에 해당합니다.

실제로 나는 Linux 3.13 (Ubuntu 14.04) 시스템에서 1.5 기가 바이트 가비지 파일로 몇 가지 빠른 테스트를 수행하여 이러한 결과를 얻었습니다 (물론 캐시를 프라이밍 한 후 실제로 '최고 3 결과'입니다).

$ time wc -l < /tmp/junk
real 0.280s user 0.156s sys 0.124s (total cpu 0.280s)
$ time cat /tmp/junk | wc -l
real 0.407s user 0.157s sys 0.618s (total cpu 0.775s)
$ time sh -c 'cat /tmp/junk | wc -l'
real 0.411s user 0.118s sys 0.660s (total cpu 0.778s)

두 파이프 라인 결과는 실제 벽시계 시간보다 더 많은 CPU 시간 (사용자 + 시스템)을 사용했다고 주장합니다. 파이프 라인을 인식하는 셸 (bash)의 내장 'time'명령을 사용하고 있기 때문입니다. 그리고 파이프 라인의 개별 프로세스가 별도의 코어를 사용하여 CPU 시간을 실시간보다 빠르게 누적 할 수있는 멀티 코어 머신을 사용하고 있습니다. 를 사용하면 /usr/bin/time실시간보다 CPU 시간이 짧아 단일 파이프 라인 요소가 명령 줄에서 전달 된 시간 만 표시 할 수 있음을 보여줍니다. 또한 셸의 출력은 밀리 초를 /usr/bin/time제공 하는 반면 수백 분의 1 초만 제공합니다.

따라서 효율 수준 wc -l은이 cat283분의 409 = 1.453 이상의 실시간 45.3 % = 2.768 및 280분의 775 또는 엄청나게 1백77퍼센트 이상의 CPU를 사용했을 때 큰 차이를 만든다! 내 무작위로 그것은 당시 테스트 상자였습니다.

이러한 테스트 스타일 간에는 적어도 하나의 다른 중요한 차이점이 있으며 이것이 이점인지 결함인지 말할 수 없습니다. 이것을 스스로 결정해야합니다.

를 실행하면 cat big_file | /usr/bin/time my_program프로그램은 파이프에서 정확한 속도로 전송 된 속도로 입력 한 값 cat을 청크로 수신합니다 cat.

를 실행하면 /usr/bin/time my_program < big_file프로그램이 실제 파일에 열린 파일 디스크립터를 수신합니다. 프로그램 ( 또는 많은 경우에 작성된 언어의 I / O 라이브러리)은 일반 파일을 참조하는 파일 디스크립터가 표시 될 때 다른 조치를 취할 수 있습니다. mmap(2)명시 적 read(2)시스템 호출 을 사용하는 대신 입력 파일을 주소 공간에 맵핑하는 데 사용할 수 있습니다 . 이러한 차이는 cat바이너리 를 실행하는 적은 비용보다 벤치 마크 결과에 훨씬 큰 영향을 줄 수 있습니다 .

물론 두 프로그램간에 동일한 프로그램이 크게 다른 성능을 발휘한다면 흥미로운 벤치 마크 결과입니다. 실제로 프로그램이나 I / O 라이브러리 가를 사용 하는 것과 같이 흥미로운 일을하고 있음을 보여줍니다 mmap(). 실제로 벤치 마크를 두 가지 방법으로 실행하는 것이 좋습니다. 아마도 cat운영 비용을 "용서"하기 위해 작은 요소에 의해 결과를 할인 할 수 있습니다 cat.


26
와우, 그것은 매우 통찰력이 있었다! cat이 프로그램의 stdin에 입력을 공급하는 데 불필요하고 <shell redirect가 선호된다는 것을 알고 있지만, 나는 일반적으로 이전 방법이 시각적으로 보존하는 데이터의 왼쪽에서 오른쪽으로의 흐름으로 인해 cat에 붙어 있습니다. 파이프 라인을 생각할 때 그러한 경우 성능 차이는 무시할만한 것으로 나타났습니다. 하지만 교육 해 주셔서 감사합니다 벨라
JJC

11
리다이렉션은 초기 단계에서 쉘 커맨드 라인에서 파싱되어 왼쪽에서 오른쪽으로 흐름 $ < big_file time my_program $ time < big_file my_program 이 더 즐거워지면 다음 중 하나를 수행 할 수 있습니다 : 이것은 모든 POSIX 쉘에서 작동해야합니다. `그리고 나는`rc`와 같은 exotica에 대해 확신하지 않습니다 :)
Bela Lubkin

6
`cat` 바이너리가 동시에 실행되기 때문에 아마도 흥미롭지 않은 증분 성능 차이를 제외하고는 테스트중인 프로그램이 입력 파일을 mmap () 할 수 있다는 가능성을 포기하고 있습니다. 결과가 크게 달라질 수 있습니다. 이것은 '파일의 입력 행'관용구 만 사용하여 다양한 언어로 벤치 마크를 직접 작성한 경우에도 마찬가지입니다. 다양한 I / O 라이브러리의 자세한 작업에 따라 다릅니다.
Bela Lubkin

2
참고 사항 : Bash의 내장 기능 time은 첫 번째 프로그램 대신 전체 파이프 라인을 측정합니다. time seq 2 | while read; do sleep 1; done2 초 /usr/bin/time seq 2 | while read; do sleep 1; done인쇄, 0 초 인쇄
folkol

1
@folkol-yes, << 두 파이프 라인 결과는 실시간 [사용] (배시)의 내장 '시간'명령보다 더 많은 CPU를 보여줍니다. ... / usr / bin / time ... 명령 줄에서 단일 파이프 라인 요소가 전달한 시간 만 지정할 수 있습니다. >> '
벨라 Lubkin

90

Mac에서 g ++를 사용하여 컴퓨터에서 원래 결과를 재현했습니다.

while루프 직전에 C ++ 버전에 다음 명령문을 추가하면 Python 버전 과 인라인됩니다 .

std::ios_base::sync_with_stdio(false);
char buffer[1048576];
std::cin.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

sync_with_stdio의 속도가 2 초로 향상되었으며 더 큰 버퍼를 설정하면 1 초로 줄었습니다.


5
보다 유용한 정보를 얻기 위해 다른 버퍼 크기를 시도 할 수 있습니다. 나는 당신이 빠르게 감소하는 수익을 볼 것으로 생각합니다.
Karl Knechtel

8
나는 답장에 너무 성급했다. 버퍼 크기를 기본값 이외의 값으로 설정해도 큰 차이는 발생하지 않았습니다.
karunski

109
또한 스택에 1MB 버퍼를 설정하지 않아도됩니다.
스택 오버 플로우로

11
Matthieu, Mac은 기본적으로 8MB 프로세스 스택을 사용합니다. Linux는 스레드 기본값 인 IIRC 당 4MB를 사용합니다. 1MB는 상대적으로 얕은 스택 깊이로 입력을 변환하는 프로그램에서 큰 문제가되지 않습니다. 더 중요한 것은, std :: cin은 버퍼가 범위를 벗어나면 스택을 휴지통에 버립니다.
SEK

22
@SEK Windows 기본 스택 크기는 1MB입니다.
Étienne

39

getline, 스트림 연산자 scanf는 파일로드 시간에 신경 쓰지 않거나 작은 텍스트 파일을로드하는 경우 편리합니다. 그러나 성능이 마음에 든다면 실제로 전체 파일을 메모리에 버퍼링해야합니다 (적합하다고 가정).

예를 들면 다음과 같습니다.

//open file in binary mode
std::fstream file( filename, std::ios::in|::std::ios::binary );
if( !file ) return NULL;

//read the size...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);

//read into memory buffer, then close it.
char *filebuf = new char[length+1];
file.read(filebuf, length);
filebuf[length] = '\0'; //make it null-terminated
file.close();

원하는 경우 다음과 같이보다 편리한 액세스를 위해 해당 버퍼 주위에 스트림을 랩핑 할 수 있습니다.

std::istrstream header(&filebuf[0], length);

또한 파일을 제어하는 ​​경우 텍스트 대신 플랫 이진 데이터 형식을 사용하십시오. 공백의 모든 모호성을 처리 할 필요가 없기 때문에 읽고 쓰는 것이 더 안정적입니다. 또한 파싱하기가 더 작고 훨씬 빠릅니다.


20

다음 코드는 지금까지 게시 된 다른 코드보다 더 빠릅니다. ([0, 1000)에서 균일하게 줄 길이를 가진 Visual Studio 2013, 64 비트, 500MB 파일).

const int buffer_size = 500 * 1024;  // Too large/small buffer is not good.
std::vector<char> buffer(buffer_size);
int size;
while ((size = fread(buffer.data(), sizeof(char), buffer_size, stdin)) > 0) {
    line_count += count_if(buffer.begin(), buffer.begin() + size, [](char ch) { return ch == '\n'; });
}

그것은 모든 파이썬 시도보다 2 배 이상 뛰어납니다.


버퍼링되지 않은 readsyscall을 길이가 일정한 정적 버퍼로 BUFSIZE또는 동등한 해당 mmapsyscall을 통해 반복적으로 만들고 그 버퍼 수를 줄 바꿈하여 줄 바꿈 하는 작은 사용자 정의이지만 완전히 간단한 C 프로그램을 사용하면 그보다 훨씬 빨리 얻을 수 있습니다 for (char *cp = buf; *cp; cp++) count += *cp == "\n". BUFSIZE그러나 시스템 에 맞게 조정 해야하지만 어떤 stdio가 이미 수행했을 것입니다. 그러나이 for루프는 박스 하드웨어에 대한 엄청나게 빠른 어셈블러 언어 명령어로 컴파일되어야합니다.
tchrist

3
count_if와 람다는 또한 "놀랍게 비명을 지르는 빠른 어셈블러"로 컴파일됩니다.
Petter

17

그런데 C ++ 버전의 행 수가 Python 버전의 수보다 큰 이유는 eof 이상으로 읽으려고 할 때 eof 플래그가 설정되기 때문입니다. 올바른 루프는 다음과 같습니다.

while (cin) {
    getline(cin, input_line);

    if (!cin.eof())
        line_count++;
};

70
정말 정확한 루프는 다음과 같습니다. while (getline(cin, input_line)) line_count++;
Jonathan Wakely

2
@JonathanWakely 나는 꽤 늦었다는 것을 알고 있지만 사용 ++line_count;하지는 않습니다 line_count++;.
val은 Reinstate Monica가

7
컴파일러가 버그를 갖는 차이가 있다면 @val. 변수는 a long이며 컴파일러는 증분 결과가 사용되지 않는다고 말할 수 있습니다. 후행 증분 및 사전 증분에 대해 동일한 코드를 생성하지 않으면 손상됩니다.
Jonathan Wakely

2
실제로 괜찮은 컴파일러는 증가 후 오용을 감지하고 대신 사전 증가로 대체 할 수 있지만 컴파일러 는 필요하지 않습니다 . 따라서 컴파일러가 대체를 수행하지 않아도 깨지지 않습니다. 게다가, ++line_count;대신 쓰는 것이 line_count++;아프지 않을 것입니다 :)
Fareanor

1
@valsaysReinstateMonica이 특정 예에서 왜 하나가 선호됩니까? 결과는 여기서 어느 쪽도 사용되지 않으므로, 뒤에 읽히 while겠습니까? 어떤 종류의 오류가 있고 그것이 line_count올바른지 확인하고 싶 습니까? 나는 단지 추측하지만 그것이 왜 중요한지 이해하지 못한다.
TankorSmash

14

두 번째 예 (scanf () 사용)에서이 속도가 여전히 느린 이유는 scanf ( "% s")가 문자열을 구문 분석하고 공백 문자 (공백, 탭, 줄 바꾸기)를 찾기 때문일 수 있습니다.

또한 CPython은 하드 디스크 읽기를 피하기 위해 일부 캐싱을 수행합니다.


12

답의 첫 번째 요소 <iostream>는 느립니다. 천천히 scanf아래에서 와 같이 성능이 크게 향상 되었지만 여전히 파이썬보다 두 배 느립니다.

#include <iostream>
#include <time.h>
#include <cstdio>

using namespace std;

int main() {
    char buffer[10000];
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    int read = 1;
    while(read > 0) {
        read = scanf("%s", buffer);
        line_count++;
    };
    sec = (int) time(NULL) - start;
    line_count--;
    cerr << "Saw " << line_count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = line_count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } 
    else
        cerr << endl;
    return 0;
}

세 번째 수정을 할 때까지이 게시물을 보지 못했지만 다시 한 번 제안 해 주셔서 감사합니다. 이상하게도 위의 edit3에서 scanf 줄을 사용하면 나에게 2 배의 히트가 없습니다. 그건 그렇고 2.7을 사용하고 있습니다.
JJC

10
c ++ 버전을 수정 한 후이 stdio 버전은 내 컴퓨터의 c ++ iostream 버전보다 상당히 느립니다. (3 초 대 1 초)
karunski 2019

10

글쎄, 나는 당신의 두 번째 솔루션에서 cin로 전환 scanf했다는 것을 알았습니다. 에서 scanf로 전환 fgets하면 성능이 다시 향상됩니다 fgets. 문자열 입력을위한 가장 빠른 C ++ 함수입니다.

BTW, 그 동기화에 대해 몰랐어요. 하지만 여전히 시도해야합니다 fgets.


2
fgets불완전한 라인을 추가로 확인하지 않고 충분히 큰 라인에 대해 (라인 수 및 루프를 실제로 사용해야하는 경우 루프에서 라인을 분할하는 측면에서) 잘못된 경우를 제외하고 (보상을 시도하면 불필요하게 큰 버퍼를 할당해야 함) 여기서 std::getline실제 입력과 완벽하게 일치하도록 재 할당을 처리합니다). 빠르고 잘못하기는 쉽지만 "약간 느리지 만 정확"을 사용하면 거의 항상 가치 sync_with_stdio가 있습니다.
ShadowRanger
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.