Seite 1 von 1

[C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 13:25
von Schrompf
Moin,

dachte mir, die Frage kann ich ja auch mal den Profis hier stellen. Ich habe eine komplexe nicht kopierbare Klasse. Dazu will ich bei Programmstart eine Anzahl anhand Systemparameter festlegen und dann diese Anzahl Instanzen erzeugen. Bisher habe ich das banal in einem vorreservierten std::vector getan:

Code: Alles auswählen

std::vector<Dingens> mDingenses;
mDingenses.reserve(anzahl);
for( 0 .. anzahl )
  mDingenes.emplace_back(...Konstruktorparameter...);
Jetzt nach Wechsel zu Visual Studio 2015 geht das nicht mehr. Anscheinend kompiliert der jetzt die Templates richtig, anstatt sie nur textuell einzusetzen wie bisher. Und damit wird auch die nicht benutzte Funktion resize() instanziiert, wodurch der Compiler Copy- oder Move-Konstruktor braucht und sich darob beschwert.

Wie könnte ich das Problem codearm lösen? Eigenen Speicher und Placement New wäre aktuell die einzige Option, die mir einfällt, aber da gibt's sicher was Kluges.

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 13:49
von Schrompf
Korrektur: anscheinend hat sich nur die Implementation von std::vector<>::reserve() geändert. Eigentlich hätte das nie funktionieren dürfen, weil reserve() ja bei schon existierender Befüllung *immer* Objeke bewegen oder umkopieren muss. Hmm... seltsam. Aber nuja, die Orginalfrage besteht weiterhin: wie löst man das simpel und sicher?

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 13:54
von dot
Schrompf hat geschrieben:Jetzt nach Wechsel zu Visual Studio 2015 geht das nicht mehr. Anscheinend kompiliert der jetzt die Templates richtig, anstatt sie nur textuell einzusetzen wie bisher. Und damit wird auch die nicht benutzte Funktion resize() instanziiert, wodurch der Compiler Copy- oder Move-Konstruktor braucht und sich darob beschwert.
Der C++ Standard schreibt (für den Fall der impliziten Spezialisierung eines Klassentemplate) vor, dass die Definition einer Memberfunktion nur instanziert wird, wenn die Memberfunktion auch benutzt wird. Das Problem liegt bereits in der Verwendung von reserve() und emplace_back(). Beide Funktionen müssen potentiell eine Reallokation des internen Buffers durchführen und dabei den Inhalt des Arrays vom alten in den neuen Buffer moven, es ist für diese beiden Funktionen daher (auch laut Standard) notwendig, dass der im vector gespeicherte Typ zumindest Moveable ist. Ich kann ehrlich gesagt nicht ganz nachvollziehen, wie der Code so jemals kompiliert haben soll.
Schrompf hat geschrieben:Eigenen Speicher und Placement New wäre aktuell die einzige Option, die mir einfällt, aber da gibt's sicher was Kluges.
Ich fürchte, wenn du die Objekte zusammehängend im Speicher liegen haben willst, wird es wohl oder übel darauf hinauslaufen. Ansonsten einfach std::list verwenden... ;)

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 14:03
von Schrompf
Stimmt, einfach ein anderer Container, der nicht reallokiert. Hm. Hier in diesem Fall ist mir aber tatsächlich die cachelokale Unterbringung wichtig, auch wenn das wahrscheinlich völlig fehlgeleitete Optimierung ist. Hm. Genaugenommen ist das wahrscheinlich sogar tödlich wegen False Sharing, wenn ich meine Worker Threads lokal beieinander liegen habe. Irgendwann werden die Cache-Zeilen mal größer und dann grillt mir das False Sharing die Performance.

Gut, dass wir dieses Gespräch geführt haben. Der Erkenntnisweg des Thomas Z. - jetzt live auf irgendnem Dritten.

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 14:08
von dot
Rein aus interesse: Was ist denn das für ein Ding, das so klein ist, dass False Sharing ein Problem sein könnte, gleichzeitig aber konzeptionell weder Copyable noch Moveable sein darf!? Mit std::list werden die Dinger in der Praxis sehr wahrscheinlich auch wieder hintereinander im Speicher liegen, weil die Allokationen direkt hintereinander passieren...

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 14:16
von Schrompf
Mein erster Versuch eines parallelen Job-Systems.

Code: Alles auswählen

/// @file Traum_ParallelArbeiter.h
/// Ein Arbeiter, der anfallende Aufgaben in seinem eigenen Thread ausführt

#pragma once

/// Führt in dem Thread, in dem er gestartet wurde, Aufgaben aus der Warteschlange aus.
class Traum_ParallelArbeiter
{
  friend class Traum_Parallel;

public:
  /// Konstruktor, nimmt eine Warteschlange entgegen, aus der er Jobs ausführt, und die Wartebedingung, an der er lauscht,
  /// wenn's gerade nix zu tun gibt
  Traum_ParallelArbeiter( moodycamel::ConcurrentQueue<Traum_ParallelFaser*>& pWarteschlange, std::mutex& pMutex, 
    std::condition_variable& pBedingung);

private:
  /// Arbeitsfunktion, sollte im dafür vorgesehenen Thread aufgerufen werden.
  void Arbeiten();

  /// Sagt dem Arbeiter, dass er zum Ende kommen soll
  void SetEndeSignal();

private:
  /// die Warteschlange, aus der wir Aufgaben ziehen
  moodycamel::ConcurrentQueue<Traum_ParallelFaser*>& mWarteschlange;
  /// Mutex und Bedingung, auf denen wir warten, bis wieder was zu tun ist
  std::mutex& mMutex;
  std::condition_variable& mBedingung;

  /// besagt, ob wir zum Ende kommen sollen.
  std::atomic_bool mIstEndeGefordert;

  /// ordentliches Stück Platzhalterspeicher ans Ende, damit jeder Arbeiter in einer eigenen Cache-Zeile landet
  static const size_t PlatzhalterGroesse = 256 - 3*sizeof( void*) - sizeof( std::atomic_bool);
  uint8_t mPlatzhalter[PlatzhalterGroesse];
};

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 14:20
von dot
Ok, macht Sinn, in dem Fall seh ich aber kein Problem darin, einfach eine std::list zu benutzen, da die Geschwindigkeit, in der über die Liste von Traum_ParallelArbeitern iteriert werden kann, völlig irrelevant sein sollte, ansonsten sorgst du mit deinem Platzhalter ja selbst schon dafür, dass das nicht unbedingt so effizient ist...

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 14:30
von Schrompf
Iteriert wird darüber eh nur einmal beim Starten und einmal beim Beenden. Mir ist nur aufgefallen, dass die ja bei jeder neuen Fiber, die sie annehmen, ihre Membervars anfassen. Daher die Vermutung von False Sharing.

Wobei... auch das ist Quatsch. Das sind ja nur Referenzen auf die Queue und die Wartebedingung, die sind für alle Instanzen des Arbeiters eh dieselben. Und der Rest der Klasse ist so irrelevant, dass False Sharing hier wirklich keinen kratzt. Du hast Recht, ich schmeiß den Platzhalter wieder raus.

Und ich habe allgemein den Eindruck, dass es sich lohnen könnte, das Gesamtsystem hier mal zur Diskussion zu stellen. Wenn jemand von euch Lust und Zeit hat, Software noch in dem Detail zu diskutieren.

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 16.09.2015, 20:07
von Spiele Programmierer
Ich nehme in so einem Fall meistens std::vector<std::unique_ptr<T>>.
Vorteile: Random Access, einfügen von bereits konstruierten Objekten und theoretisch leicht bessereres Cache-Verhalten, weil die Vorwärts- und Rückwärtszeiger dazwischen entfallen.

Optimal ist es natürlich auch nicht, aber bis auf ganz große Ausnahmen, sind Objekte ja zumindest verschiebbar.

Re: [C++] Container für nicht-kopierbare Objekte

Verfasst: 17.09.2015, 06:42
von dot
Stimmt, das ist auf jeden Fall auch eine gute Lösung.