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

1840

Saya ingin membandingkan baris-baris input 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 menjadi ahli Pythonista, tolong beri tahu saya jika saya melakukan sesuatu yang salah atau jika saya salah mengerti sesuatu.


(Jawaban TLDR: sertakan pernyataan: cin.sync_with_stdio(false)atau gunakan fgetssaja.

Hasil TLDR: gulirkan semuanya ke bawah pertanyaan saya dan lihat 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

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

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

Saya harus mencatat bahwa saya sudah mencoba keduanya di 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 relevan.

$ 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

Adendum dan rekap benchmark 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 untuk file baris 100 juta pada disk cepat. Inilah perbandingannya, dengan beberapa solusi / pendekatan:

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
JJC
sumber
14
Apakah Anda menjalankan tes Anda beberapa kali? Mungkin ada masalah cache disk.
Vaughn Cato
9
@ JJC: Saya melihat dua kemungkinan (dengan asumsi Anda telah menghapus masalah caching yang disarankan oleh David): 1) <iostream>kinerja menyebalkan. Bukan pertama kali itu terjadi. 2) Python cukup pintar untuk tidak menyalin data dalam for for loop karena Anda tidak menggunakannya. Anda dapat menguji ulang mencoba menggunakan scanfdan a char[]. Sebagai alternatif, Anda dapat mencoba menulis ulang loop sehingga ada sesuatu yang dilakukan dengan string (mis. Simpan huruf 5 dan hasilkan sebagai hasilnya).
JN
15
Masalahnya adalah sinkronisasi dengan stdio - lihat jawaban saya.
Vaughn Cato
19
Karena sepertinya tidak ada yang menyebutkan mengapa Anda mendapatkan garis tambahan dengan C ++: Jangan menguji cin.eof()!! Masukkan getlinepanggilan ke dalam pernyataan 'jika`.
Xeo
21
wc -lcepat karena membaca aliran lebih dari satu baris sekaligus (mungkin fread(stdin)/memchr('\n')kombinasi). Hasil python berada dalam urutan yang sama besarnya misalnya,wc-l.py
jfs

Jawaban:

1644

Secara default, cindisinkronkan dengan stdio, yang menyebabkannya menghindari buffering input. 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 input buffer, alih-alih membaca satu karakter pada suatu waktu, aliran akan dibaca dalam potongan yang lebih besar. Ini mengurangi jumlah panggilan sistem, yang biasanya relatif mahal. Namun, karena FILE*berbasis stdiodan iostreamssering memiliki implementasi terpisah dan oleh karena itu memisahkan buffer, ini dapat menyebabkan masalah jika keduanya digunakan bersama. Sebagai contoh:

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

Jika lebih banyak input dibaca cindaripada yang sebenarnya dibutuhkan, maka nilai integer kedua tidak akan tersedia untuk scanffungsi, yang memiliki buffer independen sendiri. Ini akan menghasilkan hasil yang tidak terduga.

Untuk menghindari ini, secara default, stream disinkronkan dengan stdio. Satu cara umum untuk mencapai ini adalah cinmembaca setiap karakter satu per satu sesuai kebutuhan menggunakan stdiofungsi. Sayangnya, ini memperkenalkan banyak overhead. Untuk sejumlah kecil input, ini bukan masalah besar, tetapi ketika Anda membaca jutaan baris, penalti kinerja sangat signifikan.

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

Vaughn Cato
sumber
142
Ini harus di atas. Ini hampir pasti benar. Jawabannya tidak terletak pada mengganti membaca dengan fscanfpanggilan, karena itu cukup sederhana tidak berfungsi seperti halnya Python. Python harus mengalokasikan memori untuk string, mungkin beberapa kali karena alokasi yang ada dianggap tidak memadai - persis seperti pendekatan C ++ std::string. Tugas ini hampir pasti I / O terikat dan ada terlalu banyak FUD berkeliling tentang biaya membuat std::stringobjek di C ++ atau menggunakan <iostream>dalam dan dari dirinya sendiri.
Karl Knechtel
51
Ya, menambahkan baris ini tepat di atas yang asli saya sementara loop mempercepat kode hingga melampaui python. Saya akan memposting hasilnya sebagai hasil edit terakhir. Terima kasih lagi!
JJC
6
Ya, ini sebenarnya berlaku untuk cout, cerr, dan menyumbat juga.
Vaughn Cato
2
Untuk membuat cout, cin, cerr dan clog lebih cepat, lakukan dengan cara ini std :: ios_base :: sync_with_stdio (false);
01100110
56
Perhatikan bahwa sync_with_stdio()ini adalah fungsi anggota statis, dan panggilan ke fungsi ini pada objek streaming mana pun (mis. cin) Mengaktifkan atau menonaktifkan sinkronisasi untuk semua objek iostream standar.
John Zwinck
171

Hanya karena penasaran saya telah melihat apa yang terjadi di bawah tenda, dan saya telah menggunakan 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
2mia
sumber
159

Saya beberapa tahun di belakang sini, tetapi:

Dalam 'Edit 4/5/6' dari pos asli, Anda menggunakan konstruksi:

$ /usr/bin/time cat big_file | program_to_benchmark

Ini salah dalam beberapa cara berbeda:

  1. Anda sebenarnya menentukan waktu pelaksanaan cat, bukan tolok ukur Anda. Penggunaan CPU 'pengguna' dan 'sistem' yang ditampilkan oleh timemereka adalah cat, bukan program yang Anda patok. Lebih buruk lagi, waktu 'nyata' juga belum tentu akurat. Bergantung pada implementasi catdan jalur pipa di OS lokal Anda, ada kemungkinan bahwa catmenulis buffer raksasa terakhir dan keluar jauh sebelum proses pembaca menyelesaikan pekerjaannya.

  2. Penggunaan cattidak perlu dan pada kenyataannya kontraproduktif; Anda menambahkan bagian yang bergerak. Jika Anda menggunakan sistem yang cukup lama (yaitu dengan satu CPU dan - pada generasi komputer tertentu - I / O lebih cepat dari CPU) - fakta yang catberjalan secara substansial dapat mewarnai hasil. Anda juga tunduk pada buffering input dan output apa pun dan pemrosesan lainnya yang catdapat dilakukan. (Ini kemungkinan akan memberi Anda penghargaan 'Penggunaan Kucing Tidak Berguna' jika saya adalah Randal Schwartz.

Konstruksi yang lebih baik adalah:

$ /usr/bin/time program_to_benchmark < big_file

Dalam pernyataan ini, itu adalah shell yang membuka big_file, meneruskannya ke program Anda (yah, sebenarnya timeyang kemudian menjalankan program Anda sebagai subprocess) sebagai deskriptor file yang sudah terbuka. 100% pembacaan file sepenuhnya merupakan tanggung jawab program yang Anda coba patok. Ini membuat Anda membaca nyata kinerjanya tanpa komplikasi palsu.

Saya akan menyebutkan dua kemungkinan, tetapi sebenarnya salah, 'perbaikan' yang juga dapat dipertimbangkan (tapi saya 'beri nomor' secara berbeda karena ini bukan hal-hal yang salah di pos asli):

A. Anda dapat 'memperbaiki' ini dengan menentukan waktu hanya program Anda:

$ cat big_file | /usr/bin/time program_to_benchmark

B. atau dengan mengatur waktu seluruh pipa:

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

Ini salah karena alasan yang sama dengan # 2: mereka masih menggunakan yang cattidak perlu. Saya menyebutkannya karena beberapa alasan:

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

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

  • dalam prakteknya , pada mesin modern, penambahan catdalam pipa mungkin tidak ada konsekuensi nyata.

Tetapi saya mengatakan hal terakhir itu dengan ragu-ragu. Jika kami memeriksa hasil terakhir di 'Sunting 5' -

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

- klaim ini yang catmengonsumsi 74% CPU selama pengujian; dan memang 1,34 / 1,83 adalah sekitar 74%. Mungkin serangkaian:

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

akan hanya mengambil 0,49 detik tersisa! Mungkin tidak: di catsini harus membayar untuk read()panggilan sistem (atau yang setara) yang mentransfer file dari 'disk' (sebenarnya buffer cache), serta pipa menulis untuk mengirimkannya wc. Tes yang benar masih harus melakukan ituread() panggilan panggilan itu; hanya panggilan write-to-pipe dan read-from-pipe yang akan disimpan, dan itu seharusnya cukup murah.

Namun, saya memperkirakan Anda akan dapat mengukur perbedaan antara cat file | wc -ldan wc -l < filedan menemukan perbedaan yang nyata (persentase 2 digit). Setiap tes yang lebih lambat akan membayar penalti yang sama dalam waktu absolut; namun jumlah yang lebih kecil dari total waktu yang lebih besar.

Sebenarnya saya melakukan beberapa tes cepat dengan file sampah 1,5 gigabyte, pada sistem Linux 3.13 (Ubuntu 14.04), memperoleh hasil ini (ini sebenarnya hasil 'terbaik dari 3'; setelah melakukan 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 kedua hasil pipeline mengklaim telah mengambil lebih banyak waktu CPU (pengguna + sistem) daripada waktu jam dinding yang sebenarnya. Ini karena saya menggunakan perintah 'waktu' built-in shell (bash), yang sadar akan pipeline; dan saya menggunakan mesin multi-core di mana proses terpisah dalam suatu pipeline dapat menggunakan core terpisah, mengakumulasi waktu CPU lebih cepat daripada realtime. Menggunakan /usr/bin/timeSaya melihat waktu CPU lebih kecil dari waktu nyata - menunjukkan bahwa itu hanya dapat mengatur waktu elemen pipa tunggal dilewatkan ke itu pada baris perintah. Juga, output shell memberikan milidetik sementara /usr/bin/timehanya memberikan seperseratus detik.

Jadi pada tingkat efisiensi wc -l,cat membuat perbedaan besar: 409/283 = 1,453 atau 45,3% lebih realtime, dan 775/280 = 2,768, atau 177% lebih banyak menggunakan CPU! Pada kotak uji acak-nya-ada-pada-saat-itu.

Saya harus menambahkan bahwa setidaknya ada satu perbedaan signifikan antara gaya pengujian ini, dan saya tidak bisa mengatakan apakah ini merupakan keuntungan atau kesalahan; Anda harus memutuskan ini sendiri:

Ketika Anda menjalankan cat big_file | /usr/bin/time my_program, program Anda menerima input dari pipa, tepat pada kecepatan yang dikirim oleh cat, dan dalam potongan tidak lebih besar dari yang ditulis oleh cat.

Ketika Anda menjalankan /usr/bin/time my_program < big_file, program Anda menerima deskriptor file terbuka ke file aktual. Program Anda - atau dalam banyak kasus, perpustakaan I / O dari bahasa tempat penulisan itu - dapat mengambil tindakan yang berbeda ketika disajikan dengan deskriptor file yang merujuk file biasa. Ini dapat digunakan mmap(2)untuk memetakan file input ke ruang alamatnya, daripada menggunakan read(2)panggilan sistem eksplisit . Perbedaan ini dapat memiliki efek yang jauh lebih besar pada hasil benchmark Anda daripada biaya kecil menjalankan catbiner.

Tentu saja ini merupakan hasil tolok ukur yang menarik jika program yang sama memiliki kinerja yang berbeda secara signifikan antara kedua kasus tersebut. Ini menunjukkan bahwa, memang, program atau perpustakaan I / O -nya melakukan sesuatu yang menarik, seperti menggunakan mmap(). Jadi dalam praktiknya mungkin lebih baik menjalankan benchmark dua arah; mungkin mengabaikan cathasil oleh beberapa faktor kecil untuk "memaafkan" biaya menjalankannya catsendiri.

Bela Lubkin
sumber
26
Wow, itu cukup mendalam! Walaupun saya telah menyadari bahwa cat tidak perlu untuk memasukkan input ke stdin program dan bahwa <shell redirect lebih disukai, saya biasanya terjebak ke cat karena aliran data kiri-ke-kanan yang dipertahankan oleh metode sebelumnya secara visual. ketika saya beralasan tentang jaringan pipa. Perbedaan kinerja dalam kasus-kasus seperti yang saya temukan diabaikan. Tapi, saya menghargai Anda mendidik kami, Bela.
JJC
11
Pengalihan diuraikan keluar dari baris perintah shell pada tahap awal, yang memungkinkan Anda untuk melakukan salah satu dari ini, jika itu memberikan tampilan yang lebih menyenangkan dari aliran kiri ke kanan: $ < big_file time my_program $ time < big_file my_program Ini harus bekerja di setiap shell POSIX (yaitu bukan `csh `dan saya tidak yakin tentang eksotika seperti` rc`:)
Bela Lubkin
6
Sekali lagi, selain dari perbedaan kinerja inkremental yang mungkin tidak menarik karena biner `cat` berjalan pada saat yang sama, Anda melepaskan kemungkinan program yang sedang diuji untuk dapat mmap () file input. Ini bisa membuat perbedaan besar dalam hasil. Ini benar bahkan jika Anda sendiri yang membuat tolok ukur, dalam berbagai bahasa, hanya menggunakan idiom 'input line from a file'. Itu tergantung pada cara kerja rinci dari berbagai perpustakaan I / O mereka.
Bela Lubkin
2
Catatan sisi: Bash's builtin timemengukur seluruh pipa bukannya program pertama. time seq 2 | while read; do sleep 1; donemencetak 2 detik, /usr/bin/time seq 2 | while read; do sleep 1; donemencetak 0 detik.
folkol
1
@folkol - ya, << Perhatikan bahwa dua hasil pipeline [menunjukkan] lebih banyak CPU [daripada] realtime [menggunakan] (Bash) built-in 'time' command; ... / usr / bin / time ... hanya dapat mengatur waktu elemen pipeline tunggal diteruskan ke sana pada baris perintahnya. >> '
Bela Lubkin
90

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

Menambahkan pernyataan berikut ke versi C ++ tepat sebelum whileloop membawanya sejajar dengan versi Python :

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

sync_with_stdio meningkatkan kecepatan menjadi 2 detik, dan pengaturan buffer yang lebih besar membawanya ke 1 detik.

karunski
sumber
5
Anda mungkin ingin mencoba berbagai ukuran buffer untuk mendapatkan informasi yang lebih bermanfaat. Saya menduga Anda akan melihat pengembalian yang berkurang dengan cepat.
Karl Knechtel
8
Aku terlalu terburu-buru dalam menjawab; mengatur ukuran buffer ke sesuatu selain default tidak menghasilkan perbedaan yang cukup besar.
karunski
109
Saya juga akan menghindari pengaturan buffer 1MB di stack. Ini dapat menyebabkan stackoverflow (meskipun saya kira ini adalah tempat yang bagus untuk berdebat tentang hal itu!)
Matthieu M.
11
Matthieu, Mac menggunakan tumpukan proses 8MB secara default. Linux menggunakan standar 4MB per utas, IIRC. 1MB tidak terlalu menjadi masalah bagi program yang mengubah input dengan kedalaman stack yang relatif dangkal. Lebih penting lagi, std :: cin akan membuang tumpukan jika buffer keluar dari ruang lingkup.
SEK
22
@SEK Ukuran tumpukan standar Windows adalah 1MB.
Étienne
39

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

Ini sebuah contoh:

//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 dapat membungkus aliran di sekitar buffer itu untuk akses yang lebih nyaman seperti ini:

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

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

Stu
sumber
20

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

Petter
sumber
Anda bahkan bisa mendapatkan lebih cepat dari itu dengan program C kecil yang sangat sederhana tetapi benar-benar langsung yang secara iteratif membuat readsyscalls unbuffered menjadi buffer statis dengan panjang BUFSIZEatau melalui mmapsyscall yang sesuai , lalu mencambuk buffer yang menghitung baris baru à la for (char *cp = buf; *cp; cp++) count += *cp == "\n". Anda harus menyetel BUFSIZEuntuk sistem Anda, stdio mana yang sudah dilakukan untuk Anda. Tetapi forloop itu harus dikompilasi ke instruksi bahasa assembler cepat berteriak luar biasa untuk perangkat keras kotak Anda.
tchrist
3
count_if dan lambda juga mengkompilasi ke "assembler awesomely screaming-fast".
Petter
17

Ngomong-ngomong, alasan jumlah baris untuk versi C ++ lebih besar dari jumlah untuk versi Python adalah bahwa bendera bukti hanya akan ditetapkan ketika upaya dilakukan untuk membaca di luar bukti. Jadi loop yang benar adalah:

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

    if (!cin.eof())
        line_count++;
};
Gregg
sumber
70
Lingkaran yang benar-benar benar adalah: while (getline(cin, input_line)) line_count++;
Jonathan Wakely
2
@ Jonathan. Sebenarnya saya tahu bahwa saya sangat terlambat, tetapi gunakan ++line_count;dan tidak line_count++;.
val berkata Reinstate Monica
7
@val jika itu membuat perbedaan kompiler Anda memiliki bug. Variabelnya adalah a long, dan kompilernya cukup mampu mengatakan bahwa hasil kenaikannya tidak digunakan. Jika tidak menghasilkan kode yang identik untuk postincrement dan preincrement, itu rusak.
Jonathan Wakely
2
Memang setiap kompiler yang layak akan dapat mendeteksi penyalahgunaan pasca kenaikan dan menggantinya dengan pra-kenaikan sebagai gantinya, tetapi kompiler tidak diharuskan untuk melakukannya . Jadi tidak, itu tidak rusak bahkan jika kompiler tidak melakukan substitusi. Selain itu, menulis ++line_count;bukannya line_count++;tidak ada salahnya :)
Fareanor
1
@valsaysReinstateMonica Dalam contoh khusus ini, mengapa salah satu lebih disukai? Hasilnya tidak digunakan di sini, jadi itu akan dibaca setelah while, kan? Apakah penting jika ada kesalahan dan Anda ingin memastikan line_countitu benar? Saya hanya menebak tetapi saya tidak mengerti mengapa itu penting.
TankorSmash
14

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

Juga, ya, CPython melakukan caching untuk menghindari pembacaan harddisk.

davinchi
sumber
12

Unsur pertama jawaban: <iostream>lambat. Sangat lambat. Saya mendapatkan peningkatan kinerja besar dengan scanfseperti di bawah ini, tetapi masih dua kali lebih lambat dari 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;
}
JN
sumber
Tidak melihat posting ini sampai saya membuat edit ketiga saya, tetapi terima kasih lagi atas saran Anda. Anehnya, tidak ada 2x hit untuk saya vs. python sekarang dengan garis scanf di edit3 di atas. Saya menggunakan 2,7, omong-omong.
JJC
10
Setelah memperbaiki versi c ++, versi stdio ini jauh lebih lambat daripada versi c ++ iostreams di komputer saya. (3 detik vs 1 detik)
karunski
10

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

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

José Ernesto Lara Rodríguez
sumber
2
Kecuali fgetsakan salah (dalam hal jumlah baris, dan dalam hal memisahkan garis di loop jika Anda benar-benar perlu menggunakannya) untuk garis yang cukup besar, tanpa pemeriksaan tambahan untuk garis yang tidak lengkap (dan mencoba untuk mengkompensasi untuk itu melibatkan alokasi buffer besar yang tidak perlu) , di mana std::getlinemenangani realokasi untuk mencocokkan input aktual dengan mulus). Cepat dan salah itu mudah, tetapi hampir selalu layak untuk menggunakan "sedikit lebih lambat, tetapi benar", yang mematikan sync_with_stdiomembuat Anda.
ShadowRanger