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


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


(Câu trả lời TLDR: bao gồm tuyên bố: cin.sync_with_stdio(false) hoặc chỉ sử dụng fgets thay thế.

Kết quả TLDR: cuộn xuống dưới cùng của câu hỏi của tôi và xem 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

Tương đương Python:

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

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

Chỉnh sửa 2:  (Đã xóa bản chỉnh sửa này, vì không còn áp dụng được nữa)

$ 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

Chỉnh sửa 3:

Được rồi, tôi đã thử đề xuất của J.N. về việc cố gắng để Python lưu trữ dòng đọc: nhưng nó không có sự khác biệt về tốc độ của python.

Tôi cũng đã thử đề xuất của J.N. về việc sử dụng scanf thành một char mảng thay vì getline thành một std::string. Chơi lô tô! Điều này dẫn đến hiệu suất tương đương cho cả Python và C ++. (3,333,333 LPS với dữ liệu đầu vào của tôi, mà bằng cách này chỉ là dòng ngắn của ba lĩnh vực mỗi, thường là khoảng 20 ký tự rộng, mặc dù đôi khi nhiều hơn).

Mã số:

char input_a[512];
char input_b[32];
char input_c[512];
while(scanf("%s %s %s\n", input_a, input_b, input_c) != EOF) {
    line_count++;
};

Tốc độ:

$ cat test_lines | ./readline_test_cpp2
Read 10000000 lines in 3 seconds. LPS: 3333333
$ cat test_lines | ./readline_test2.py
Read 10000000 lines in 3 seconds. LPS: 3333333

(Có, tôi đã chạy nó nhiều lần.) Vì vậy, tôi đoán bây giờ tôi sẽ sử dụng scanf thay vì getline. Tuy nhiên, tôi vẫn tò mò nếu mọi người nghĩ rằng hiệu suất này được đánh từ std::string/getline là điển hình và hợp lý.

Chỉnh sửa 4 (là: Final Edit / Solution):

Thêm:

cin.sync_with_stdio(false);

Ngay phía trên vòng lặp ban đầu của tôi ở trên dẫn đến mã chạy nhanh hơn Python.

So sánh hiệu suất mới (đây là trên MacBook Pro 2011 của tôi), sử dụng mã gốc, bản gốc có đồng bộ hóa bị vô hiệu hóa và mã Python gốc, tương ứng, trên một tệp có 20M dòng văn bản. Có, tôi chạy nó nhiều lần để loại bỏ bộ nhớ đệm đĩa bị nhầm lẫn.

$ /usr/bin/time cat test_lines_double | ./readline_test_cpp
       33.30 real         0.04 user         0.74 sys
Read 20000001 lines in 33 seconds. LPS: 606060
$ /usr/bin/time cat test_lines_double | ./readline_test_cpp1b
        3.79 real         0.01 user         0.50 sys
Read 20000000 lines in 4 seconds. LPS: 5000000
$ /usr/bin/time cat test_lines_double | ./readline_test.py
        6.88 real         0.01 user         0.38 sys
Read 20000000 lines in 6 seconds. LPS: 3333333

Nhờ @Vaughn Cato cho câu trả lời của mình! Bất kỳ người xây dựng nào có thể đưa ra hoặc tham khảo tốt mọi người có thể chỉ ra rằng tại sao việc đồng bộ hóa này xảy ra, ý nghĩa của nó là gì, khi nào nó hữu ích và khi vô hiệu hóa sẽ được đánh giá cao bởi hậu thế. :-)

Chỉnh sửa 5 / Giải pháp tốt hơn:

Theo đề xuất của Gandalf The Grey bên dưới, gets thậm chí còn nhanh hơn scanf hoặc không đồng bộ cin tiếp cận. Tôi cũng học được rằng scanf và gets đều là UNSAFE và KHÔNG ĐƯỢC SỬ DỤNG do khả năng tràn bộ đệm. Vì vậy, tôi đã viết lặp lại này bằng cách sử dụng fgets, giải pháp thay thế an toàn hơn. Dưới đây là các dòng thích hợp cho noobs của tôi:

char input_line[MAX_LINE];
char *result;

//<snip>

while((result = fgets(input_line, MAX_LINE, stdin )) != NULL)
    line_count++;
if (ferror(stdin))
    perror("Error reading stdin.");

Bây giờ, đây là kết quả bằng cách sử dụng một tập tin lớn hơn (100M dòng; ~ 3.4 GB) trên một máy chủ nhanh với đĩa rất nhanh, so sánh mã Python, không đồng bộ hóa cin, và fgetscách tiếp cận, cũng như so sánh với tiện ích wc. [Các scanf phân đoạn phiên bản bị lỗi và tôi không cảm thấy muốn khắc phục sự cố đó.]:

$ /usr/bin/time cat temp_big_file | readline_test.py
0.03user 2.04system 0:28.06elapsed 7%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 28 seconds. LPS: 3571428

$ /usr/bin/time cat temp_big_file | readline_test_unsync_cin
0.03user 1.64system 0:08.10elapsed 20%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 8 seconds. LPS: 12500000

$ /usr/bin/time cat temp_big_file | readline_test_fgets
0.00user 0.93system 0:07.01elapsed 13%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 7 seconds. LPS: 14285714

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
100000000


Recap (lines per second):
python:         3,571,428
cin (no sync): 12,500,000
fgets:         14,285,714
wc:            54,644,808

Bạn có thể thấy, fgets là tốt hơn, nhưng vẫn còn khá xa wc hiệu suất; Tôi khá chắc chắn điều này là do thực tế là wc kiểm tra mỗi nhân vật mà không cần bất kỳ sao chép bộ nhớ. Tôi nghi ngờ rằng, vào thời điểm này, các phần khác của mã sẽ trở thành nút cổ chai, vì vậy tôi không nghĩ rằng tối ưu hóa mức đó thậm chí còn đáng giá, thậm chí nếu có thể (vì, sau cùng, tôi thực sự cần lưu trữ các dòng đọc trong trí nhớ).

Cũng lưu ý rằng một sự cân bằng nhỏ với việc sử dụng char * bộ đệm và fgets so với không đồng bộ cin để chuỗi là sau này có thể đọc các dòng của bất kỳ chiều dài, trong khi trước đây yêu cầu hạn chế đầu vào cho một số hữu hạn. Trong thực tế, điều này có thể không phải là vấn đề để đọc hầu hết các tệp đầu vào dựa trên dòng, vì bộ đệm có thể được đặt thành một giá trị rất lớn mà sẽ không bị vượt quá bởi đầu vào hợp lệ.

Điều này đã được giáo dục. Cảm ơn tất cả các ý kiến ​​và đề xuất của bạn.

Chỉnh sửa 6:

Theo đề xuất của J.F. Sebastian trong phần bình luận bên dưới, tiện ích GNU wc sử dụng đồng bằng C read() (trong vòng wrapper Safe-read.c) đọc khối (16k byte) tại một thời điểm và đếm các dòng mới. Đây là một tương đương Python dựa trên mã của J.F. (chỉ hiển thị đoạn mã có liên quan thay thế Python for vòng lặp:

BUFFER_SIZE = 16384
count = sum(chunk.count('\n') for chunk in iter(partial(sys.stdin.read, BUFFER_SIZE), ''))

Hiệu suất của phiên bản này khá nhanh (mặc dù vẫn còn chậm hơn một chút so với tiện ích cc c nguyên, tất nhiên):

$ /usr/bin/time cat temp_big_file | readline_test3.py
0.01user 1.16system 0:04.74elapsed 24%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 4.7275 seconds. LPS: 21152829

Một lần nữa, tôi hơi ngớ ngẩn khi so sánh C ++ fgets/cin và mã python đầu tiên trên một mặt wc -l và đoạn mã Python cuối cùng này trên đoạn mã kia, vì đoạn mã sau không thực sự lưu trữ các dòng đọc, nhưng chỉ đếm các dòng mới. Tuy nhiên, thật thú vị khi khám phá tất cả các triển khai khác nhau và suy nghĩ về các tác động hiệu suất. Cảm ơn một lần nữa!

Chỉnh sửa 7: Phụ lục điểm chuẩn nhỏ và tóm tắt

Để hoàn thành, tôi nghĩ tôi muốn 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 (đã đồ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à bảng đầy đủ bây giờ:

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

1465
2018-02-21 03:24


gốc


Bạn có chạy thử nghiệm nhiều lần không? Có lẽ có vấn đề về bộ nhớ cache trên đĩa. - Vaughn Cato
@ JJC: Tôi thấy hai khả năng (giả sử bạn đã xóa vấn đề bộ nhớ đệm được đề xuất bởi David): 1) <iostream> hiệu suất hú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 bởi vì bạn không sử dụng nó. Bạn có thể thử lại để sử dụng scanf và một 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ụ như giữ thư thứ năm và nối nó trong một kết quả). - J.N.
Vấn đề là đồng bộ hóa với stdio - xem câu trả lời của tôi. - Vaughn Cato
Vì không ai có vẻ đã đề cập đến lý do tại sao bạn có thêm một dòng với C ++: Đừng kiểm tra cin.eof()!! Đặt getline gọi vào câu lệnh if '. - Xeo
wc -l nhanh vì nó đọc luồng nhiều hơn một dòng tại một thời điểm (có thể là fread(stdin)/memchr('\n') sự phối hợp). Các kết quả Python có cùng thứ tự độ lớn, ví dụ: wc-l.py - jfs


Các câu trả lời:


Theo mặc định, cin được đồng bộ hóa với stdio, điều này làm cho nó tránh được bất kỳ bộ đệm đầu vào nào. Nếu bạn thêm phần này vào đầu phần 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ự tại một thời điểm, luồng sẽ được đọc theo 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 là khá đắt. Tuy nhiên, kể từ khi FILE* dựa trên stdio và iostreams thường có các triển khai riêng biệt và do đó các 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 có thêm đầu vào được đọc bởi cin thực tế là cần thiết, thì giá trị số nguyên thứ hai sẽ không có sẵn cho scanf hàm có bộ đệm độc lập riêng. Điều này sẽ dẫn đến kết quả không mong muốn.

Để 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à có cin đọc từng ký tự một khi cần stdio chức năng. 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à đá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ể vô hiệu hóa tính năng này để cải thiện hiệu suất nếu bạn biết mình đang làm gì, vì vậy họ đã cung cấp sync_with_stdio phương pháp.


1286
2018-03-11 18:10



Đ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 trong việc thay thế đọc bằng một fscanf cuộc gọi, bởi vì điều đó khá đơn giản không làm nhiều công việc như Python. Python phải cấp phát bộ nhớ cho chuỗi, có thể nhiều lần khi phân bổ hiện có được coi là không đầy đủ - 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 đi xung quanh về chi phí tạo ra std::string đối tượng trong C ++ hoặc sử dụng <iostream> trong và của chính nó. - Karl Knechtel
Có, thêm dòng này ngay lập tức trên vòng lặp ban đầu của tôi trong khi tăng tốc mã để vượt qua ngay cả python. Tôi sắp đăng kết quả dưới dạng bản chỉnh sửa cuối cùng. Cảm ơn một lần nữa! - JJC
Có, điều này thực sự áp dụng cho cout, cerr, và làm tắc nghẽn. - Vaughn Cato
Để làm cho cout, cin, cerr và làm tắc nghẽn nhanh hơn, hãy làm theo cách này std :: ios_base :: sync_with_stdio (false); - 01100110
Lưu ý rằng sync_with_stdio() là một hàm thành viên tĩnh và một cuộc 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


Chỉ 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 trên mỗi bài kiểm tra.

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

Python

./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

115
2018-02-21 03:33





Tôi là một vài năm sau đây, nhưng:

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

$ /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. Việc sử dụng CPU 'người dùng' và 'sys' được hiển thị bằng `thời gian` là của` con mèo`, không phải là chương trình điểm chuẩn của bạn. Thậm chí tệ hơn, thời gian 'thực' cũng không nhất thiết là chính xác. Tùy thuộc vào việc thực hiện `cat` và đường ống trong hệ điều hành cục bộ của bạn, có thể là` cat` viết một bộ đệm khổng lồ cuối cùng và thoát ra lâu trước khi quá trình đọc kết thúc công việc của nó.

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

Xây dựng tốt hơn sẽ là:

$ /usr/bin/time program_to_benchmark < big_file

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

Tôi sẽ đề cập đến hai có thể, nhưng thực sự sai, 'sửa chữa' mà 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 thứ sai trong bài 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 theo thời gian toàn bộ đường ống:

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

Đây là sai cho cùng một lý do như # 2: họ vẫn đang sử dụng `cat` khô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 cơ sở chuyển hướng I / O của vỏ POSIX

  • có thể có trường hợp `mèo`  cần thiết (ví dụ: tệp cần đọc yêu cầu một số loại đặc quyền để truy cập và bạn không muốn cấp đặc quyền đó cho chương trình để được điểm chuẩn: `sudo cat / dev / sda | / usr / bin / time my_compression_test - không có đầu ra`)

  • trong thực tếtrên các máy móc hiện đại, `cat` được thêm vào trong đường ống có lẽ không có hậu quả thực sự

Nhưng tôi nói điều cuối cùng với một chút do dự. Nếu chúng ta 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 ...

- điều này cho rằng `cat` tiêu thụ 74% CPU trong quá trình thử nghiệm; và thực sự là 1,34 / 1,83 là khoảng 74%. Có lẽ một lần chạy:

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

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

Tuy nhiên, tôi dự đoán bạn sẽ có thể đo lường sự khác biệt giữa `tệp cat | wc -l` và `wc -l <file` và 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ẽ trả tiền phạt tương tự trong thời gian tuyệt đối; Tuy nhiên, số tiền này 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 một tệp rác 1,5 gigabyte, trên hệ thống Linux 3.13 (Ubuntu 14.04), thu được những kết quả này (đây thực sự là kết quả tốt nhất của 3 '; sau khi mồi bộ nhớ cache, tất nhiên):

$ 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 yêu cầu đã lấy thêm thời gian CPU (user + sys) so với thời gian thực. Điều này là do tôi đang sử dụng lệnh 'time' của trình bao (Bash) được xây dựng sẵn, đó là nhận thức của đường ống; và tôi đang sử dụng một máy đa lõi trong đó 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 / time Tô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 đơn được truyền cho nó trên dòng lệnh của nó. Ngoài ra, đầu ra của trình bao cho mili giây trong khi / usr / bin / time chỉ cung cấp hundreths trong một giây.

Vì vậy, ở mức hiệu quả của `wc -l`,` cat` tạo ra sự khác biệt lớn: 409/283 = 1.453 hoặc 45.3% thời gian thực, và 775/280 = 2.768, hoặc một con số khổng lồ hơn 177% được sử dụng! Trên hộp kiểm tra ngẫu nhiên của nó, nó đã từng ở đó.

Tôi nên thêm rằng có ít nhất một sự khác biệt đáng kể khác giữa các kiểu thử nghiệm này, và tôi không thể nói đó là một 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 nhận được đầu vào từ một đường ống, chính xác là tốc độ được gửi bởi `cat`, và 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 sẽ nhận được một bộ mô tả tệp mở cho tệp thực. Chương trình của bạn - hoặc là trong nhiều trường hợp, các thư viện I / O của ngôn ngữ mà nó đượ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 đến 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 các cuộc gọi hệ thống đọc (2) rõ ràng. Những khác biệt này có thể có hiệu ứng lớn hơn nhiều so với kết quả benchmark của bạn so với chi phí nhỏ khi chạy nhị phân `cat`.

Tất nhiên 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ó  làm một cái gì đó thú vị, như sử dụng mmap (). Vì vậy, trong thực tế nó có thể là tốt để chạy các tiêu chuẩn cả hai cách; có lẽ giảm giá kết quả `mèo` bởi một số yếu tố nhỏ để" tha thứ "chi phí chạy` mèo`.


79
2018-03-13 23:04



Wow, điều đó khá sâu sắc! Trong khi tôi nhận thức được rằng con mèo không cần thiết cho việc nạp dữ liệu vào stdin của chương trình và <chuyển hướng shell được ưa thích, tôi thường bị mắc kẹt bởi mèo do luồng dữ liệu từ trái sang phải mà phương pháp cũ bảo tồn trực quan khi tôi giải thích về đường ống. Sự khác biệt về hiệu suất trong những trường hợp như vậy mà tôi thấy không đáng kể. Nhưng, tôi đánh giá cao việc giáo dục chúng tôi, Bela. - JJC
Tôi sẽ kiềm chế một upvote, cá nhân, vì điều này không giải quyết các câu hỏi ban đầu (lưu ý rằng việc sử dụng mèo là không đổi trong các ví dụ cạnh tranh). Nhưng, một lần nữa, cảm ơn cho các cuộc thảo luận trí tuệ về các ins and outs của * nix. - JJC
Chuyển hướng được phân tích cú pháp 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ỳ trình bao POSIX nào (tức là không phải `csh` và tôi không chắc chắn về exotica như` rc`:) - Bela Lubkin
Một lần nữa, ngoài sự chênh lệch hiệu suất gia tăng có lẽ không thú vị do nhị phân `cat` chạy cùng một lúc, bạn đang từ bỏ khả năng của chương trình đang thử nghiệm có khả năng mmap () tệp đầu vào. Điều này có thể tạo ra 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 tiêu chuẩn, bằng các ngôn ngữ khác nhau, chỉ sử dụng thành ngữ 'dòng đầu vào từ một tệp'. Nó phụ thuộc vào các hoạt động chi tiết của các thư viện I / O khác nhau của chúng. - Bela Lubkin
Đừng quên rằng bạn vẫn có thể làm trái sang phải với chuyển hướng: <file program gần như cùng một điều (với lời nhắc nhở của JJC) cat file | program. - Justin C. B.


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 while vòng lặp mang lại cho nó nội tuyến với Python phiên bản:

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 độ tới 2 giây và thiết lập bộ đệm lớn hơn đã giảm xuống còn 1 giây.


75
2018-03-11 16:37



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
Tôi đã quá vội vàng trong câu trả lời của tôi; thiết lập kích thước bộ đệm cho 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
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.
Matthieu, Mac sử dụng ngăn xếp quy trình 8MB 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 chuyể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ẽ thùng rác ngăn xếp nếu bộ đệm đi ra khỏi phạm vi. - SEK
Kích thước ngăn xếp mặc định của Windows @SEK là 1MB. - Étienne


getline, nhà điều hành 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ỏ. Tuy nhiên, nếu hiệu suất là một cái gì đó bạn quan tâm, bạn thực sự chỉ cần đệm toàn bộ tập tin 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ể quấn một luồng xung quanh bộ đệm đó để truy cập thuận tiện hơn như sau:

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

Ngoài ra, nếu bạn 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. Đọc và viết là đáng tin cậy hơn 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 cú pháp.


23
2018-02-21 03:32





Nhân tiện, lý do số lượng dòng cho phiên bản C ++ lớn hơn số đếm cho phiên bản Python là cờ eof chỉ được đặt khi một nỗ lực được thực hiện để đọc ngoài 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++;
};

11
2018-02-21 03:17



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


Các mã sau đây đã nhanh hơn cho tôi so với các mã khác được đăng ở đây cho đến nay: (Visual Studio 2013, 64-bit, 500 MB tập tin với chiều dài dòng thống nhất 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 bởi hơn một yếu tố 2.


8
2018-02-22 02:33





Trong ví dụ thứ hai của bạn (với scanf ()) lý do tại sao điều này vẫn cò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ỳ khoảng trắng nào (dấu cách, tab, dòng mới).

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


7





Yếu tố đầu tiên của câu trả lời: <iostream> chậm. Chết tiệt chậm. Tôi nhận được một hiệu suất rất lớn với scanf như ở 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;
}

6



Không thấy bài đăng này cho đến khi tôi thực hiện chỉnh sửa thứ ba của mình, nhưng cảm ơn một lần nữa vì đề xuất của bạn. Kỳ lạ thay, không có 2x hit cho tôi so với python bây giờ với dòng scanf trong edit3 ở trên. Tôi đang sử dụng 2,7, nhân tiện. - JJC
Sau khi sửa chữ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 c ++ iostream trên máy tính của tôi. (3 giây so với 1 giây) - karunski
Tương tự ở đây. Đồng bộ hóa để stdio là lừa. - J.N.
fgets thậm chí còn nhanh hơn; vui lòng xem chỉnh sửa 5 ở trên. Cảm ơn. - JJC