2013-03-01 16 views
8

Ta procedura nazywa się milion razy, aby tworzyć duże pliki CSV pełne liczb. Czy jest na to skuteczniejszy sposób?Podwójny ciąg bez notacji naukowej lub zer końcowych, efektywnie

static std::string dbl2str(double d) 
    { 
     std::stringstream ss; 
     ss << std::fixed << std::setprecision(10) << d;    //convert double to string w fixed notation, hi precision 
     std::string s = ss.str();         //output to std::string 
     s.erase(s.find_last_not_of('0') + 1, std::string::npos);  //remove trailing 000s (123.1200 => 123.12, 123.000 => 123.) 
     return (s[s.size()-1] == '.') ? s.substr(0, s.size()-1) : s; //remove dangling decimal (123. => 123) 
    } 
+1

Tytuł wydaje się błędny, powinien być podwójny do napisu? – hyde

+0

oops - tytuł jest cofany. . . oczywiście jest podwójny na ciąg – tpascale

+0

Możliwy duplikat [Formatowanie n cyfr znaczących w C++ bez zapisu naukowego] (http://stackoverflow.com/questions/17211122/formatting-n-significant-digits-in-c-without-scientific- notacja) – mirams

Odpowiedz

7

Przed rozpoczęciem należy sprawdzić, czy w tej funkcji zużywa się znaczny czas. Wykonaj to, mierząc, za pomocą profilera lub w inny sposób. Wiedząc, że nazywasz to milion razy, wszystko jest bardzo dobre, ale jeśli się okaże, że twój program nadal spędza tylko 1% czasu w tej funkcji, to nic, co tu robisz, nie może poprawić wydajności Twojego programu o więcej niż 1%. Gdyby tak było, odpowiedź na twoje pytanie brzmiałaby: "dla twoich celów nie, ta funkcja nie może być znacznie bardziej wydajna i marnujesz swój czas, jeśli spróbujesz".

Po pierwsze, należy unikać s.substr(0, s.size()-1). To kopiuje większość ciągu znaków i powoduje, że twoja funkcja nie jest odpowiednia dla NRVO, więc myślę, że generalnie dostaniesz kopię po powrocie. Więc pierwsza zmiana Chciałbym zrobić to wymienić ostatnią linię z:

if(s[s.size()-1] == '.') { 
    s.erase(s.end()-1); 
} 
return s; 

Ale jeśli wydajność jest poważnym problemem, a oto jak ja to zrobię. Nie obiecuję, że jest to najszybsze z możliwych, ale pozwala uniknąć problemów związanych z niepotrzebnymi alokacjami i kopiowaniem. Każde podejście obejmujące stringstream będzie wymagało skopiowania z strumienia string do wyniku, więc potrzebujemy bardziej niskiego poziomu operacji, snprintf.

static std::string dbl2str(double d) 
{ 
    size_t len = std::snprintf(0, 0, "%.10f", d); 
    std::string s(len+1, 0); 
    // technically non-portable, see below 
    std::snprintf(&s[0], len+1, "%.10f", d); 
    // remove nul terminator 
    s.pop_back(); 
    // remove trailing zeros 
    s.erase(s.find_last_not_of('0') + 1, std::string::npos); 
    // remove trailing point 
    if(s.back() == '.') { 
     s.pop_back(); 
    } 
    return s; 
} 

Drugie wywołanie snprintf zakłada std::string wykorzystuje ciągły przechowywania. Gwarantuje to C++ 11. Nie jest to gwarantowane w C++ 03, ale jest prawdziwe dla wszystkich aktywnie utrzymywanych implementacji std::string znanych komitetowi C++. Jeśli wydajność jest naprawdę ważna, to myślę, że rozsądne jest zrobienie tego nieprzenośnego założenia, ponieważ pisanie bezpośrednio do ciągu zapisuje kopiowanie w łańcuchu później.

s.pop_back() jest C++ 11 sposób powiedzenia s.erase(s.end()-1) i s.back() jest s[s.size()-1]

Dla innego możliwym poprawy, można pozbyć się pierwszego zaproszenia do snprintf i zamiast wielkość swojej s do pewnej wartości, takich jak std::numeric_limits<double>::max_exponent10 + 14 (w zasadzie długość, której potrzebuje -DBL_MAX). Problem polega na tym, że przydziela i zeruje znacznie więcej pamięci, niż jest to zwykle potrzebne (322 bajty dla podwójnego IEEE). Mam intuicję, że będzie to wolniejsze od pierwszego połączenia z snprintf, nie wspominając o marnotrawieniu pamięci w przypadku, gdy wartość zwracana przez ciąg jest utrzymywana przez jakiś czas przez rozmówcę. Ale zawsze możesz to przetestować.

Alternatywnie, std::max((int)std::log10(d), 0) + 14 oblicza rozsądnie ciasne górne ograniczenie wymaganego rozmiaru i może być szybsze niż snprintf może je dokładnie obliczyć.

Wreszcie może się okazać, że można poprawić wydajność, zmieniając interfejs funkcji. Na przykład, zamiast wrócić nowy ciąg można może dołączyć do łańcucha przechodzi się przez dzwoniącego:

void append_dbl2str(std::string &s, double d) { 
    size_t len = std::snprintf(0, 0, "%.10f", d); 
    size_t oldsize = s.size(); 
    s.resize(oldsize + len + 1); 
    // technically non-portable 
    std::snprintf(&s[oldsize], len+1, "%.10f", d); 
    // remove nul terminator 
    s.pop_back(); 
    // remove trailing zeros 
    s.erase(s.find_last_not_of('0') + 1, std::string::npos); 
    // remove trailing point 
    if(s.back() == '.') { 
     s.pop_back(); 
    } 
} 

Następnie dzwoniący może reserve() dużo miejsca, zadzwoń czynność kilka razy (przypuszczalnie z drugiej łańcuch dołącza się pomiędzy) i zapisz wynikowy blok danych do pliku za jednym razem, bez przydzielania pamięci innej niż reserve. "Obfitość" nie musi być całym plikiem, może to być jedna linia lub "akapit" na raz, ale wszystko, co unika alokacji pamięci o milionach, może potencjalnie zwiększyć wydajność.

+0

dzięki za to bardzo szczegółowe wyjaśnienie – tpascale

1
  • zastosowanie snprintf i tablicę char zamiast stringstream i string
  • przechodzą wskaźnik bufora char do dbl2str w której drukowany (w celu uniknięcia konstruktora kopii string nazwie wracając) . Zamontować łańcuch zostanie wydrukowany w buforze znaków (lub przekonwertować buforu char kiedy nazywa się ciągiem lub dodać je do istniejącego łańcucha)
  • zadeklarować funkcję inline w pliku nagłówkowym

    #include <cstdio> 
    inline void dbl2str(char *buffer, int bufsize, double d) 
    { 
        /** the caller must make sure that there is enough memory allocated for buffer */ 
        int len = snprintf(buffer, bufsize, "%lf", d); 
    
        /* len is the number of characters put into the buffer excluding the trailing \0 
        so buffer[len] is the \0 and buffer[len-1] is the last 'visible' character */ 
    
        while (len >= 1 && buffer[len-1] == '0') 
        --len; 
    
        /* terminate the string where the last '0' character was or overwrite the existing 
        0 if there was no '0' */ 
        buffer[len] = 0; 
    
        /* check for a trailing decimal point */ 
        if (len >= 1 && buffer[len-1] == '.') 
        buffer[len-1] = 0; 
    } 
    
+0

Słowo kluczowe * inline * nie wpływa bezpośrednio na optymalizację przez "inlining", jest instrukcją dla linkera, że ​​ten symbol może pojawić się wiele razy w łączeniu i to nie jest błąd. Ta funkcja już jest * statyczna *. – hyde

4

Wydajne pod względem szybkości lub zwięzłości?

char buf[64]; 
sprintf(buf, "%-.*G", 16, 1.0); 
cout << buf << endl; 

Wyświetla "1". Formatuje do znaczących 16 cyfr, bez końcowych zer, zanim przejdzie do notacji naukowej.

+0

The - nie jest ściśle konieczne (zostało to uzasadnione) –

Powiązane problemy