Pertanyaan Mengapa membaca garis dari stdin jauh lebih lambat di C + + daripada Python?


Saya ingin membandingkan garis pembacaan masukan string dari stdin menggunakan Python dan C ++ dan terkejut melihat kode C ++ saya menjalankan urutan besarnya lebih lambat daripada kode Python yang setara. Karena C ++ saya berkarat dan saya belum ahli Pythonista, tolong beri tahu saya jika saya melakukan sesuatu yang salah atau jika saya salah memahami sesuatu.


(Jawaban TLDR: sertakan pernyataan: cin.sync_with_stdio(false) atau hanya digunakan fgets sebagai gantinya.

Hasil TLDR: gulir ke bawah ke bagian bawah pertanyaan saya dan lihat ke tabel.)


Kode 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 Equivalent:

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

Inilah hasil saya:

$ 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

Edit:  Saya harus mencatat bahwa saya mencoba ini baik di bawah Mac OS X v10.6.8 (Snow Leopard) dan Linux 2.6.32 (Red Hat Linux 6.2). Yang pertama adalah MacBook Pro, dan yang terakhir adalah server yang sangat gemuk, bukan berarti ini terlalu penting.

Edit 2:  (Menghapus hasil edit ini, karena tidak lagi berlaku)

$ 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

Sunting 3:

Oke, saya mencoba saran J.N. untuk mencoba agar Python menyimpan baris yang dibaca: tetapi tidak ada bedanya dengan kecepatan python.

Saya juga mencoba saran J.N. untuk menggunakan scanf menjadi char array bukan getline menjadi std::string. Bingo! Ini menghasilkan kinerja yang setara untuk Python dan C ++. (3,333,333 LPS dengan data input saya, yang dengan cara hanya garis pendek dari tiga bidang masing-masing, biasanya sekitar 20 karakter lebar, meskipun kadang-kadang lebih).

Kode:

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

Kecepatan:

$ 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

(Ya, saya menjalankannya beberapa kali.) Jadi, saya rasa sekarang saya akan menggunakannya scanf dari pada getline. Tapi, saya masih penasaran apakah orang-orang berpikir pertunjukan ini berasal std::string/getline khas dan masuk akal.

Edit 4 (adalah: Pengeditan / Solusi Akhir):

Menambahkan:

cin.sync_with_stdio(false);

Tepat di atas loop sementara asli saya di atas menghasilkan kode yang berjalan lebih cepat dari Python.

Perbandingan kinerja baru (ini adalah pada MacBook Pro 2011 saya), menggunakan kode asli, yang asli dengan sinkronisasi dinonaktifkan, dan kode Python asli, masing-masing, pada file dengan 20 juta baris teks. Ya, saya menjalankannya beberapa kali untuk menghilangkan disk cache yang membingungkan.

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

Terima kasih kepada @Vaughn Cato atas jawabannya! Setiap elaborasi orang dapat membuat atau referensi yang baik orang dapat menunjukkan mengapa sinkronisasi ini terjadi, apa artinya, ketika itu berguna, dan ketika itu baik-baik saja untuk menonaktifkan akan sangat dihargai oleh anak cucu. :-)

Edit 5 / Solusi Lebih Baik:

Seperti yang disarankan oleh Gandalf The Grey di bawah ini, gets bahkan lebih cepat daripada scanf atau tidak tersinkronisasi cin pendekatan. Saya juga belajar itu scanf dan gets keduanya TIDAK AMAN dan TIDAK AKAN DIGUNAKAN karena potensi buffer overflow. Jadi, saya menulis iterasi ini menggunakan fgets, alternatif yang lebih aman untuk didapatkan. Berikut adalah garis yang relevan untuk sesama noobs:

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.");

Sekarang, di sini adalah hasil menggunakan file yang lebih besar (100M baris; ~ 3.4 GB) pada server cepat dengan disk yang sangat cepat, membandingkan kode Python, yang tidak tersinkronisasi cin, dan fgetspendekatan, serta membandingkan dengan utilitas wc. [Itu scanf segmentasi versi rusak dan saya tidak ingin memecahkannya.]:

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

Seperti yang Anda lihat, fgets lebih baik, tetapi masih cukup jauh dari kinerja wc; Saya cukup yakin ini disebabkan oleh fakta bahwa wc memeriksa setiap karakter tanpa menyalin memori. Saya menduga bahwa, pada titik ini, bagian lain dari kode akan menjadi hambatan, jadi saya tidak berpikir mengoptimalkan ke tingkat itu bahkan akan bermanfaat, bahkan jika mungkin (karena, setelah semua, saya benar-benar perlu menyimpan garis baca dalam kenangan).

Juga perhatikan bahwa tradeoff kecil dengan menggunakan char * buffer dan fgets vs. tidak tersinkronisasi cin ke string adalah bahwa yang terakhir dapat membaca garis-garis dengan panjang apapun, sementara yang pertama membutuhkan input yang terbatas ke beberapa angka yang terbatas. Dalam praktiknya, ini mungkin bukan masalah untuk membaca sebagian besar file masukan berbasis garis, karena buffer dapat diatur ke nilai yang sangat besar yang tidak akan dilampaui oleh input yang valid.

Ini sudah mendidik. Terima kasih atas semua komentar dan saran Anda.

Edit 6:

Seperti yang disarankan oleh J.F. Sebastian pada komentar di bawah ini, utilitas GNU wc menggunakan plain C read() (dalam pembungkus safe-read.c) untuk membaca potongan (dari 16k byte) sekaligus dan menghitung baris baru. Ini setara dengan Python berdasarkan kode J.F. (hanya menunjukkan potongan yang relevan yang menggantikan Python for lingkaran:

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

Kinerja versi ini cukup cepat (meskipun masih sedikit lebih lambat dari utilitas Cc mentah, tentu saja):

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

Sekali lagi, itu agak konyol bagi saya untuk membandingkan C ++ fgets/cin dan kode python pertama di satu sisi wc -l dan potongan Python terakhir ini pada yang lain, karena dua yang terakhir tidak benar-benar menyimpan garis baca, tetapi hanya menghitung baris baru. Namun, menarik untuk mengeksplorasi semua implementasi yang berbeda dan memikirkan implikasi kinerja. Terima kasih lagi!

Edit 7: Addendum dan rekapitulasi tolok ukur yang kecil

Untuk kelengkapan, saya pikir saya akan memperbarui kecepatan baca untuk file yang sama pada kotak yang sama dengan kode C ++ asli (disinkronkan). Sekali lagi, ini adalah untuk file baris 100M pada disk yang cepat. Inilah tabel lengkapnya sekarang:

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

1456
2018-02-21 03:24


asal


Jawaban:


Secara default, cin disinkronkan dengan stdio, yang menyebabkannya untuk menghindari buffering input apa pun. Jika Anda menambahkan ini ke bagian atas utama Anda, Anda akan melihat kinerja yang jauh lebih baik:

std::ios_base::sync_with_stdio(false);

Biasanya, ketika aliran masukan disangga, alih-alih membaca satu karakter pada satu waktu, aliran akan dibaca dalam potongan yang lebih besar. Ini mengurangi jumlah panggilan sistem, yang biasanya relatif mahal. Namun, sejak itu FILE* berdasarkan stdio dan iostreams sering memiliki implementasi terpisah dan buffer karena itu terpisah, ini bisa menimbulkan masalah jika keduanya digunakan bersama. Sebagai contoh:

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

Jika lebih banyak masukan dibaca oleh cin daripada yang sebenarnya dibutuhkan, maka nilai integer kedua tidak akan tersedia untuk scanf fungsi, yang memiliki buffer independen sendiri. Ini akan mengarah pada hasil yang tidak diharapkan.

Untuk menghindari hal ini, secara default, aliran disinkronkan stdio. Salah satu cara umum untuk mencapai ini adalah dengan memilikinya cin baca setiap karakter satu per satu sesuai kebutuhan menggunakan stdio fungsi. Sayangnya, ini memperkenalkan banyak overhead. Untuk input dalam jumlah kecil, ini bukan masalah besar, tetapi ketika Anda membaca jutaan baris, penalti kinerja adalah signifikan.

Untungnya, perancang perpustakaan memutuskan bahwa Anda juga harus dapat menonaktifkan fitur ini untuk mendapatkan peningkatan kinerja jika Anda tahu apa yang Anda lakukan, sehingga mereka menyediakan sync_with_stdio metode.


1279
2018-03-11 18:10



Hanya karena penasaran saya telah melihat apa yang terjadi di bawah kap mesin, dan saya telah menggunakannya dtruss / strace pada setiap tes.

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



Saya beberapa tahun di belakang sini, tetapi:

Di 'Edit 4/5/6' dari posting asli, Anda menggunakan konstruksi:

$ /usr/bin/time cat big_file | program_to_benchmark

Ini salah dalam beberapa cara berbeda:

  1. Anda sebenarnya mengatur waktu eksekusi `kucing`, bukan patokan Anda. Penggunaan CPU 'user' dan 'sys' yang ditampilkan oleh `time` adalah` kucing`, bukan program benchmarked Anda. Lebih buruk lagi, waktu 'nyata' juga belum tentu akurat. Bergantung pada implementasi `cat` dan jalur pipa di OS lokal Anda, mungkin` kucing` menulis buffer raksasa terakhir dan keluar jauh sebelum proses pembaca selesai bekerja.

  2. Penggunaan `cat` tidak diperlukan dan bahkan kontraproduktif; Anda menambahkan bagian yang bergerak. Jika Anda berada pada sistem yang cukup lama (yaitu dengan satu CPU dan - pada komputer generasi tertentu - I / O lebih cepat dari CPU) - fakta bahwa `kucing` sedang berjalan dapat sangat mewarnai hasilnya. Anda juga tunduk pada input apa pun dan buffering output dan pemrosesan lain `kucing` dapat dilakukan. (Ini mungkin akan memberi Anda penghargaan 'Penggunaan Kucing yang Tidak Berguna' jika saya adalah Randal Schwartz: https://en.wikipedia.org/wiki/Cat_(Unix)#UUOC_(Useless_Use_Of_Cat))

Konstruksi yang lebih baik adalah:

$ /usr/bin/time program_to_benchmark < big_file

Dalam pernyataan ini adalah kulit yang membuka big_file, meneruskannya ke program Anda (well, sebenarnya ke `time` yang kemudian mengeksekusi program Anda sebagai subproses) sebagai deskriptor file yang sudah terbuka. 100% pembacaan file adalah tanggung jawab program yang Anda coba patok. Ini memberi Anda pembacaan nyata dari kinerjanya tanpa komplikasi palsu.

Saya akan menyebutkan dua kemungkinan, tetapi sebenarnya salah, 'perbaikan' yang juga dapat dipertimbangkan (tetapi saya 'nomor' mereka berbeda karena ini bukan hal-hal yang salah dalam posting asli):

J. Anda dapat 'memperbaiki' ini dengan hanya mengatur waktu program Anda:

$ cat big_file | /usr/bin/time program_to_benchmark

B. atau dengan mengatur waktu seluruh jalur pipa:

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

Ini salah untuk alasan yang sama seperti # 2: mereka masih menggunakan `cat` secara tidak perlu. Saya menyebutkannya karena beberapa alasan:

  • mereka lebih 'alami' untuk orang yang tidak sepenuhnya nyaman dengan fasilitas redirection I / O dari shell POSIX

  • mungkin ada kasus di mana `kucing` aku s diperlukan (misalnya: file yang akan dibaca membutuhkan semacam hak istimewa untuk mengakses, dan Anda tidak ingin memberikan hak istimewa itu ke program yang akan dibakukan: `sudo cat / dev / sda | / usr / bin / time my_compression_test - no-output`)

  • dalam praktek, pada mesin modern, 'kucing' yang ditambahkan dalam pipa mungkin tidak ada konsekuensi nyata

Tapi saya mengatakan hal terakhir itu dengan sedikit ragu. Jika kita memeriksa hasil terakhir di 'Edit 5' -

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

- ini mengklaim bahwa `kucing` mengkonsumsi 74% dari CPU selama pengujian; dan memang 1,34 / 1,83 sekitar 74%. Mungkin serangkaian:

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

hanya akan mengambil sisa 0,49 detik! Mungkin tidak: `kucing` di sini harus membayar untuk membaca () sistem panggilan (atau yang setara) yang mentransfer file dari 'disk' (sebenarnya buffer cache), serta menulis pipa untuk mengirimnya ke` wc`. Tes yang benar masih harus melakukan panggilan baca itu; hanya panggilan dari satu saluran ke pipa lain dan dari pipa yang bisa diselamatkan, dan itu seharusnya cukup murah.

Namun, saya memprediksi Anda akan dapat mengukur perbedaan antara `file kucing | wc-l` dan `wc-l <file` dan temukan perbedaan yang nyata (persentase 2 digit). Setiap tes yang lebih lambat akan membayar penalti serupa dalam waktu absolut; yang bagaimanapun akan berjumlah sebagian kecil dari total waktu yang lebih besar.

Bahkan saya melakukan beberapa tes cepat dengan file sampah 1,5 gigabyte, pada sistem Linux 3.13 (Ubuntu 14.04), memperoleh hasil ini (ini benar-benar hasil 'terbaik dari 3'; setelah priming cache, tentu saja):

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

Perhatikan bahwa dua hasil pipeline mengklaim telah mengambil lebih banyak waktu CPU (user + sys) daripada realtime. Ini karena saya menggunakan perintah 'waktu' dari shell (Bash), yang sadar akan pipeline; dan saya berada di mesin multi-core di mana proses terpisah dalam pipa dapat menggunakan inti yang terpisah, mengumpulkan waktu CPU lebih cepat daripada waktu nyata. Menggunakan / usr / bin / time Saya melihat waktu CPU lebih kecil daripada waktu nyata - menunjukkan bahwa itu hanya bisa waktu elemen pipa tunggal dilewatkan ke pada baris perintahnya. Juga, output shell memberikan milidetik sementara / usr / bin / waktu hanya memberikan hundreth detik.

Jadi pada tingkat efisiensi `wc-l`,` kucing` membuat perbedaan besar: 409/283 = 1,453 atau 45,3% lebih banyak waktu nyata, dan 775/280 = 2,768, atau kekalahan 177% lebih banyak CPU yang digunakan! Di kotak uji acak saya ada-di-saat-saat itu.

Saya harus menambahkan bahwa setidaknya ada satu perbedaan signifikan lainnya di antara gaya pengujian ini, dan saya tidak dapat mengatakan apakah itu manfaat atau kesalahan; Anda harus memutuskan ini sendiri:

Saat Anda menjalankan `cat big_file | / usr / bin / time my_program`, program Anda menerima masukan dari sebuah pipa, tepatnya kecepatan yang dikirim oleh `kucing`, dan dalam potongan tidak lebih besar dari yang ditulis oleh` kucing`.

Ketika Anda menjalankan `/ usr / bin / time my_program <big_file`, program Anda menerima deskriptor file terbuka ke file yang sebenarnya. Program Anda - atau dalam banyak kasus pustaka I / O dari bahasa yang ditulisnya - dapat mengambil tindakan yang berbeda ketika disajikan dengan deskriptor file yang merujuk file biasa. Mungkin menggunakan mmap (2) untuk memetakan file input ke dalam ruang alamatnya, daripada menggunakan read eksplisit (2) panggilan sistem. Perbedaan-perbedaan ini bisa memiliki efek yang jauh lebih besar pada hasil benchmark Anda daripada biaya kecil menjalankan biner `kucing`.

Tentu saja ini adalah hasil patokan yang menarik jika program yang sama berkinerja sangat berbeda di antara kedua kasus tersebut. Ini menunjukkan bahwa, memang, program atau perpustakaan I / O-nya adalah melakukan sesuatu yang menarik, seperti menggunakan mmap (). Jadi dalam praktiknya mungkin baik untuk menjalankan tolok ukur kedua cara; mungkin mengabaikan hasil `cat` oleh beberapa faktor kecil untuk" memaafkan "biaya menjalankan` cat` itu sendiri.


77
2018-03-13 23:04



Saya mereproduksi hasil asli di komputer saya menggunakan g ++ pada Mac.

Menambahkan pernyataan berikut ke versi C ++ tepat sebelum while loop membuatnya sejajar dengan Python versi:

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

sync_with_stdio meningkatkan kecepatan hingga 2 detik, dan mengatur buffer yang lebih besar membawanya ke 1 detik.


75
2018-03-11 16:37



getline, operator streaming, scanf, bisa nyaman jika Anda tidak peduli tentang waktu pemuatan file atau jika Anda memuat file teks kecil. Tapi, jika kinerja adalah sesuatu yang Anda pedulikan, Anda harus benar-benar hanya menyangga seluruh file ke dalam memori (dengan asumsi itu akan cocok).

Inilah contohnya:

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

Jika mau, Anda bisa membungkus aliran di sekitar buffer itu untuk akses yang lebih mudah seperti ini:

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

Juga, jika Anda mengendalikan file, pertimbangkan untuk menggunakan format data biner datar alih-alih teks. Lebih dapat diandalkan untuk membaca dan menulis karena Anda tidak harus berurusan dengan semua ambiguitas spasi. Ini juga lebih kecil dan lebih cepat untuk mengurai.


23
2018-02-21 03:32



By the way, alasan jumlah baris untuk versi C ++ adalah satu lebih besar dari hitungan untuk versi Python adalah bahwa bendera eof hanya diatur ketika suatu upaya dilakukan untuk membaca di luar dari eof. Jadi loop yang benar adalah:

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

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

11
2018-02-21 03:17



Kode berikut lebih cepat untuk saya daripada kode lain yang diposting di sini sejauh ini: (Visual Studio 2013, 64-bit, file 500 MB dengan panjang garis seragam dalam [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'; });
}

Ini mengalahkan semua upaya Python saya dengan lebih dari satu faktor 2.


8
2018-02-22 02:33



Dalam contoh kedua Anda (dengan scanf ()) alasan mengapa ini masih lebih lambat mungkin karena scanf ("% s") mem-parsing string dan mencari setiap spasi (spasi, tab, baris baru).

Juga, ya, CPython melakukan beberapa cache untuk menghindari pembacaan harddisk.


7



Unsur pertama dari sebuah jawaban: <iostream> lambat. Sangat lambat. Saya mendapatkan peningkatan performa yang luar biasa scanf seperti di bawah, tetapi masih dua kali lebih lambat daripada 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



Yah, saya melihat bahwa dalam solusi kedua Anda, Anda beralih dari cin untuk scanf, yang merupakan saran pertama yang akan saya buat untuk Anda (cin is sloooooooooooow). Sekarang, jika Anda beralih dari scanf untuk fgets, Anda akan melihat peningkatan kinerja lainnya: fgets adalah fungsi C ++ tercepat untuk input string.

BTW, tidak tahu tentang hal sinkronisasi itu, bagus. Tetapi Anda harus tetap mencoba fgets.


5