Tại sao đọc các dòng từ stdin trong C ++ chậm hơn nhiều so với Python?


1840

Tôi muốn so sánh các dòng đọc chuỗi đầu vào từ stdin bằng Python và C ++ và đã bị sốc khi thấy mã C ++ của tôi chạy chậm hơn so với mã Python tương đương. Vì C ++ của tôi bị gỉ và tôi chưa phải là chuyên gia Pythonista, vui lòng cho tôi biết nếu tôi làm sai hoặc nếu tôi hiểu nhầm điều gì đó.


(Câu trả lời TLDR: bao gồm câu lệnh: cin.sync_with_stdio(false)hoặc chỉ sử dụng fgetsthay thế.

Kết quả TLDR: cuộn toàn bộ xuống cuối câu hỏi của tôi và nhìn vào bảng.)


Mã 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

Python tương đương:

#!/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))

Đây là kết quả của tôi:

$ 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

Tôi nên lưu ý rằng tôi đã thử cả hai trong Mac OS X v10.6.8 (Snow Leopard) và Linux 2.6.32 (Red Hat Linux 6.2). Cái trước là MacBook Pro, và cái sau là một máy chủ rất mạnh, không phải cái này quá phù hợp.

$ 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

Phụ lục điểm chuẩn nhỏ và tóm tắt

Để hoàn thiện, tôi nghĩ rằng tôi sẽ cập nhật tốc độ đọc cho cùng một tệp trên cùng một hộp với mã C ++ gốc (được đồng bộ hóa). Một lần nữa, đây là cho một tập tin dòng 100M trên một đĩa nhanh. Đây là so sánh, với một số giải pháp / cách tiếp cận:

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
Bạn đã chạy thử nghiệm của bạn nhiều lần? Có lẽ có một vấn đề bộ nhớ cache đĩa.
Vaughn Cato

9
@JJC: Tôi thấy hai khả năng (giả sử bạn đã loại bỏ vấn đề bộ đệm được đề xuất bởi David): 1) <iostream>hiệu suất tệ. Không phải lần đầu tiên nó xảy ra. 2) Python đủ thông minh để không sao chép dữ liệu trong vòng lặp for vì bạn không sử dụng nó. Bạn có thể kiểm tra lại cố gắng sử dụng scanfvà a char[]. Ngoài ra, bạn có thể thử viết lại vòng lặp để một cái gì đó được thực hiện với chuỗi (ví dụ: giữ chữ cái thứ 5 và nối nó trong một kết quả).
JN

15
Vấn đề là đồng bộ hóa với stdio - xem câu trả lời của tôi.
Vaughn Cato

19
Vì dường như không ai đề cập đến lý do tại sao bạn có thêm một dòng với C ++: Đừng thử chống lại cin.eof()!! Đặt getlinecuộc gọi vào câu lệnh 'if`.
Xèo

21
wc -llà nhanh bởi vì nó đọc luồng nhiều hơn một dòng tại một thời điểm (nó có thể là fread(stdin)/memchr('\n')sự kết hợp). Các kết quả của Python theo cùng một thứ tự cường độ, ví dụ:wc-l.py
jfs

Câu trả lời:


1644

Theo mặc định, cinđược đồng bộ hóa với stdio, điều này khiến nó tránh mọi bộ đệm đầu vào. Nếu bạn thêm phần này vào đầu trang chính, bạn sẽ thấy hiệu suất tốt hơn nhiều:

std::ios_base::sync_with_stdio(false);

Thông thường, khi một luồng đầu vào được đệm, thay vì đọc một ký tự một lần, luồng sẽ được đọc trong các khối lớn hơn. Điều này làm giảm số lượng cuộc gọi hệ thống, thường tương đối đắt tiền. Tuy nhiên, vì FILE*dựa trên stdioiostreamsthường có các triển khai riêng biệt và do đó bộ đệm riêng biệt, điều này có thể dẫn đến một vấn đề nếu cả hai được sử dụng cùng nhau. Ví dụ:

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

Nếu nhiều đầu vào được đọc cinhơn mức thực sự cần thiết, thì giá trị số nguyên thứ hai sẽ không có sẵn cho scanfhàm, có bộ đệm độc lập riêng. Điều này sẽ dẫn đến kết quả bất ngờ.

Để tránh điều này, theo mặc định, các luồng được đồng bộ hóa với stdio. Một cách phổ biến để đạt được điều này là cinđọc từng ký tự một khi cần sử dụng các stdiohàm. Thật không may, điều này giới thiệu rất nhiều chi phí. Đối với một lượng nhỏ đầu vào, đây không phải là một vấn đề lớn, nhưng khi bạn đang đọc hàng triệu dòng, hình phạt hiệu suất là rất đáng kể.

May mắn thay, các nhà thiết kế thư viện đã quyết định rằng bạn cũng có thể tắt tính năng này để cải thiện hiệu suất nếu bạn biết bạn đang làm gì, vì vậy họ đã cung cấp sync_with_stdiophương pháp này.


142
Điều này nên ở trên cùng. Nó gần như chắc chắn là chính xác. Câu trả lời không thể nằm ở việc thay thế việc đọc bằng một fscanfcuộc gọi, bởi vì điều đó khá đơn giản là không làm được nhiều việc như Python. Python phải phân bổ bộ nhớ cho chuỗi, có thể nhiều lần vì phân bổ hiện tại được coi là không đủ - chính xác như cách tiếp cận C ++ với std::string. Nhiệm vụ này gần như chắc chắn là I / O bị ràng buộc và có quá nhiều FUD xoay quanh chi phí tạo std::stringđối tượng trong C ++ hoặc sử dụng <iostream>chính nó.
Karl Knechtel

51
Có, thêm dòng này ngay trên vòng lặp ban đầu của tôi trong khi tăng tốc mã để vượt qua cả python. Tôi sắp đăng kết quả là bản chỉnh sửa cuối cùng. Cảm ơn một lần nữa!
JJC

6
Vâng, điều này thực sự áp dụng cho cout, cerr và guốc là tốt.
Vaughn Cato

2
Để làm cho cout, cin, cerr và làm tắc nhanh hơn, hãy làm theo cách này std :: ios_base :: sync_with_stdio (false);
01100110

56
Lưu ý rằng đó sync_with_stdio()là một hàm thành viên tĩnh và một lệnh gọi đến hàm này trên bất kỳ đối tượng luồng nào (ví dụ cin) bật hoặc tắt đồng bộ hóa cho tất cả các đối tượng iostream tiêu chuẩn.
John Zwinck

171

Vì tò mò, tôi đã xem xét những gì xảy ra dưới mui xe và tôi đã sử dụng dtruss / strace trong mỗi bài kiểm tra.

C ++

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

tòa nhà cao tầng 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

Con trăn

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

tòa nhà cao tầng sudo dtruss -c ./a.py < in

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

159

Tôi ở đây vài năm, nhưng:

Trong 'Chỉnh sửa 4/5/6' của bài đăng gốc, bạn đang sử dụng cấu trúc:

$ /usr/bin/time cat big_file | program_to_benchmark

Điều này là sai theo một vài cách khác nhau:

  1. Bạn đang thực sự định thời gian thực hiện cat, không phải điểm chuẩn của bạn. Các 'người dùng và sử dụng CPU 'sys' được hiển thị bằng cách timelà những người cat, không phải chương trình quy chuẩn hóa của bạn. Thậm chí tệ hơn, thời gian 'thực' cũng không nhất thiết phải chính xác. Tùy thuộc vào việc triển khai catvà các đường ống trong hệ điều hành cục bộ của bạn, có thể catghi một bộ đệm khổng lồ cuối cùng và thoát ra rất lâu trước khi quá trình đọc kết thúc công việc của nó.

  2. Sử dụng catlà không cần thiết và thực tế là phản tác dụng; bạn đang thêm các bộ phận chuyển động. Nếu bạn đang sử dụng một hệ thống đủ cũ (tức là với một CPU duy nhất và - trong một số thế hệ máy tính nhất định - I / O nhanh hơn CPU) - thì thực tế catlà việc chạy có thể tô màu kết quả. Bạn cũng phải tuân theo bất kỳ bộ đệm đầu vào và đầu ra nào và việc xử lý khác catcó thể làm. (Điều này có thể sẽ mang lại cho bạn giải thưởng 'Sử dụng mèo vô dụng' nếu tôi là Randal Schwartz.

Một công trình tốt hơn sẽ là:

$ /usr/bin/time program_to_benchmark < big_file

Trong tuyên bố này, nó là shell mở big_file, chuyển nó đến chương trình của bạn (thực ra time, sau đó thực thi chương trình của bạn dưới dạng một quy trình con) như một bộ mô tả tệp đã mở. 100% việc đọc tệp là trách nhiệm hoàn toàn của chương trình bạn đang cố gắng điểm chuẩn. Điều này giúp bạn đọc thực sự về hiệu suất của nó mà không có biến chứng giả.

Tôi sẽ đề cập đến hai khả năng, nhưng thực sự sai, 'sửa lỗi' cũng có thể được xem xét (nhưng tôi 'số' chúng khác nhau vì đây không phải là những điều sai trong bài viết gốc):

A. Bạn có thể 'sửa' điều này bằng cách chỉ định thời gian cho chương trình của bạn:

$ cat big_file | /usr/bin/time program_to_benchmark

B. hoặc bằng cách định thời gian cho toàn bộ đường ống:

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

Những điều này là sai vì những lý do tương tự như # 2: họ vẫn đang sử dụng catkhông cần thiết. Tôi đề cập đến chúng vì một vài lý do:

  • chúng 'tự nhiên' hơn đối với những người không hoàn toàn thoải mái với các tiện ích chuyển hướng I / O của vỏ POSIX

  • có thể có trường hợp cat cần thiết (ví dụ như: các tập tin được đọc đòi hỏi một số loại đặc quyền truy cập, và bạn không muốn cấp quyền đặc quyền để chương trình được quy chuẩn hóa: sudo cat /dev/sda | /usr/bin/time my_compression_test --no-output)

  • Trong thực tế , trên các máy móc hiện đại, việc thêm catvào đường ống có lẽ không có kết quả thực sự.

Nhưng tôi nói rằng điều cuối cùng với một số do dự. Nếu chúng tôi kiểm tra kết quả cuối cùng trong 'Chỉnh sửa 5' -

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

- tuyên bố này đã cattiêu thụ 74% CPU trong quá trình thử nghiệm; và thực sự 1,34 / 1,83 là khoảng 74%. Có lẽ một loạt:

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

sẽ chỉ mất 0,49 giây còn lại! Có lẽ là không: catở đây phải trả tiền cho các read()cuộc gọi hệ thống (hoặc tương đương) đã chuyển tệp từ 'đĩa' (bộ đệm thực sự là bộ đệm), cũng như đường ống ghi để gửi chúng đến wc. Bài kiểm tra đúng vẫn sẽ phải thực hiện những read()cuộc gọi đó; chỉ các cuộc gọi ghi vào đường ống và đọc từ đường ống mới được lưu và những cuộc gọi này sẽ khá rẻ.

Tuy nhiên, tôi dự đoán bạn sẽ có thể đo được sự khác biệt giữa cat file | wc -lwc -l < filevà tìm thấy sự khác biệt đáng chú ý (2 chữ số). Mỗi bài kiểm tra chậm hơn sẽ phải trả một hình phạt tương tự trong thời gian tuyệt đối; tuy nhiên sẽ chiếm một phần nhỏ hơn trong tổng thời gian lớn hơn của nó.

Trong thực tế, tôi đã thực hiện một số thử nghiệm nhanh với tệp rác 1,5 gigabyte, trên hệ thống Linux 3.13 (Ubuntu 14.04), thu được các kết quả này (tất nhiên đây là kết quả 'tốt nhất trong 3'; sau khi xử lý bộ đệm)

$ 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)

Lưu ý rằng hai kết quả đường ống tuyên bố đã mất nhiều thời gian CPU (người dùng + sys) hơn thời gian đồng hồ treo tường thực sự. Điều này là do tôi đang sử dụng lệnh 'thời gian' tích hợp của shell (bash), là nhận thức của đường ống; và tôi đang ở trên một máy đa lõi, nơi các quy trình riêng biệt trong một đường ống có thể sử dụng các lõi riêng biệt, tích lũy thời gian CPU nhanh hơn thời gian thực. Sử dụng /usr/bin/timetôi thấy thời gian CPU nhỏ hơn thời gian thực - cho thấy rằng nó chỉ có thể thời gian phần tử đường ống duy nhất được truyền cho nó trên dòng lệnh của nó. Ngoài ra, đầu ra của shell cho mili giây trong khi /usr/bin/timechỉ cho một phần trăm giây.

Vì vậy, ở mức độ hiệu quả wc -l, sự catkhác biệt rất lớn: 409/283 = 1.453 hoặc 45.3% thời gian thực hơn, và 775/280 = 2.768, hoặc sử dụng CPU nhiều hơn 177%! Trong hộp kiểm tra ngẫu nhiên của tôi, nó đã có tại thời điểm đó.

Tôi nên nói thêm rằng có ít nhất một sự khác biệt đáng kể khác giữa các phong cách thử nghiệm này và tôi không thể nói liệu đó là lợi ích hay lỗi; bạn phải tự quyết định điều này:

Khi bạn chạy cat big_file | /usr/bin/time my_program, chương trình của bạn sẽ nhận được đầu vào từ một đường ống, với tốc độ chính xác được gửi bởi catvà trong các khối không lớn hơn được viết bởi cat.

Khi bạn chạy /usr/bin/time my_program < big_file, chương trình của bạn nhận được một mô tả tệp mở cho tệp thực tế. Chương trình của bạn - hoặc trong nhiều trường hợp các thư viện I / O của ngôn ngữ được viết - có thể thực hiện các hành động khác nhau khi được trình bày với một bộ mô tả tệp tham chiếu một tệp thông thường. Nó có thể sử dụng mmap(2)để ánh xạ tệp đầu vào vào không gian địa chỉ của nó, thay vì sử dụng read(2)các cuộc gọi hệ thống rõ ràng . Những khác biệt này có thể có ảnh hưởng lớn hơn nhiều đến kết quả điểm chuẩn của bạn so với chi phí nhỏ khi chạy catnhị phân.

Tất nhiên đó là một kết quả điểm chuẩn thú vị nếu cùng một chương trình thực hiện khác nhau đáng kể giữa hai trường hợp. Nó cho thấy rằng, thực sự, chương trình hoặc các thư viện I / O của nó đang làm một cái gì đó thú vị, như sử dụng mmap(). Vì vậy, trong thực tế, có thể tốt khi chạy các điểm chuẩn theo cả hai cách; có lẽ giảm giá catkết quả bởi một số yếu tố nhỏ để "tha thứ" cho chi phí cattự chạy .


26
Wow, đó là khá sâu sắc! Mặc dù tôi đã nhận thấy rằng mèo là không cần thiết để cung cấp đầu vào cho stdin của các chương trình và <chuyển hướng shell được ưa thích, nhưng tôi thường bị mắc kẹt do luồng dữ liệu từ trái sang phải mà phương thức trước đó bảo tồn trực quan khi tôi lý do về đường ống dẫn. Sự khác biệt về hiệu suất trong những trường hợp như vậy tôi thấy là không đáng kể. Nhưng, tôi đánh giá cao việc giáo dục chúng tôi, bela.
JJC

11
Chuyển hướng được phân tách ra khỏi dòng lệnh shell ở giai đoạn đầu, cho phép bạn thực hiện một trong những điều này, nếu nó mang lại sự xuất hiện dễ chịu hơn của luồng từ trái sang phải: $ < big_file time my_program $ time < big_file my_program Điều này sẽ hoạt động trong bất kỳ shell POSIX nào (tức là không phải là `csh `và tôi không chắc chắn về Exotica như` rc`:)
Bela Lubkin

6
Một lần nữa, ngoài sự khác biệt hiệu suất gia tăng có lẽ không thú vị do nhị phân `cat` chạy cùng lúc, bạn đang từ bỏ khả năng chương trình được kiểm tra có thể mmap () tệp đầu vào. Điều này có thể làm cho một sự khác biệt sâu sắc trong kết quả. Điều này đúng ngay cả khi bạn tự viết các điểm chuẩn bằng các ngôn ngữ khác nhau, chỉ sử dụng 'dòng đầu vào từ thành ngữ' của tệp. Nó phụ thuộc vào hoạt động chi tiết của các thư viện I / O khác nhau của họ.
Maison Lubkin

2
Lưu ý bên lề: Nội dung của Bash timeđang đo toàn bộ đường ống thay vì chương trình đầu tiên. time seq 2 | while read; do sleep 1; donein 2 giây, /usr/bin/time seq 2 | while read; do sleep 1; donein 0 giây.
dân gian

1
@folkol - có, << Lưu ý rằng hai kết quả đường ống [hiển thị] nhiều CPU [hơn] thời gian thực [sử dụng] lệnh 'thời gian' tích hợp '(Bash); ... / usr / bin / time ... chỉ có thể thời gian phần tử đường ống duy nhất được truyền cho nó trên dòng lệnh của nó. >> '
Maison Lubkin

90

Tôi đã sao chép kết quả ban đầu trên máy tính của mình bằng g ++ trên máy Mac.

Thêm các câu lệnh sau vào phiên bản C ++ ngay trước khi whilevòng lặp đưa nó vào dòng với phiên bản Python :

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

sync_with_stdio cải thiện tốc độ lên 2 giây và thiết lập bộ đệm lớn hơn đưa nó xuống còn 1 giây.


5
Bạn có thể muốn thử các kích thước bộ đệm khác nhau để có thêm thông tin hữu ích. Tôi nghi ngờ bạn sẽ thấy lợi nhuận giảm nhanh chóng.
Karl Knechtel

8
Tôi đã quá vội vàng trong câu trả lời của mình; thiết lập kích thước bộ đệm thành một cái gì đó khác với mặc định không tạo ra sự khác biệt đáng kể.
karunski

109
Tôi cũng sẽ tránh thiết lập bộ đệm 1MB trên ngăn xếp. Nó có thể dẫn đến stackoverflow (mặc dù tôi đoán đó là một nơi tốt để tranh luận về nó!)
Matthieu M.

11
Matthieu, Mac sử dụng ngăn xếp quy trình 8 MB theo mặc định. Linux sử dụng 4MB cho mỗi luồng mặc định, IIRC. 1MB không phải là vấn đề lớn đối với một chương trình biến đổi đầu vào với độ sâu ngăn xếp tương đối nông. Quan trọng hơn, mặc dù, std :: cin sẽ dọn rác ngăn xếp nếu bộ đệm vượt quá phạm vi.
SEK

22
@SEK Kích thước ngăn xếp mặc định của Windows là 1MB.
Étienne

39

getline, toán tử luồng scanf, có thể thuận tiện nếu bạn không quan tâm đến thời gian tải tệp hoặc nếu bạn đang tải các tệp văn bản nhỏ. Nhưng, nếu hiệu suất là thứ bạn quan tâm, bạn thực sự chỉ nên đệm toàn bộ tệp vào bộ nhớ (giả sử nó sẽ phù hợp).

Đây là một ví dụ:

//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();

Nếu bạn muốn, bạn có thể bọc một luồng xung quanh bộ đệm đó để truy cập thuận tiện hơn như thế này:

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

Ngoài ra, nếu bạn đang kiểm soát tệp, hãy xem xét sử dụng định dạng dữ liệu nhị phân phẳng thay vì văn bản. Nó đáng tin cậy hơn để đọc và viết bởi vì bạn không phải đối phó với tất cả sự mơ hồ của khoảng trắng. Nó cũng nhỏ hơn và nhanh hơn nhiều để phân tích.


20

Mã sau đây đối với tôi nhanh hơn mã khác được đăng ở đây: (Visual Studio 2013, tệp 64 bit, 500 MB với độ dài dòng đồng đều trong [0, 1000)).

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'; });
}

Nó đánh bại tất cả các nỗ lực Python của tôi nhiều hơn một yếu tố 2.


Bạn thậm chí có thể nhanh hơn thế nữa với một chương trình C tùy chỉnh nhỏ nhưng hoàn toàn đơn giản, lặp đi lặp lại làm cho readcác tòa nhà không có bộ đệm thành một bộ đệm tĩnh có chiều dài BUFSIZEhoặc thông qua các tòa nhà tương ứng tương ứng mmap, và sau đó lướt qua bộ đệm đó đếm các dòng mới for (char *cp = buf; *cp; cp++) count += *cp == "\n". Tuy nhiên, bạn sẽ phải điều chỉnh BUFSIZEcho hệ thống của mình, điều mà stdio sẽ thực hiện cho bạn. Nhưng forvòng lặp đó sẽ biên dịch thành các hướng dẫn ngôn ngữ trình biên dịch nhanh chóng đáng sợ cho phần cứng của hộp của bạn.
tchrist

3
Count_if và một lambda cũng biên dịch thành "trình biên dịch nhanh la hét".
Petter

17

Nhân tiện, lý do số dòng cho phiên bản C ++ lớn hơn số đếm cho phiên bản Python là vì cờ eof chỉ được đặt khi một nỗ lực được thực hiện để đọc vượt quá eof. Vì vậy, vòng lặp chính xác sẽ là:

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

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

70
Vòng lặp thực sự chính xác sẽ là: while (getline(cin, input_line)) line_count++;
Jonathan Wakely

2
@JonathanWakely Tôi biết rằng tôi khá muộn, nhưng sử dụng ++line_count;và không line_count++;.
val nói Phục hồi lại

7
@val nếu điều đó làm cho bất kỳ sự khác biệt trình biên dịch của bạn có một lỗi. Biến là a longvà trình biên dịch hoàn toàn có khả năng cho biết kết quả của mức tăng không được sử dụng. Nếu nó không tạo mã giống hệt nhau cho postincrement và preincrement, nó bị hỏng.
Jonathan Wakely

2
Thật vậy, bất kỳ trình biên dịch tử tế nào cũng sẽ có thể phát hiện sự lạm dụng sau tăng và thay thế nó bằng cách tăng trước thay thế, nhưng trình biên dịch không bắt buộc . Vì vậy, nó không bị hỏng ngay cả khi trình biên dịch không thực hiện thay thế. Hơn nữa, viết ++line_count;thay vì line_count++;sẽ không đau :)
Fareanor

1
@valaysReinstateMonica Trong ví dụ cụ thể này, tại sao một trong hai sẽ được ưu tiên? Kết quả không được sử dụng ở đây theo bất kỳ cách nào, vì vậy nó sẽ được đọc sau while, phải không? Sẽ có vấn đề nếu có một số loại lỗi và bạn muốn chắc chắn rằng đó line_countlà chính xác? Tôi chỉ đoán nhưng tôi không hiểu tại sao nó lại quan trọng.
TankorSmash

14

Trong ví dụ thứ hai của bạn (với scanf ()) lý do tại sao điều này vẫn chậm hơn có thể là do scanf ("% s") phân tích chuỗi và tìm kiếm bất kỳ char (dấu cách, tab, dòng mới) nào.

Ngoài ra, có, CPython thực hiện một số bộ nhớ đệm để tránh đọc ổ cứng.


12

Một yếu tố đầu tiên của một câu trả lời: <iostream>là chậm. Chết tiệt. Tôi nhận được một sự gia tăng hiệu suất rất lớn scanfnhư ở bên dưới, nhưng nó vẫn chậm hơn hai lần so với Python.

#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;
}

Không thấy bài đăng này cho đến khi tôi thực hiện chỉnh sửa thứ ba, nhưng cảm ơn lần nữa vì đề xuất của bạn. Thật kỳ lạ, bây giờ không có lần truy cập nào cho tôi so với python với dòng scanf trong edit3 ở trên. Nhân tiện, tôi đang sử dụng 2.7.
JJC

10
Sau khi sửa phiên bản c ++, phiên bản stdio này chậm hơn đáng kể so với phiên bản iostreams c ++ trên máy tính của tôi. (3 giây so với 1 giây)
karunski

10

Chà, tôi thấy rằng trong giải pháp thứ hai của bạn, bạn đã chuyển từ cinsang scanf, đó là gợi ý đầu tiên tôi sẽ làm cho bạn (cin là sloooooooooooow). Bây giờ, nếu bạn chuyển từ scanfsang fgets, bạn sẽ thấy một hiệu suất tăng khác: fgetslà chức năng C ++ nhanh nhất cho đầu vào chuỗi.

BTW, không biết về điều đồng bộ đó, tốt đẹp. Nhưng bạn vẫn nên thử fgets.


2
Ngoại trừ fgetssẽ sai (về số lượng dòng và về mặt phân chia các dòng trên các vòng nếu bạn thực sự cần sử dụng chúng) cho các dòng đủ lớn, không có kiểm tra bổ sung cho các dòng không đầy đủ (và cố gắng bù cho nó liên quan đến việc phân bổ bộ đệm lớn không cần thiết , trong đó std::getlinexử lý phân bổ lại để khớp với đầu vào thực tế một cách liền mạch). Nhanh và sai thì dễ, nhưng hầu như luôn luôn đáng để sử dụng "chậm hơn một chút, nhưng đúng", điều này sync_with_stdiogiúp bạn có được.
ShadowRanger
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.