Registriert: Di Mai 18, 2004 16:45 Beiträge: 2623 Wohnort: Berlin
Programmiersprache: Go, C/C++
Aufgabe Es wird eine API zur Verfügung gestellt, mit der man 0 bis mehrere Werte in einer Gruppe ablegen kann. Die logische Verknüpfung der Werte, in einer Gruppe, werden durch den Nutzer, der API bestimmt und nicht durch die API selber. Ob der Nutzer ein Wert mit einem 0.25ms Intervall in Verbindung bringt oder einfach nur jeden 8 Wert will ist nicht Aufgabe der API. Diese Gruppen werden von einer Verwaltungs Instanz gehalten. Die API soll nicht Thread-Safe sein, da dieses die Anzahl der möglichen Resultate und die Komplexität der Challenge unnötig erhöht. Mit der API können nur Gruppen abgebildet werden, die als Gleitkommazahl darstellbar sind. Die einzelnen Gruppen sollen durch ein Namen Kennzeichenbar sein, müssen aber nicht Einzigartig sein.
Was zählt als Lösung Für jede Lösung muss der Sourcecode und ein Beispielcode(wie verwendet man den Code) für jede öffentlich zugreifbare Methode/Property eingereicht werden. Es dürfen externe Bibliotheken verwendet werden und sollten mit den Bezugsquellen genannt werden. Ein UML Diagram ist nicht Pflicht hilft aber zum schnelleren Verständnis, der Lösung.
_________________ "Wer die Freiheit aufgibt um Sicherheit zu gewinnen, der wird am Ende beides verlieren" Benjamin Franklin
Verwendet wurde Bibliothekstechnisch nur die scala-library.jar von Scala 2.9. Die ist also bei Scala mit dabei. Ich war mal so frei und habe das alles in einer Datei gemacht und Beispielcode von Library-Code durch Comments getrennt.
Ausgabe:
Code:
Values recorded so far:
- Temperature: 40,000 at 1327437292574ms, 42,000 at 1327437292557ms
- Memory Usage: 512,000 at 1327437292574ms
Tante Edith meint: So ganz ohne Kommentar möchte ich es dann doch nicht dastehen lassen. Hat ja keinen Sinn, wenn niemand den Code versteht
Beginnend mit dem Profiler selbst: Zeile 14 ist das import-Statement. Das sollte jetzt keine all zu großen Überraschungen bereiten. Deswegen sei auch nur kurz darauf eingegangen: Man erkennt hieran, dass mutable collections verwendet werden. Leider hat Scala von Haus aus keine immutable MultiMap dabei und die Aufgabenstellung schrie förmlich danach. Und da Thread-safety nicht verlangt war, sprach direkt nichts gegen die mutable MultiMap. Zeile 16 ist die Klassendefinition für die Sample-Klasse. War zwar nicht verlangt, ich fand es aber hübscher, wenn so ein vom Profiler aufgezeichnetes Sample etwas gekapselt ist. Diese Art in Scala, Klassen zu deklarieren, sorgt dafür, dass automatisch ein Konstruktor gemacht wird, der die beiden in Klammern angegebenen Parameter initialisiert. Der Compiler generiert ein passendes equals und hashCode. Die Methode toString habe ich überschrieben, damit es in der Ausgabe ein wenig hübscher wird, ist aber auch optional. Zeile 20 schließlich ist der eigentliche Profiler. Da die gewünschte Funktionalität bereits vollständig in der Standardbibliothek enthalten ist, ist der Profiler erwartungsgemäß etwas knapp. Zeile 20 ist daher einfach nur ein Singleton (dafür dient das object-Keyword). Nennenswert ist da noch das mixin-keyword. Das mixt den MultiMap-Trait in die HashMap hinein und schon hat man eine MultiMap
Und nun zum Beispielcode: Zeile 4 ist quasi die main-Methode, wie man sie aus Java und C* kennt. Nur auf das nötigste reduziert. Zeile 5-7 fügt dem Profiler Werte hinzu und der Profiler sorgt dafür, dass sie in den passenden benamsten Gruppen landen. Zeile 10 spuckt die Werte, die der Profiler gespeichert hat, wieder aus. Wer die Zeile auch nach mehrmaligem Lesen nicht so ganz rallt, sei beruhigt. Die macht nämlich eine ganze Menge und ist deswegen so kryptisch. Tatsächlich wird über alle Gruppen iteriert, ihr Name ausgegeben und nach ihrem Namen die einzelnen Messwerte, die nach ihrem Timestamp sortiert werden. Könnte man auch etwas leserlicher schreiben, mir hat der Einzeiler aber gefallen
_________________ "Für kein Tier wird so viel gearbeitet wie für die Katz'."
Eine C++ Lösung mit Macro-Einsatz zur Reduzierung von Allocations und Vermeidung von Stringvergleichen....wir wollen ja schließlich etwas Profilen...
Gut, dass mein Code auch keine Stringvergleiche macht. Und Dank der mittlerweile standardmäßig eingeschalteten Escape Analysis sind Allocations auch weitestgehend auf dem Stack und nicht mehr auf dem Heap.
_________________ "Für kein Tier wird so viel gearbeitet wie für die Katz'."
Gut, dass mein Code auch keine Stringvergleiche macht.
Du benutzt eine HashMap. D.h. es wird zu erst ein Hash berechnet...der Hash gesucht und dann nochmal der String verglichen..könnte ja eine Hashkollision gewesen sein. Gut, ich kenne Scala nicht, vielleicht werden hier kontante Strings speziell behandelt, so dass es letztlich nur ein Pointer-Vergleich ist. In jedem Fall behaupte ich das "direkt an die richtige Stelle schreiben" schneller ist
Was die Allkokation angeht, die HashMap liegt mit Sicherheit auf dem Heap und muss schließlich irgendwie wachsen wenn neue Samples kommen. Wenn ich nach ca. 1min googlen diese Escape Analyisis richtig verstehe optimiert diese nur "Short lived objects", was auch Sinn macht. Die Samples leben aber sicher recht lange.
Java macht tatsächlich als erstes einen Pointervergleich beim String.equals. Und da die Strings hier konstant sind und im Bytecode alle gleichen Strings nicht nur gleich, sondern auch identisch sind, geht der Vergleich sehr schnell. Und der Hash der Strings wird, sobald er ein Mal berechnet wurde, gespeichert. Ist also auch sehr schnell vergleichbar. Daher hat der Code quasi keine nennenswerten Stringvergleiche zur Laufzeit.
Das mit den short-lived objects ist richtig. Aber genau das sind ja die, die die Performance üblicherweise herunterziehen.
Ich habe mal eine komplett thread-safe Version gemacht:
overridedef toString ="%.3f at %dms".format(value, timestamp / 1000000)
}
Der Code ist komplett immutable (!), daher auch zu 100% threadsafe. Außerdem noch ein bisschen besser gekapselt, so dass der Aufrufer diesmal wirklich leichtes Spiel hat (Zeile 1 - 9). Diese Version gefällt mir fast besser als die erste Hat außerdem den Vorteil, dass man den Profiler und die Ergebnisse wild in der Gegend herumreichen kann ohne Angst haben zu müssen, dass irgend ein anderer Teil des Programms daran Dinge verändert.
_________________ "Für kein Tier wird so viel gearbeitet wie für die Katz'."
std::cout<<mem->at(0)<<"% at "<<memTime->at(0)<<"seconds after start"<<std::endl;
return0;
}
Output
Code:
DeltaTime
Memory
MemoryTimeStamp
0.33
0.14% at 0.1seconds after start
edit: Idee Es gibt 2 Klassen, SampleGroup und Profiler. SampleGroup wird von std::vector<float> abgeleitet, da diese intern ein array von floats verwendet und Speicher, für weitere Elemente vor reserviert. Durch diese Eigenschaften ist das hinzufügen und zugreifen sehr schnell, ausser wenn der reservierte Speicher voll ist und ein neuer reserviert wird, denn dann muss der alte Block kopiert werden. Will man den damit verbundenen Peek vermeiden, dann sollte man auf dequeue zurück greifen und auf gering längere Zeiten, für das hinzufügen von Elementen in kauf nehmen. Der Konstruktor erwartet ein const char*, weil c++ alle Texte als const char* interpretiert und ich das duplizieren eines ohnehin immer verfügbaren Textes sparen.
Profiler ist eine Klasse mit statischen Membern und Methoden. Zum lagern der SampleGroup's benutzte ich folgenden Container std::map<unsigned long long,SampleGroup>. Der Key ist wird eventuell einige verwundern und ist eine Optimierung. Da eine SampleGroup über ein Namen erreichbar ist und C++ nun mal den Text unter einer einzigartigen Speicheradresse hinterlegt,kann man diesen auch perfekt als Key verwenden. Um sicher zu gehen, dass ich den Pointer fassen kann, nehme ich ein unsigned long long, dass per C++11 Standard jeder Compiler supporten muss. Wenn man also schon zur Compiletime weiß, auf welches Element man zugreifen will, dann kann man dies so prima lösen. Wenn man erst zur Runtime weiß, welches Objekt man haben will, dann muss man über die Groups Methode sich durch die Objekte hangeln. Der Zugriff auf Profilerdaten sollte eigentlich keine Zeitkritische Operation sein aber das erweitern schon.
Da bisher nur GCC constexpr aus den neuen Standard beherrscht, hab ich den Pointeransatz gewählt. Sobald VS2012 den Support bietet, macht es aber Sinn den Pointer-Ansatz durch 2 Hashfunktionen zu ersetzten. Die erste wäre eine Compiletime Hashfunktion, durch constexpr realisiert und die 2. wäre gleiches nur für Runtime, damit auch string Klassen verwendet werden können.
_________________ "Wer die Freiheit aufgibt um Sicherheit zu gewinnen, der wird am Ende beides verlieren" Benjamin Franklin
Mitglieder in diesem Forum: 0 Mitglieder und 0 Gäste
Du darfst keine neuen Themen in diesem Forum erstellen. Du darfst keine Antworten zu Themen in diesem Forum erstellen. Du darfst deine Beiträge in diesem Forum nicht ändern. Du darfst deine Beiträge in diesem Forum nicht löschen. Du darfst keine Dateianhänge in diesem Forum erstellen.