kleine Funktion vektorisieren?



  • bisher haben mir hier schon einigen Leute sehr gut geholfen - Danke an camper,SeppJ,Schlangenmensch und die anderen

    Wie kann man die calculate-Methode vektorisieren? (Es sind ein paar von dieser Art aber mir würde diese als "Einstieg" in die Thematik helfen)

    #include <chrono>
    #include <array>
    #include <iostream>
    
    typedef double realtype_t;
    
    struct point3d_t
    {
    	point3d_t(const realtype_t& x, const realtype_t& y, const realtype_t& z) :_data{ x, y, z }{}
    	std::array<realtype_t, 3> _data;
    	realtype_t x() const { return _data[0]; }
    	realtype_t y() const { return _data[1]; }
    	realtype_t z() const { return _data[2]; }
    };
    
    struct kinematic
    {
    	kinematic(const point3d_t& a, const point3d_t& b, const point3d_t& c, const point3d_t& d, const point3d_t& e):
    		_a(a),
    		_b(b),
    		_c(c),
    		_d(d),
    		_e(e)
    	{
    	}
    
    	point3d_t _a;
    	point3d_t _b;
    	point3d_t _c;
    	point3d_t _d;
    	point3d_t _e;
    
    	point3d_t calculate(realtype_t f, realtype_t g, realtype_t h) const
    	{
    		realtype_t sin_h = std::sin(h);
    		realtype_t cos_h = std::cos(h);
    		return point3d_t
    		(
    			_a.x() + _b.x()     - (-_e.x() + _c.y() + _d.y()) * sin_h + (_c.x() + _d.x()) * cos_h,
    			_a.y() + _b.y() + f + (-_e.x() + _c.y() + _d.y()) * cos_h + (_c.x() + _d.x()) * sin_h,
    			_a.z() + _b.z() + g + _c.z() + _d.z()
    		);
    	}
    };
    
    int main(int argc, char** argv)
    {
    	constexpr size_t WORK_COUNT = 70000000;
    	constexpr size_t AVERAGE_COUNT = 10;
    
    	std::cout << "work_count: " << WORK_COUNT << std::endl;
    	std::cout << "average_count: " << AVERAGE_COUNT << std::endl;
    
    	//irgendwelche Werte - nur eben nicht optimierbar
    	point3d_t a(1 / argc, 2 / argc, 3 / argc);
    	point3d_t b(11 / argc, 22 / argc, 33 / argc);
    	point3d_t c(111 / argc, 222 / argc, 333 / argc);
    	point3d_t d(1111 / argc, 2222 / argc, 3333 / argc);
    	point3d_t e(11111 / argc, 2222 / argc, 3333 / argc);
    
    	const auto k = kinematic(a,b,c,d,e);
    
    	double anti_optimizer_value = 0.0;
    
    	double complete_duration = 0.0;
    
    	for (size_t t = 0; t < AVERAGE_COUNT; ++t)
    	{
    		auto start = std::chrono::high_resolution_clock::now();
    
    		for (size_t i = 0; i < WORK_COUNT; ++i)
    		{
    			  //irgendwelche Werte - nur eben nicht optimierbar
    				realtype_t f = i+argc;
    				realtype_t g = i / f;
    				realtype_t h = i / g;
    				const auto r = k.calculate(f, g, h);
    
    				anti_optimizer_value += r.x() + r.y() + r.z();
    		}
    
    		auto end = std::chrono::high_resolution_clock::now();
    		auto diff = end - start;
    
    		auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
    		complete_duration += milliseconds.count();
    
    		std::cout << "duration: " << milliseconds.count() << " ms" << std::endl;
    	}
    
    	double average = complete_duration / AVERAGE_COUNT;
    
    	std::cout << "average: " << average << " ms (dummy: " << anti_optimizer_value << ")" << std::endl;
    
    	return 0;
    }
    

    "Legende"

    3d Punkte (a|b|c|e)[3]={X,Y,Z} = als floats (oder doubles)
    -> runtime Konstant (veraendern sich nicht beim Aufruf von calculate)
    -> kommen aus Konfiguration
    -> bisher Members von kinematic

    f, g, h = float (oder double) Wert
    -> runtime Variant (aendern sich bei jedem Aufruf von calculate)

    ich bin an kein Speicherlayout bei den 3d Punkten, Members, Parameter etc. gebunden -> alles ist möglich
    bisher stelle ich meinen Rechentyp per template-Parameter um alles auf float oder double -> double ist aber nicht zwingend noetig
    calulate ist eine Methode - könnte aber auch eine freie Funktion sein falls das was bringt

    Entwicklungssystem:
    VS2015 - zwingend
    (VS2017,gcc6.3,clang4/5 - sind (nur) zum spielen vorhanden)

    Mein Wissen:

    -Einfach verwenden != Schnell - man kann viel Falsch machen was dann auch zu noch schlechterer Performanz fuehren kann
    -der Microsoft-Kompiler kann Autovektorisieren wenn man ihm hilft
    -Programmieren mit Instrinsics ist wie Assembler 🙂 kann aber auch viel bringen - jeder Kompiler hat da sein eigenes Süppchen
    -Es gibt Libraries z.B. boost.SIMD die bei der Vektorisierung von Code helfen - es gibt da ein paar Libs aber deren Qualität usw.
    kann ich schlecht einschaetzen (stupide Aufrufe von boost::simd::sincos usw. wirken sich bisher eher negativ auf Performanz aus -
    was aber auch an meinem Speicher-Layout/Ablauf hängen kann
    -die calculate-Methode ist so "einfach" das man da wohl mit Autovektorisierung oder Instrinsics arbeiten kann
    -der VStudio/Analyze/Performance Analyser sagt das ich die meiste Zeit in sin/cos verbringen - und scheinbar ist das auch ein Problem
    beim Vektorisieren - vielleicht hilft boost::simd::sincos(single/vector) bei richtiger Anwendung?

    Eingrenzung:

    1. bisher bekomme ich ich nur einen (f,g,h) Satz - d.h. ich koennte wirklich nur die Punktberechnung
    in sich vektorisieren

    2. in Zukunft (aber erst viel spaeter) will ich mein Programm umbauen damit man auch Blöcke(z.B. aus n (f,g,h) bekommt und
    calculate dann noch eine for-Schleife enthaelt - aber da muss ich erst mal pruefen ob das funktionieren kann

    kann man hier überhaupt was optimieren oder frisst eben sin/cos schon alles weg - boost.SIMD bietet eine kombinierte/vektorisierte sincos-Funktion
    die liefert die gleichen Ergebnisse ist aber (bisher) langsamer als die Microsoft Routinen - kann aber auch wie gesagt an falsche Anwendung liegen

    Ich könnte wohl auch erstmal die runtime-Konstanten im Kinematic-ctor vorberechnen und mich nur auf sin/cos konzentrieren - aber ich glaube das bringt nicht so viel oder?

    hat jemand Ideen, Tips?



  • die unveraenderlichen Anteile im ctor berechnet - hat aber keine messbare Auswirkung - geht im Rauchen unter

    struct kinematic
    {
    	kinematic(const point3d_t& a, const point3d_t& b, const point3d_t& c, const point3d_t& d, const point3d_t& e):
    		_a(a),
    		_b(b),
    		_c(c),
    		_d(d),
    		_e(e),
    		//---
    		_x_fix_part(_a.x() + _b.x()),
    		_y_fix_part(_a.y() + _b.y()),
    		_xy_fix_part0((-_e.x() + _c.y() + _d.y())),
    		_xy_fix_part1((_c.x() + _d.x())),
    		_z_fix_part(_a.z() + _b.z() + _c.z() + _d.z())
    		//---
    	{
    	}
    
    	point3d_t _a;
    	point3d_t _b;
    	point3d_t _c;
    	point3d_t _d;
    	point3d_t _e;
    
    	realtype_t _x_fix_part;
    	realtype_t _y_fix_part;
    	realtype_t _xy_fix_part0;
    	realtype_t _xy_fix_part1;
    	realtype_t _z_fix_part;
    
    	point3d_t calculate(realtype_t f, realtype_t g, realtype_t h) const
    	{
    		realtype_t sin_h = std::sin(h);
    		realtype_t cos_h = std::cos(h);
    		return point3d_t
    		(
    			_a.x() + _b.x() - (-_e.x() + _c.y() + _d.y()) * sin_h + (_c.x() + _d.x()) * cos_h,
    			_a.y() + _b.y() + (-_e.x() + _c.y() + _d.y()) * cos_h + (_c.x() + _d.x()) * sin_h + f,
    			_a.z() + _b.z() + _c.z() + _d.z() + g
    		);
    	}
    
    	point3d_t calculate2(realtype_t f, realtype_t g, realtype_t h) const
    	{
    		realtype_t sin_h = std::sin(h);
    		realtype_t cos_h = std::cos(h);
    		return point3d_t
    		(
    			_x_fix_part - _xy_fix_part0 * sin_h + _xy_fix_part1 * cos_h,
    			_y_fix_part + _xy_fix_part0 * cos_h + _xy_fix_part1 * sin_h + f,
    			_z_fix_part + g
    		);
    	}
    
    };
    


  • der gezeigte Code ist nur ein kleiner kompilierbarer Benchmark - braucht ca. 12-14sek auf meinen System - soll nur Aufzeigen wie die calculate-Methode genutzt wird


Log in to reply