SORU
21 ŞUBAT 2012, Salı


Neden şiddetli bir dize Python C den daha yavaş?

Hızı biraz kazanmak için bir çaba içinde C Python bazı kod dönüştürme ve paslı C yeteneklerimi keskinleştirmek için çalışıyorum. Dün standart girdiden okuma çizgileri naif bir uygulama daha C Python (this) çok daha hızlı olduğu zaman şok oldum. Bugün, nihayet birleştirme sınırlayıcıları ile C bir dize bölmek için nasıl düşündüm (python paylaşalım benzer anlambilim()), şimdi de deja vu yaşıyor! C benim kod çok uzun iş de en az dünkü ders için olduğu gibi büyüklük daha, bir sipariş yapmak için alır.

Kod Python:

#!/usr/bin/env python
from __future__ import print_function                                            
import time
import sys

count = 0
start_time = time.time()
dummy = None

for line in sys.stdin:
    dummy = line.split()
    count  = 1

delta_sec = int(time.time() - start_time)
print("Python: Saw {0} lines in {1} seconds. ".format(count, delta_sec), end='')
if delta_sec > 0:
    lps = int(count/delta_sec)
    print("  Crunch Speed: {0}".format(lps))
else:
    print('')

C Kod:

#include <iostream>                                                              
#include <string>
#include <sstream>
#include <time.h>
#include <vector>

using namespace std;

void split1(vector<string> &tokens, const string &str,
        const string &delimiters = " ") {
    // Skip delimiters at beginning
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);

    // Find first non-delimiter
    string::size_type pos = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos) {
        // Found a token, add it to the vector
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next non-delimiter
        pos = str.find_first_of(delimiters, lastPos);
    }
}

void split2(vector<string> &tokens, const string &str, char delim=' ') {
    stringstream ss(str); //convert string to stream
    string item;
    while(getline(ss, item, delim)) {
        tokens.push_back(item); //add token to vector
    }
}

int main() {
    string input_line;
    vector<string> spline;
    long count = 0;
    int sec, lps;
    time_t start = time(NULL);

    cin.sync_with_stdio(false); //disable synchronous IO

    while(cin) {
        getline(cin, input_line);
        spline.clear(); //empty the vector for the next line to parse

        //I'm trying one of the two implementations, per compilation, obviously:
//        split1(spline, input_line);  
        split2(spline, input_line);

        count  ;
    };

    count--; //subtract for final over-read
    sec = (int) time(NULL) - start;
    cerr << "C     : Saw " << count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } else
        cerr << endl;
    return 0;

//compiled with: g   -Wall -O3 -o split1 split_1.cpp

İki farklı bölme uygulamaları denedim unutmayın. (Split1) belirteçleri için arama yapmak için string yöntemlerini kullanır ve birden çok belirteç birleştirme yanı sıra çok sayıda simgeleri işlemek mümkün (here geliyor). İkinci (split2) kullanır getline okuma dize olarak bir akış değil birleştirme sınırlayıcı ve sadece destekleyen tek bir delimeter karakter (nasıldı Gönderen birkaç StackOverflow kullanıcı cevaplar için dize bölme sorular).

Bu çeşitli sipariş birden çok kez karşılaştım. Test makinem Macbook Pro (2011, 8 GB, Dört Çekirdekli), bu konularda o kadar da değil. 20 satır metin her şuna benzer bir boşluk ile ayrılmış üç sütun ile dosya ile test ediyorum: "foo.bar 127.0.0.1 ev.foo.". bar

Sonuçlar:

$ /usr/bin/time cat test_lines_double | ./split.py
       15.61 real         0.01 user         0.38 sys
Python: Saw 20000000 lines in 15 seconds.   Crunch Speed: 1333333
$ /usr/bin/time cat test_lines_double | ./split1
       23.50 real         0.01 user         0.46 sys
C     : Saw 20000000 lines in 23 seconds.  Crunch speed: 869565
$ /usr/bin/time cat test_lines_double | ./split2
       44.69 real         0.02 user         0.62 sys
C     : Saw 20000000 lines in 45 seconds.  Crunch speed: 444444

Neyi yanlış yapıyorum? Orada daha iyi bir yol için dize bölme C bu güvenmek yok harici kütüphaneleri (örneğin, boost) destekler birleştirme dizileri sınırlayıcıları (gibi python'un split), iş parçacığı güvenli (hayır strtok), ve kimin performansı en az on par ile python?

1 / Kısmi Çözüm Düzenle?:

C gibi daha adil bir karşılaştırma yapma kukla listesini sıfırlamak ve eklenecek python her zaman çalıştım. Bu hala C kodu ne yaptığını tam olarak değil, ama biraz daha yakın. Temel olarak, bu döngü artık

for line in sys.stdin:
    dummy = []
    dummy  = line.split()
    count  = 1

Python performansını şimdi split1 C uygulanmasına devam edilir.

/usr/bin/time cat test_lines_double | ./split5.py
       22.61 real         0.01 user         0.40 sys
Python: Saw 20000000 lines in 22 seconds.   Crunch Speed: 909090

Ben hala, eğer Python string işleme Matt Joiner önerilen) için optimize edilmiş olsa bile, bu C uygulamaları daha hızlı olmaz şaşırttı. Eğer biri daha iyi bir yol C kullanılarak bunun nasıl yapılacağı hakkında bir fikir varsa , lütfen kodunuzu paylaşın. (Sanırım benim bir sonraki adım çalışıyor uygulamak için bu saf C, rağmen gitmiyorum ticaret kapalı programcı verimlilik için yeniden uygulamak benim genel proje C, yani bu sadece bir deney için dize bölme hızı.)

Yardımlarınız için herkese teşekkürler.

Son Düzenleme/Çözüm:

Alf'in kabul cevaba bakınız lütfen. Kesinlikle referans ve STL dizeler dizeleri ile python fırsatlar sık sık kopyalanmış olduğundan, performans vanilya python uygulamaları ile daha iyi olur. Karşılaştırma için, derlenmiş ve koştu benim verilerde Alf kod ve burada performans aynı makine gibi tüm diğer çalışan, aslında aynı saf python uygulaması (gerçi daha hızlı python uygulaması bu sıfırlar/ekler listesi gösterildiği gibi, yukarıdaki düzenleme):

$ /usr/bin/time cat test_lines_double | ./split6
       15.09 real         0.01 user         0.45 sys
C     : Saw 20000000 lines in 15 seconds.  Crunch speed: 1333333

Sadece küçük kalan benim sancı kodu gerekli miktarı bu durumda gerçekleştirmek için C almak ile ilgili.

Bu sorun ve dünkü stdın çizgi sorunu okuma (yukarıda bağlantılı) buradan dersinden hep kıyaslama gerektiğini dilde "göreceli" " performans. varsayılan hakkında naif varsayımlar yapmak yerine Eğitim takdir ediyorum.

Tekrar önerileriniz için herkese teşekkürler!

CEVAP
21 ŞUBAT 2012, Salı


Tahmin edin bakalım, Python dizeleri referans sayılır değişmez dizeleri, böylece hiçbir dizeleri kopyalanan etrafında Python kodu ise C std::string bir değişken değeri, türü ve kopyalanan en küçük fırsat.

Eğer amaç hızlı bölme ise tek bir anlamı var, sürekli alt işlemleri içinatıftaPython özgün dize parçaları için, (Java ve C#&üssün;) gibi.

std::string sınıf güzel bir tarafı da var, ama C: Evet, öylestandartdizeleri ve verimlilik ana bir göz değil nerede etrafında portably güvenli bir şekilde iletmek için kullanılabilir. Ama bu kadar konuşma yeter. Kod -- ve benim makinede bu Python dize işleme C (o) bir alt kümesi olan C uygulanan bu yana tabii ki Python hızlıdır,:

#include <iostream>                                                              
#include <string>
#include <sstream>
#include <time.h>
#include <vector>

using namespace std;

class StringRef
{
private:
    char const*     begin_;
    int             size_;

public:
    int size() const { return size_; }
    char const* begin() const { return begin_; }
    char const* end() const { return begin_   size_; }

    StringRef( char const* const begin, int const size )
        : begin_( begin )
        , size_( size )
    {}
};

vector<StringRef> split3( string const& str, char delimiter = ' ' )
{
    vector<StringRef>   result;

    enum State { inSpace, inToken };

    State state = inSpace;
    char const*     pTokenBegin = 0;    // Init to satisfy compiler.
    for( auto it = str.begin(); it != str.end();   it )
    {
        State const newState = (*it == delimiter? inSpace : inToken);
        if( newState != state )
        {
            switch( newState )
            {
            case inSpace:
                result.push_back( StringRef( pTokenBegin, &*it - pTokenBegin ) );
                break;
            case inToken:
                pTokenBegin = &*it;
            }
        }
        state = newState;
    }
    if( state == inToken )
    {
        result.push_back( StringRef( pTokenBegin, &*str.end() - pTokenBegin ) );
    }
    return result;
}

int main() {
    string input_line;
    vector<string> spline;
    long count = 0;
    int sec, lps;
    time_t start = time(NULL);

    cin.sync_with_stdio(false); //disable synchronous IO

    while(cin) {
        getline(cin, input_line);
        //spline.clear(); //empty the vector for the next line to parse

        //I'm trying one of the two implementations, per compilation, obviously:
//        split1(spline, input_line);  
        //split2(spline, input_line);

        vector<StringRef> const v = split3( input_line );
        count  ;
    };

    count--; //subtract for final over-read
    sec = (int) time(NULL) - start;
    cerr << "C     : Saw " << count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

//compiled with: g   -Wall -O3 -o split1 split_1.cpp -std=c  0x

Yasal Uyarı: herhangi bir hata yoktur umarım. İşlevselliği test etmedim, ama sadece hız kontrol etti. Ama eğer önemli ölçüde hızını etkilemeyecek bir hata ya da iki, düzeltme varsa bile bence.

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

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • InfoPuppet

    InfoPuppet

    15 Kasım 2011
  • Richard Laxa

    Richard Laxa

    30 AĞUSTOS 2012
  • stewmurray47

    stewmurray47

    1 Kasım 2006