atof() 10 mal langsamer als eigene implementierung - warum?



  • Ich hab gerade mal versucht atof() nachzuprogrammieren erstmal ohne erwartungen.
    Dann ist mir beim test jedoch aufgefallen, dass mein code 10 mal schneller ist als der von atof().
    Jetzt frage ich mich ob das normal ist und warum die funktion atof so langsam ist
    (habe ich etwas vergessen oder?)

    hier der geschwindigkeitstest:

    speed test: atof() vs. my own to_f32()
    ----------------------------------------------
    
    to_f32: finished after 1343 milliseconds
    atof:   finished after 10270 milliseconds
    

    und hier mein code:

    // convert char* to core::u32
    	inline core::u32 to_u32(const char* in, const char **out=nullptr){
    		core::u32 ret = 0; // return value
    		while(*in >= '0' && *in <= '9'){
    			ret = (ret*10) + *in - int('0'); // add
    			++in;  // next
    		}
    		if(out != nullptr) *out = in;
    		return ret;
    	}
    
    	// convert std::string to core::f32
    	inline core::f32 to_f32(const char* in, const char **out=nullptr){
    		bool inv = false;
    		core::f32 ret = 0.f;
    		const char* t;
    
    		// check if the number is negativ or positiv
    		if(*in == '-'){
    			inv = true;
    			++in;
    		}
    
    		ret = (float) to_u32(in,&in);
    
    		if(*in == '.'){
    			++in;
    
    			core::f32 f = (core::f32) to_u32(in, &t);
    			f *= to_f32_table[t-in];
    
    			ret += f;
    			in = t;
    		}
    
    		if(*in == 'e' || *in == 'E'){
    			++in;
    
    			// check if the number is negativ or positiv
    			bool e_inv = false;
    			if(*in == '-'){
    				inv = true;
    				++in;
    			}else if(*in == '+') ++in;
    
    			core::f32 exp = (core::f32) to_u32(in, &in);
    			if(e_inv == true) exp *= -1.0f;
    
    			ret *= (core::f32) pow(10.0f,exp);
    		}
    
    		if(out != nullptr) *out = in;
    		return ret;
    	}
    
    #include <iostream>
    using namespace std;
    
    int main(){
    	cout << "speed test: atof() vs. my own to_f32()\n----------------------------------------------\n" << endl;
    
    	core::timer t;
    	t.start();
    
    	for(__int64 i=0; i<10000000; ++i){
    		float f = core::string::to_f32("324.765e+005");
    	}
    
    	t.stop();
    	cout << "to_f32: finished after " << t.get_time() << " milliseconds" << endl;
    
    	t.reset();
    	t.start();
    
    	for(__int64 i=0; i<10000000; ++i){
    		float f = atof("324.765e+005");
    	}
    
    	t.stop();
    	cout << "atof:   finished after " << t.get_time() << " milliseconds" << endl;
    
    	getchar();
    	return 0;
    }
    


  • wie sieht denn "to_f32_table" aus?



  • Du hast auch eine sehr spezielle Variante von atof nachprogrammiert. Das "echte" atof macht noch deutlich mehr checks:
    http://en.cppreference.com/w/cpp/string/byte/atof
    []prüfen, ob string mit "0x" oder "0X" beginnt und den string binär interpretieren
    [
    ]auf Infinity testen
    [*]auf NaN testen
    Ob sich das sonst gleich verhält, weiß ich nicht.



  • 1. atof erzeugt double, nicht float.
    2. atof behandelt unendlich und NaN.
    3. atof überspringt führende Leerzeichen.
    4. atof kann hexadezimale Darstellung parsen.
    5. Locales.



  • Optimir0r schrieb:

    wie sieht denn "to_f32_table" aus?

    Da werden wohl alle 10^-x drinstehen, wobei x der Anzahl der geparsten Nachkommastellen entspricht.



  • Zum Thema:
    Ich wage zu bezweifeln, dass deine Version 1,798*10^308 in ausgeschriebener Weise parsen kann, und das auch noch so genau wie möglich umrechnet, unter Berücksichtigung von Spracheigenheiten.

    Mal das Genauigkeitsbeispiel:

    std::cout << std::abs(to_f32("324.765e+005")-atof("324.765e+005"))/atof("324.765e+005")*100 << '\n';
    

    Gibt bei mir einen relativen Fehler von 0.212%, unter der Prämisse, dass mein atof richtig rechnet 😃



  • gamer8o4 schrieb:

    Dann ist mir beim test jedoch aufgefallen, dass mein code 10 mal schneller ist als der von atof().
    Jetzt frage ich mich ob das normal ist und warum die funktion atof so langsam ist
    (habe ich etwas vergessen oder?)

    Das ist normal, aber ich hätte nur auf den Faktor 2 bis 5 geschätzt. Denn Du schreibst keine Standardbibliothek für alle 10Mrd Menschen, sondern eine für Dich und ein paartausend Freunde. Schlankere Schnittstellen, bescheidenere Wünsche und schon flutscht es.

    gamer8o4 schrieb:

    if(out != nullptr) *out = in;
    

    Sind wir wieder albern aufgelegt oder haben wir 13 Strafzyklen in der Tasche?



  • @gamer8o4
    Schreib eine Implementierung die genau das selbe tut wie atof , und dann vergleiche nochmal.

    Davon abgesehen: ja, mit selber Parsen kann man einiges rausholen -- meist aber nur deswegen weil man die aktuelle Locale nicht ermitteln muss. Oder eben weil man, wie Du, haufenweise Dinge weglässt.

    Ansonsten stell deine Funktion mal auf double um, und vergleich sie dann mit _strtod_l (MSVC -- andere Compiler werden was ähnliches haben).
    (Und natürlich ohne die Locale für _strtod_l bei jedem Schleifendurchlauf neu über _get_current_locale zu holen.)



  • volkard schrieb:

    Sind wir wieder albern aufgelegt oder haben wir 13 Strafzyklen in der Tasche?

    Dummerweise scheinen die Strafzyklen in Benchmarks vermutlich nicht auf. Weil er dort den TLB und die Branch-Prediction ganz für sich alleine hat, und daher das "if" kaum weh tut.
    In realen Programmen werden TLB + Branch-Prediction zwar auch helfen, aber weh tut so ein unnötiges "if" immer noch. Vor allem beim Lesen 🙂
    (Und erst recht wenn der Schreiberling meint schnellen Code geschrieben zu haben)



  • wie sieht denn "to_f32_table" aus?

    static const float to_f32_table[17] = {
    		0.f,
    		0.1f,
    		0.01f,
    		0.001f,
    		0.0001f,
    		0.00001f,
    		0.000001f,
    		0.0000001f,
    		0.00000001f,
    		0.000000001f,
    		0.0000000001f,
    		0.00000000001f,
    		0.000000000001f,
    		0.0000000000001f,
    		0.00000000000001f,
    		0.000000000000001f,
    		0.0000000000000001f
    	};
    

    Du hast auch eine sehr spezielle Variante von atof nachprogrammiert. Das "echte" atof macht noch deutlich mehr checks

    das erklärt es naürlich 😃 aber naja, für die meißten meiner anwendungszwecke reicht das so

    Sind wir wieder albern aufgelegt oder haben wir 13 Strafzyklen in der Tasche?

    wenn ich ehrlich bin hab ich mir dabei recht wenig gedacht, ich meine eine 'if'-schleife.... aber gut, ich ändere das mal und versuche noch ein bisschen performance aus der funktion zu bekommen. 😉

    Schreib eine Implementierung die genau das selbe tut wie atof, und dann vergleiche nochmal.

    ehrlich gesagt, dazu fehlern mir schon teilweise die mathematischen kenntnisse. 🙄

    (Und erst recht wenn der Schreiberling meint schnellen Code geschrieben zu haben)

    nein, das wollte ich damit nicht erreichen, eher dass der parameter nicht angegeben werden muss. Aber warscheinlich löst man das besser, indem ich die funktion überlade!?






Anmelden zum Antworten