Schnelle möglichkeit von float to string

Metatron

Grünschnabel
Hallo,

ich habe ein Vektor der aus Float Arrays besteht:
vector<float*>

diesen möchte ich in eine csv Datei pressen.
Ich habe dafür auch einen lauffähigen Code, ist ja kein Hexenwerk.
Dieser Code ist aber zu langsam. Die meiste Zeit benötigt std::to_string(). Daher wäre meine Frage gibt es einen schnelleren Weg?
Ich benötige irgendwas um ein float möglichst schnell in ein String zu verwandeln.
 

ComFreek

Mod | @comfreek
Moderator
Ich hab mal ein bisschen im Internet nach "c++ float to string fast" herumgesucht. Mehrmals wurden folgende Bibliotheken vorgeschlagen:
Wenn die Konvertierung tatsächlich das Bottleneck ist, sind diese Bibliotheken möglicherweise schneller als die Standardbibliothek.
 

Jennesta

Erfahrenes Mitglied
Hallo,
du könntest auch einen Geschwindigkeitsvorteil bekommen, wenn du auf die C Funktionen setzt.
fprintf oder die Buffer Variante snprintf sollten auf jeden Fall schneller als die C++ Varianten sein, z.B. weil du keinen std::string benötigst. Je nachdem wie deine csv Datei aussieht kannst du dann auch ganze Zeilen direkt in dem Buffer definieren und ausgeben, "[..]%lf, %lf,[...]".

Um ein Benchmarking wirst du da wohl nicht herum kommen, um die beste Variante zu finden. Falls du dazu ein Ergebnis hast, würde es mich sehr interessieren.

VG Jennesta
 

Technipion

Erfahrenes Mitglied
ich habe ein Vektor der aus Float Arrays besteht:
vector<float*>

diesen möchte ich in eine csv Datei pressen.
Dieser Code ist aber zu langsam. Die meiste Zeit benötigt std::to_string().
Krass! Ich hätte ehrlichgesagt erwartet, dass die Dateioperationen bei weitem am längsten dauern.

Ist es vielleicht möglich, dass du uns mal die relevanten Codeauszüge postest? Ich will dir nichts unterstellen, aber vielleicht enthält die Konvertierung irgendwelche Bottlenecks?

Gruß Technipion
 

Metatron

Grünschnabel
Hallo,

der Code sieht wie folgt aus. sizex = 638, sizey = 958 und dim = 8. Ich habe mit absicht das schreiben der Datei ausgelagert um die Zeiten besser validieren zu können. Wenn ich direkt in die Datei schreibe ist die Zeit aber identisch. Die innerschleife benötigt ca 8 Sekunden, also insgesamt etwas mehr als 1 Minute. Bei 300 Dateien ist das schon nervig :D. Wenn ich zum Beispiel in der schleife statt sdt::string(), die floats einfach aufsummiere, bin ich bei einigen Millisekunden. (Das Berechnen der out dauert < 100ms).

Code:
   std::vector<float*> out = calculation(bla);

    string line =  std::to_string(sizex) + "\n" + std::to_string(sizey ) + "\n" + std::to_string(dim) + "\n";

    for (int d = 0; d < dim; d++)
    {     
        for (int x = 0; x < sizex * sizey; x++)
        {
            line += std::string(out[d][x]);
            line += ",";
        }
        line += "\n";
    }
    fstream data;
    data.open(savepfad + name + ".csv", ios::out);
    data<<line;
    data.close();


Die bibs kannte ich nocht nicht, hatte vorher ja auch schon gegoggelt. Schau ich mir mal an.
 

ComFreek

Mod | @comfreek
Moderator
Wie genau hast du eigentlich profiled?
Ich kenne mich mit C++ nicht so genau aus. Führt line += std::string(out[d][x]); zu einem Kopieren?

Falls alle Optimierungen nichts bringen, könntest du die äußere Schleife ja auch parallelisieren. Das könnte sich für deine Anzahl von Schleifeniterationen der inneren Schleife schon lohnen, würde ich jetzt vermuten.
 

Technipion

Erfahrenes Mitglied
Okay, ich geb's zu. Mich hat das jetzt interessiert.

Hier ist dein ursprünglicher Code etwas aufgehübscht, damit er direkt läuft:
C++:
#include <iostream>
#include <vector>       // for std::vector
#include <fstream>      // for std::fstream
#include <string>       // for std::string, std::to_string
#include <chrono>       // for std::chrono::*


auto fake_calculation(size_t x, size_t y, size_t d) -> std::vector<float*>;


int main() {
    constexpr size_t sizex = 638;
    constexpr size_t sizey = 958;
    constexpr size_t dim   = 8;

    std::vector<float*> out = fake_calculation(sizex, sizey, dim);

    std::string line = std::to_string(sizex) + "\n" + std::to_string(sizey) + "\n" + std::to_string(dim) + "\n";

    // start performance measuring:
    auto time_start = std::chrono::high_resolution_clock::now();

    for (int d = 0; d < dim; d++)
    {
        for (int x = 0; x < sizex * sizey; x++)
        {
            line += std::to_string(out[d][x]);
            line += ",";
        }
        line += "\n";
    }

    // end performance measuring:
    auto time_end = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double, std::milli> elapsed = time_end - time_start;
    std::cout << "conversion took " << elapsed.count() << "ms to finish." << std::endl;

    // dumping to file:
    std::fstream data;
    data.open("perf_test.csv", std::ios::out);
    data << line;
    data.close();
}


auto fake_calculation(size_t p_x, size_t p_y, size_t p_d)
      -> std::vector<float*> {

    std::vector<float*> fake_result;

    for (size_t d = 0; d < p_d; d++) {
        float *x_array = new float [p_x * p_y];

        for (size_t x = 0; x < p_x * p_y; x++) {
            x_array[x] = 1234.5 * (d+2) * x / 678.9; // something
        }

        fake_result.push_back(x_array);
    }

    return fake_result;
}

Ich habe dann etwas damit herumgespielt um ein Gefühl dafür zu bekommen, was bei der Performancesteigerung hilft und was nicht.
Bin dann bei folgendem Code gelandet, bei dem ich stringstreams verwende und mit std::async parallelisiert habe:
C++:
#include <iostream>
#include <vector>       // for std::vector
#include <fstream>      // for std::fstream
#include <string>       // for std::string, std::to_string
#include <chrono>       // for std::chrono::*
#include <sstream>      // for std::stringstream
#include <future>       // for std::async
#include <algorithm>    // for for_each


auto fake_calculation(size_t x, size_t y, size_t d) -> std::vector<float*>;


int main() {
    constexpr size_t sizex = 638;
    constexpr size_t sizey = 958;
    constexpr size_t dim   = 8;

    std::vector<float*> out = fake_calculation(sizex, sizey, dim);

    std::string line = std::to_string(sizex) + "\n" + std::to_string(sizey) + "\n" + std::to_string(dim) + "\n";

    std::vector<std::future<std::string> > lines;

    // start performance measuring:
    auto time_start = std::chrono::high_resolution_clock::now();

    for (int d = 0; d < dim; d++)
    { // SPAWN
        auto future_line = std::async(std::launch::async,
                                      [&](const size_t local_d) {
            std::stringstream local_ss;

            /*****************************/
            /*  SET NUMBER FORMAT HERE   */
            local_ss.precision(6);
            local_ss << std::fixed;
            /*****************************/

            for (int x = 0; x < sizex * sizey; x++)
            {
                local_ss << out[local_d][x] << ",";
            }
            local_ss << "\n";

            return local_ss.str();
        }, d);

        lines.push_back(std::move(future_line));
    }
    for (auto& fl : lines)
    { // COLLECT
        line += fl.get();
    }

    // end performance measuring:
    auto time_end = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double, std::milli> elapsed = time_end - time_start;
    std::cout << "conversion took " << elapsed.count() << "ms to finish." << std::endl;

    // dumping to file:
    std::fstream data;
    data.open("perf_test.csv", std::ios::out);
    data << line;
    data.close();

    for (auto pf : out) { // free pointers
        delete pf;
    }
}


auto fake_calculation(size_t p_x, size_t p_y, size_t p_d)
      -> std::vector<float*> {

    std::vector<float*> fake_result;

    for (size_t d = 0; d < p_d; d++) {
        float *x_array = new float [p_x * p_y];

        for (size_t x = 0; x < p_x * p_y; x++) {
            x_array[x] = 1234.5 * (d+2) * x / 678.9; // something
        }

        fake_result.push_back(x_array);
    }

    return fake_result;
}

Ergebnis:
Der ursprüngliche Code braucht auf meiner Maschine 4014 ms für die Konvertierung. Der neue Code (mit StringStreams und async) braucht im Modus std::launch::deferred insgesamt 4832 ms dafür. Also eher meh. Dafür läuft die Konvertierung mit std::launch::async in gerade mal 1231 ms ab.
Ich bin natürlich für Vorschläge und Hinweise offen!

Gruß Technipion
 

Metatron

Grünschnabel
was ist, wenn wir das ganze nicht über die string classe machen, sonder über ein char* mit fester länge, bringt das was? Die frage wäre dann, um wie viele Zeichen es sich bei dem string handelt.
 

Technipion

Erfahrenes Mitglied
was ist, wenn wir das ganze nicht über die string classe machen, sonder über ein char* mit fester länge, bringt das was?
Damit möchtest du unnötiges Kopieren vermeiden, nehme ich mal an. Habe mich das auch schon gefragt. Man müsste halt mit reinen C-Funktionen arbeiten, und auf den Komfort von C++ verzichten.

Ich habe deshalb mal gegoogelt, wie man für einen StringStream Speicher reservieren kann. Wie sich herausstellt, gar nicht. Danke für nichts C++. Man kann allerdings tricksen, guckst du hier:
https://stackoverflow.com/a/58989107
Habe das schnell in meinen Code eingebaut. Insgesamt reserviere ich Platz für (sizex * sizey) * 13 + 1 Zeichen, weil jede Zahl jetzt im scientific Modus genau 12 Zeichen braucht zuzüglich eines Kommas. Und am Ende ein newline.

C++:
#include <iostream>
#include <vector>       // for std::vector
#include <fstream>      // for std::fstream
#include <string>       // for std::string, std::to_string
#include <chrono>       // for std::chrono::*
#include <sstream>      // for std::stringstream
#include <future>       // for std::async


auto fake_calculation(size_t x, size_t y, size_t d) -> std::vector<float*>;


int main() {
    constexpr size_t sizex = 638;
    constexpr size_t sizey = 958;
    constexpr size_t dim   = 8;

    std::vector<float*> out = fake_calculation(sizex, sizey, dim);

    std::string line = std::to_string(sizex) + "\n" + std::to_string(sizey) + "\n" + std::to_string(dim) + "\n";

    std::vector<std::future<std::string> > lines;

    // start performance measuring:
    auto time_start = std::chrono::high_resolution_clock::now();

    for (int d = 0; d < dim; d++)
    { // SPAWN
        auto future_line = std::async(std::launch::async,
                                      [&](const size_t local_d) {
            // see https://stackoverflow.com/a/58989107
            size_t to_reserve = (sizex * sizey) * 13 + 1;
            std::string dummy(to_reserve, '\0');
            std::stringstream local_ss(dummy);
            dummy.clear();
            dummy.shrink_to_fit();

            /*****************************/
            /*  SET NUMBER FORMAT HERE   */
            local_ss.precision(6);
            local_ss << std::scientific;
            /*****************************/

            for (int x = 0; x < sizex * sizey; x++)
            {
                local_ss << out[local_d][x] << ",";
            }
            local_ss << "\n";

            return local_ss.str();
        }, d);

        lines.push_back(std::move(future_line));
    }
    for (auto& fl : lines)
    { // COLLECT
        line += fl.get();
    }

    // end performance measuring:
    auto time_end = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double, std::milli> elapsed = time_end - time_start;
    std::cout << "conversion took " << elapsed.count() << "ms to finish." << std::endl;

    // dumping to file:
    std::fstream data;
    data.open("perf_test.csv", std::ios::out);
    data << line;
    data.close();

    for (auto pf : out) { // free pointers
        delete pf;
    }
}


auto fake_calculation(size_t p_x, size_t p_y, size_t p_d)
      -> std::vector<float*> {

    std::vector<float*> fake_result;

    for (size_t d = 0; d < p_d; d++) {
        float *x_array = new float [p_x * p_y];

        for (size_t x = 0; x < p_x * p_y; x++) {
            x_array[x] = 1234.5 * (d+2) * x / 678.9; // something
        }

        fake_result.push_back(x_array);
    }

    return fake_result;
}

Damit braucht mein System nur noch 972ms für die Konvertierung. Es bringt also einen spürbaren Geschwindigkeitsvorteil, wenn auch nur einen kleinen. Rechnerisch ergibt sich damit, dass mein Prozessor (alter 6-Kerner von AMD mit 3,2 GHz) ziemlich genau 5 Millionen Zahlenkonvertierungen (float -> string) pro Sekunde schafft. Also braucht er im Schnitt ~600 Zyklen pro Zahl. Klingt doch eigentlich gar nicht sooo schlecht?

Gruß Technipion
 

Neue Beiträge