SORU
21 ŞUBAT 2012, Salı


Neden Standart girdiden okuma satır Python C daha yavaştır?

Stdin Python ve C kullanarak dize giriş satırları okumak karşılaştırmak istedim ve bir büyüklük sırası eşdeğer daha yavaş çalışıyor Python C kodu kodumu görmek için şok oldu. Benim C paslı ve henüz bir uzman Pythonista olmadığım için, lütfen eğer yanlış bir şey yapıyorsam ya da eğer bir şeyleri yanlış mıyım söyle bana.


(; dr cevap ver tl: deyim vardır: cin.(yanlış) sync_with_stdio ya da sadece fgets yerine kullanın.

tl;dr sonuçları: kaydırma masada soru ve benim göz altına tüm yol.)


C kod:

#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

Eşdeğer 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))

İşte benim sonuçlar:

$ 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

Düzenleme:Denedim şunu da belirtmek isterim ki her iki OS-X (10.6.8) altında ve bu 2.6.32 Linux (RHEL 6.2). İkincisi çok iri yarı bir server, bu da ilgili olmayan bir macbook pro.

Edit 2:(Artık geçerli olarak bu düzenleme kaldırıldı)

$ 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

Edit 3:

Tamam, python deposu satır okuduktan çalışırken J. N. önerisi denedim ama python hız için fark etmezdi.

Ben de char dizisine scanf yerine bir std içine getline J. N. önerisi çalıştı::string. Bingo! Bu python ve c için eşdeğer performans sonuçlandı . (3,333,333 bu arada sadece üç alan her bir kısa satır, 20 karakter geniş, Daha fazla olsa da bazen genellikle) olan giriş verileri ile LP.

Kod:

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

Hız:

$ 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

(Evet, Bunu birkaç kez yaptım.) Şimdi getline yerine scanf kullanacağım sanırım. Ama, eğer insanlar bu performans std isabet Eğer hala merak ediyorum::string/getline normal ve makul.

Edit 4 (: / Çözüm Edit) Son:

Ekleme: cin.sync_with_stdio(false);

Bir kodun hemen üstündeki orijinal while döngüm yukarıdaki Python daha hızlı çalışır.

Yeni performans karşılaştırması(bu benim Macbook Pro 2011), orijinal kodu kullanarak eşitleme devre dışı, özgün ve orijinal python, metin. 20 m hatları ile bir dosya üzerinde sırasıyla Evet, birkaç kez disk önbellekleme şaşkın ortadan kaldırmak için koştum.

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

@Sayesinde o cevap Cato Vaughn!Herhangi incelikleri insanlar yapabilir ya iyi referanslar insanlar işaret etmek için neden bu senkronize olur, ne demek, ne zaman faydalı, ne zaman tamam devre dışı olur büyük takdir tarafından gelecek nesillere.:-)

5 / Daha İyi Bir Çözüm Düzenleyin:

Gri Gandalf aşağıda önerdiği gibi, alır scanf veya uyumsuz cin yaklaşımı bile daha hızlı. Ben de scanf gets GÜVENSİZ ve taşması potansiyel nedeniyle kullanılmamalıdır de olduğunu öğrendim. Yani, bu yineleme fgets kullanarak, alır için daha güvenli bir alternatif yazdım. İşte sevgili acemi için: ilgili satır

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

Şimdi, burada sonuçları kullanarak daha büyük bir dosya (100M hatları; ~3.4 GB) hızlı bir server ile çok hızlı disk, karşılaştırma python, unsynced cin ve fgets yaklaşımlar olarak karşılaştırılması ile wc yarar. [Scanf sürümü segfaulted ve sorun giderme gibi hissetmiyorum.]:

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

Gördüğünüz gibi, fgets ama wc performans; bu tuvalet herhangi bir bellek kopyalama olmadan her karakteri inceliyor olması nedeniyle eminim çok daha iyi, hala güzel. Sanıyorum ki, bu noktada, diğer parça kodu olacak darboğaz, yani sanmıyorum optimize etmek için bu düzeyde olacağını bile değerli, hatta mümkünse (beri, tüm sonra, gerçekten ihtiyacı deposu okumak hatları bellek).

Ayrıca unutmayın küçük bir değiş tokuş ile kullanarak bir char * tampon ve fgets vs unsynced cin dize olan ikinci okuyabilir, satır uzunluğu, süre eski gerektirir sınırlayıcı giriş için bazı sınırlı sayıda. Uygulamada, bu arabellek geçerli giriş aşacak çok büyük bir değere ayarlanmış olabilir, muhtemelen sorun çoğu okuma için satır tabanlı bir giriş dosyaları.

Bu eğitici oldu. Görüş ve önerileriniz için herkese teşekkürler.

Edit 6:

Aşağıdaki yorum J. F. Sebastian önerdiği gibi, GNU wc yardımcı düz C kullanır read() (güvenli-okuma içinde.c sarıcı) bir anda parçalar (16 k bayt) okuma ve yeni satırları saymak için. İşte python eşdeğer J. F. kod dayalı (sadece döngü için: python yerine ilgili parçacığını gösteriyor

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

Bu sürüm performansı oldukça hızlıdır (hala biraz tabii: c wc ham programını daha yavaş olsa da

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

Yine, biraz aptal için beni karşılaştırmak C fgets/cin ve ilk python kod üzerinde bir el wc -l ve bu son python Pasajı diğer, son ikisi yok aslında mağaza okudum satırları ama sadece yeni satır sayısı. Yine de, ilginç farklı uygulamaları keşfetmek ve performans üzerindeki etkileri hakkında düşünmek. Tekrar teşekkürler!

Edit 7: Minik kriter ek ve tekrar

Bütünlüğü için, (senkronize) özgün C kodu ile aynı kutuda aynı dosya için okuma hızı update dedim. Yine, bu 100 metrelik bir çizgi hızlı bir disk üzerinde dosya için. İşte tam bu tablo şimdi:

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

CEVAP
21 ŞUBAT 2012, Salı


Varsayılan olarak, cin herhangi bir giriş tamponlama önlemek için neden istasyonu., eşitlenir. Eğer ana üstüne bu eklerseniz, çok daha iyi performans görmelisiniz:

std::ios_base::sync_with_stdio(false);

Girdi akışı, bir kerede bir karakter okumak yerine tamponlu olup, normal olarak, akış büyük parçalar halinde okunacak. Bu genellikle nispeten pahalı olan sistem çağrıları, sayısını azaltır. FILE* tabanlı stdio iostreams ancak, genellikle ayrı uygulamaları ve bu nedenle ayrı tamponlar var, bu ise her ikisi birlikte kullanılmıştır eğer bir sorun neden olabilir. Örneğin:

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

Eğer daha fazla giriş gerçekte gerektiğinden cin tarafından okundu, daha sonra ikinci tamsayı değeri kendi bağımsız tampon vardır scanf fonksiyon için kullanılabilir olmaz. Bu beklenmedik sonuçlara yol açacak.

Bu, varsayılan önlemek için akarsu stdio ile eşitlenir. Bunu başarmak için ortak bir şekilde cin her karakter stdio fonksiyonları kullanarak gerektiği gibi bir kerede okudum. Ne yazık ki, bu yükü bir sürü tanıttı. Giriş küçük miktarlarda, bu büyük bir sorun değil, ama hatları milyonlarca okurken, performans cezası önemli.

Neyse ki, kütüphane tasarımcıları da bu özellik eğer ne yaptığını, bu yüzden sync_with_stdio yöntemi sağladılar bilsen geliştirilmiş performans için devre dışı bırakmak mümkün olması gerektiğini karar verdi.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • hitcreatormusic2

    hitcreatormu

    21 Mayıs 2010
  • Xcode programming tutorials

    Xcode progra

    17 EYLÜL 2006
  • xiaoyu85

    xiaoyu85

    20 ŞUBAT 2010