C++Guns – RoboBlog blogging the bot

22.06.2019

C++ Guns: 2019-06 pre-Cologne Paper - funny one

Filed under: Allgemein — Tags: — Thomas @ 08:06

P1770R0: On vectors, tensors, matrices, and hypermatrices - Wirklich jeder sollte diese kurze Präsentation einmal durchschauen. Ich habe den Eindruck, als hätte mindestens ein Mensch auf der Welt Ahnung von dem Thema.

P1764R0: ssize Should be Named count - Kurz und kompakt

P0631R7: Math Constants ENDLICH!

P1768R0: Contiguous Containers Should Contain .data() - Ja

P1751R0: Numeric Type Families - Ich weiss nicht ob ich integer will die Overhead mitbringen

P1717R0: Compile­time Metaprogramming in C++ - Zu lang zum lesen jetzt

P1708R0: Simple Statistical Functions - Sehr gut! Im Kontrast zu acpl

P1709R0: Graph Data Structures - Wow, viel Stuff. Das muss ich mal mit acpl::Graph vergleichen

P1682R0: std::to_underlying for enumerations - Siehe auch std::underlying_type

P1680R0: Implementing Contracts in GCC - Nice! Unbedingt zeitnah mal ausprobieren

P1672R0: “Axiom” is a False Friend - !

P1429R2: Contracts That Work

p1744r0: Avoiding Misuse of Contract-Checking - To read

p1743r0: Contracts, Undefined Behavior, and DefensiveProgramming - To read

P1645R0: constexpr for algorithms constexpr ftw

P0037R7: Fixed-Point Real Numbers

P0267R9: A Proposal to Add 2D GraphicsRendering and Display to C++

P1467R1: Extended floating-point types 16bit floats, ARM, GPU

P1331R1: Permitting trivial default initialization in constexpr contexts

P1301R3: [[nodiscard("should have a reason")]] - Warum nicht

P1280R2: Integer Width Literals

P1068R1: Vector API for random number generation

P1050R1: Fractional Numeric Type

P1030R2:std::filesystem::path_view

P1021R4: Filling holes in Class Template Argument Deduction

P0784R6: More constexpr containers

P0533R5: constexpr for and

Mehr...
2019-06 pre-Cologne mailing available (1 of 2)
2019-06 pre-Cologne mailing available (2 of 2)

09.06.2019

C++ Guns: std::filesystem und std::regex

Filed under: Allgemein — Tags: — Thomas @ 07:06

Heute mache ich die ersten Versuche mit std::filesystem und std::regex. Der GCC 8.1 ist installiert. Die Doku liegt bereit. Es kann los gehen.

std::filesystem

Ich möchte bestimmte "Müll" Dateien im Projekt Verzeichnis auflisten und ihren Speicherplatzverbrauch summieren. Die Handhabung von std::filesystem ist wirklich erstaunlich einfach. Klar strukturiert. Es funktioniert. Etwas, dass böse Zungen von std::chrono ja nicht behaupten.

Eine Schleife über alle Dateien in einem Verzeichnis geht echt fix:

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
for(auto& p: fs::recursive_directory_iterator("/", fs::directory_options::skip_permission_denied)) {
    if(p.is_regular_file()) {
        std::cout << p.path().filename() << " " << p.file_size() << '\n';
    }
}

Bis GCC 8 muss das Projekt noch explizit mit -lstdc++fs, ab GCC 9 dann nicht mehr.

Achtung: Beim GCC 8.1 und Netzlaufwerke hab ich festgestellt, dass is_regular_file() falsche Werte zurück gibt. Im Vergleich zu fs::is_regular_file(fs::status(p)), was das selbe macht. Ob das im GCC 9.1 auch so ist, muss ich noch prüfen.

Die Fehlerbehandlung ist auch nett gelöst. Man hat diesmal die Auswahl zwischen Exception und Error Code. Viele Funktionen kann ein error_eode Objekt übergeben werden (als Referenz nicht als Pointer woohoo!).

std::error_code ec;
for(auto& p: fs::recursive_directory_iterator("/", fs::directory_options::skip_permission_denied, ec)) {
if(e) {
    std::cout << e.message();

Oder mit Exception.

try {            
    if(fs::is_regular_file(fs::status(p))) {
    }
catch(fs::filesystem_error& err) {
    std::cerr << err.what() << "\n";
}

Als keiner Bonus hier meine std::filesystem::file_type ostream Funktion

std::ostream& operator<<(std::ostream& s, const std::filesystem::file_type type) {
    using std::filesystem::file_type;
    switch (type) {
    case file_type::none:      s << "none"; break;
    case file_type::not_found: s << "not_found"; break;
    case file_type::regular:   s << "regular"; break;
    case file_type::directory: s << "directory"; break;
    case file_type::symlink:   s << "symlink"; break;
    case file_type::block:     s << "block"; break;
    case file_type::character: s << "character"; break;
    case file_type::fifo:      s << "fifo"; break;
    case file_type::socket:    s << "socket"; break;
    case file_type::unknown:   s << "unknown"; break;
    }
    return s;
}

std::regex

Regex ist auch garnicht sooo schwer, wenn man es mal kann. Ich möchte alle Dateien dieser Form finden: dbg_0000 dbg_0001 u.s.w. Das passende Regex Objekt sieht dann so aus:

const std::regex dbg_regex("dbg_[[:digit:]]+");

for(auto& p: fs::recursive_directory_iterator("/", fs::directory_options::skip_permission_denied)) {
    if(p.is_regular_file()) {
       if(std::regex_match(p.path().filename().c_str(), dbg_regex)) {
           // found
       }
    }
}

Als Verbindung zwischen Dateiname und Regex dient C-String. Da es string_view hier (noch?) nicht gibt.

08.06.2019

Graph - Basics of shortest paths

Filed under: Allgemein — Thomas @ 10:06

Shortcut links:
Basic graph definitions
Basics of shortest paths (this page)

Path lengths and distances

Sei 𝐺 = (𝑉, 𝐴) ein einfacher, gerichteter Graph und für jede Kante 𝑎 ∈ 𝐴 sei 𝑙(𝑎) eine reale Nummer und die Länge von 𝑎.

  1. Die Länge von einem gewöhnlichen Path (inklusive gewöhnliche cycles) ist die Summe von den Längen von allen Kanten in diesem Path.
  2. Abhängig vom Kontext, die Länge von einem generalized Path (inklusive generalized cycles) ist entweder definiert identisch zu gewöhnlichen Pathen, oder die Länge der backwards Kanten werden nicht addiert, sondern subtrahiert.
  3. Wenn die Länge von einem gewöhnlichen oder generalizedn Cycle negativ ist, wird dieser Cycle einen negativen cycle genannt.
  4. Für zwei Knoten 𝑠,𝑡 ∈ 𝑉, ein kürzester Path von 𝑠 nach 𝑡 ist ein (𝑠,𝑡)-path mit minimaler Länge von allen (𝑠,𝑡) Pathe.
  5. Die Distance von 𝑠 nach 𝑡 ist:
    • +unendlich wenn kein (𝑠,𝑡)-path existiert;
    • -unendlich wenn es einen negativen cycle entlang (𝑠,𝑡)-path gibt;
    • Die Länge von einem kürzesten (𝑠,𝑡)-path sonst;

Note:
In diesem Kontext sind ungerichtete Graphen als symmetrische gerichtete Graphen anzusehen, so dass zwei gegensätzlich gerichtete Kanten die selbe Länge haben.
Wenn es keine negativen cycles gibt, ist die Distanz von einem Knoten zu sich selbst 0, weil der triviale Path ohne keinen die Länge 0 hat. Auf der anderen seite, wenn ein Knoten in einem negativen cycle ist, ist seine Distanz zu sich selbst -unendlich.
Weil durch den negativen cycle ja immer ein Weg mit einer noch kürzeren Länge gefunden werden kann. Und negativ ist kleiner als Null.

Subpath property of shortest paths

Statement: Für zwei Knoten 𝑠,𝑡 ∈ 𝑉, sei 𝑝 ein kürzester (𝑠,𝑡)-Path. Seit 𝑣,𝑤 ∈ 𝑉 zwei Knoten auf dem Path 𝑝, so dass 𝑣 vor 𝑤 liegt. Dann ist der subpath von 𝑝, von 𝑣 zu 𝑤 ein kürzester (𝑣,𝑤)-Path.

Beweis: Seien 𝑝1, 𝑝2 und 𝑝3 die Subpathe von 𝑝 von 𝑠 nach 𝑣, von 𝑣 nach 𝑤 und von 𝑤 nach 𝑡. Für einen Widerspruchsbeweis angenommen, es gibt einen (𝑣,𝑤)-paht 𝑝2' der kürzer ist als 𝑝2. Dann würde die Aneinanderreihung der Pathe 𝑝1+𝑝2'+𝑝3 einen kürzeren (𝑠,𝑡)-path als 𝑝 ergeben. Da 𝑝 aber schon der kürzeste (𝑠,𝑡)-path ist, ist dies ein Widerspruch.
Die Subpathe eines kürzesten Pathes sind ebenfalls kürzeste Pathe.

Anmerkung: Gewöhnlich wird nur der Fall v=s beachtet. Also dass es nur zwei Pathe gibt und die Argumentation mit dem ersten Path gemacht wird. Diese Version wird auch prefix property genannt.

Valid distance property

Definition: Für jeden Knoten 𝑢 ∈ sei 𝑑(𝑢) eine real Zahl. Irgendeinen Zahl, muss nicht die Distanz sein. Das valid distance property wird erfüllt, wenn 𝑑(𝑤) ≤ 𝑑(𝑣) + l(𝑣,𝑤) für alle Kanten (𝑣,𝑤) ∈ 𝐴.
Das ist einfach so eine Definition.

Statement: Sei 𝑠 aus 𝑉 und für jeden Knoten 𝑢 aus 𝑉 sei d(𝑢) die Distanz von 𝑠 nach 𝑢 unter Beachtung der Kantenlängen 𝑙.
Es sei d(𝑤) ≤ d(𝑣) + 𝑙(𝑣,𝑤) Für alle Kanten (𝑣,𝑤) aus 𝐴.
Das valid distance property ist erfüllt für kürzeste Wege mit Kantenlängen im Graph.

Heißt auf gut deutsch, dass es eine "kürzeste" Kante von 𝑣 nach 𝑤 gibt und alle anderen Kanten von 𝑣 nach 𝑤 sind länger. Und da 𝑣 ja beliebig sein kann, heißt es: Es gibt einen kürzesten Weg nach 𝑤 und alle andern sind länger.

Beweis: The linke Seite der Ungleichung ist die Länge von dem kürzesten (𝑠,𝑤) Path.
Die rechte Seite ist die Länge von irgendeinem (𝑠,𝑤) Path (also die Aneinanderreihnug von dem kürzesten (𝑠,𝑣) Path und einer Kante (𝑣,𝑤))

Statement distance update: In den meisten shortest-path algorithmen, für jeden Knoten 𝑣 AUS V gibt es ein Attribut d(𝑣).
Dieses Attribut entspricht die Länge von einem (s,𝑣) Path und ist die echte Distanz von s zu 𝑣 wenn der Algorithmus terminiert.
In praktisch allen standard Algorithmen, die folgende Update Operation ist die (einzige) Operation zum updaten des d-Attributs:
1. Wähle eine Kante (𝑣,w) AUS A
2. Wenn (𝑣,w) das valid distance properts verletzt, also diese Kante einen kürzeren Weg zu w ermöglicht, also d(w) > d(𝑣) + l(𝑣,w) dann setzte d(w) := d(𝑣) + l(𝑣,w)

Distances along shortest paths

Sei 𝑠 ∈ 𝑉 und für jeden Knoten 𝑢 ∈ V sei d(𝑢) die Distanz von 𝑠 nach 𝑢 unter Berücksichtigung der Kantenlängen 𝑙.

Statement: Angenommen es gibt keine negativen Cyclen. Für eine Kante (𝑣,𝑤), es ist d(𝑤) = d(𝑣) + 𝑙(𝑣,𝑤) genau dann wenn die Kante (𝑣,𝑤) die letzte Kante von einem kürzesten (𝑠,𝑤) Path ist.

Beweis:

  1. Fall d(w) = d(v) + l(v,w)
    Dann hat die Aneinanderreihung von dem kürzesten Path von s nach v und (v,w) die Länge d(w) und damit die kürzeste Länge. (Weil die Kante (v,w) eben die letzte Kante im (s,w) Path ist.
  2. Fall d(w) != d(v) + l(v,w)
    Wegen dem valid distanz property ist d(w) < d(v) + l(v,w) (Wenn der Path nicht über die kürzeste kante geht, dann über eine Längere, also ist die Distanz auch größer als die kürzeste Distanz d(w)). Für einen Widerspruchsbeweis angenommen, dass ein kürzester (s,w) Path ist von der Form p + (v,w). Dann hat p die Länge d(w) - l(v,w) < d(v) was ein Widerspruch zur Definition von d(v) ist.

Reconstruction of a shortest path from the distances

Angenommen, dass für die Knoten 𝑠,𝑡 ∈ V die Distanz d𝑠(t) von 𝑠 to 𝑡 unter Beachtung der Kantenlängen l, welche eine reale Nummer ist (weder +inf noch -inf). Dann kann ein kürzester (𝑠,𝑡) Path 𝑝 wie folgt konstruiert werden.

  1. Initialisiere 𝑝 als leer
  2. Setzte 𝑤 := 𝑡 - Wir konstruieren den Path rückwärts von 𝑡 nach 𝑠
  3. While w /= s
    1. Finde eine Kante (𝑣,𝑤), so dass d𝑠(𝑤) = d𝑠(𝑣) + l(𝑣,𝑤)
    2. Füge die Kante (𝑣,𝑤) an den Anfang von 𝑝 an
    3. Setze 𝑤 := 𝑣

Constructing a shortest-paths arborescence or a negative cycle

Wie beim Valid distance property erwähnt, benutzen die meisten Algorithmen für das kürzeste Wege Probleme eine Standard Update Operation. Diese Operation kann wie folgt erweitert werden (mit 𝑠 für den Startknoten).
Jeder Knoten v ∈ V speichert eine von den incoming Kanten als zusätzliches Attribut:

  1. Initialisierung des Attributs:
    Dieses Attribut wird für den Knoten s mit void initialisiert.
    Für alle anderen Knoten v, die mit d(v) = +inf initialisiert wurden, wird dieses Attribut auch mit void initialisiert.

    Je nachdem ob die ausgehenden Kanten von s bei der Initialisierung beachtet, oder erst während der Iteration, ist die Distanz der abgehenden Knoten von s damit nicht +inf.
    Und das zusätzliche Attribut dieser Knoten wird mit der initial incoming Kante (s,v) initialisiert.

  2. Update: Wann immer die Standard Update Operation auf eine Kante (v,w) angewendet wird (also das Aktualisieren von der Distanz), wird diese Kante (v,w) die incoming Kante von W. Also speichert das zusätzliche Attribut immer die zuletzt gesehene Kante.

Statement:
Betrachte ein Algorithmus welcher ein kürzesten (s,v) Path für jeden Knoten v ∈ V liefert, dabei sind die die Distanzen von s eine real Nummer (weder -inf noch +inf).

Bei Terminierung gelten folgende Statements:

  1. Die incoming Kante von s ist void genau dann wenn s nicht auf einem negative cycle Paht liegt. (Das ist auch so, selbst wenn s auf einem positive cycle Path liegt. Weil der kürzeste Weg von s nach t geht nicht nochmal über s.)
  2. Die gespeicherten Kanten in dem zusätzlichen Attribut bilden ein cycle genau dann wenn die erreichten Knoten einen negativen Cycle bilden. (Einen positiven cycle bilden die Knoten ja nicht, das wissen wir bereits.)
    Jeder cycle in diesen ausgewählten Kanten ist ein negative cycle. (Weil es positive Cycle ja nicht gibt)
  3. Wenn die erreichten Knoten von s aus keinen negativen cycle enthält (Und positive cycle haben wir ja nicht), dann bilden die gewählten Kanten eine arborescene bei der der Knoten s die Wurzel ist und die arborescene spannt über alle erreichten Knoten.
    Für jeden erreichbaren Knoten v ∈ V, der Path von s zu v in dieser Arborescene ist ein kürzester (s,v)-Path. (Ja was sonst wenn man einen shortest-path Algo laufen lässt.)

(Was ist das Problem mit negativne cycle bei andern Algos? Die Terminieren nicht?)

Beweis:
Der Algorithmus liefert d(s) < 0 genau dann wenn s auf einem negativen cycle ist. (Positive cycle gibts ja nicht und sonst ist d(s) > 0). Genau in diesem Fall wird die incoming Kante von s geupdated.
Das Beweis Statement Satz 1. (Zur Erinnerung: Satz 1: Die incoming Kante von s ist void wenn NICHT negative cycle. Wenn nicht dann void.)

(Aber woher wissen wir, dass der Algo das denn auch wirklich tut?)

Für den 2. Satz beachte erst, dass der zweite Satz impliziert den only-if Teil von dem ersten Satz, so müssen wir nicht den only-if Teil von dem ersten Satz beachten.
Sei v ein erreichbarer Knoten auf einen negative cycle. Sei p ein Path auf ausgewählten Kanten welcher in v endet.
Im Besonderen, lass p eine maximale anzahl voN Kanten von allen möglichen dieser Pathe.
Dann p kann nicht an s starte, weil der algorithmus wird eine distanz Wert für v liefern, welcher kleiner ist als die echte Distanz.
Klar, p cann nicht an einem unreachable Knoten starten. Super Klar.

(Admissible/zulässige) node potentials

Für jeden Knoten 𝑣 ∈ 𝑉, sei h(𝑣) eine reale Nummer.
Für Kante 𝑎 = (𝑣,𝑤) ∈ 𝐴, sei 𝑙h(𝑎) := 𝑙(𝑎) - h(𝑣) + h(𝑤)
In diesem Kontext, diese Werte werden gewöhnliche node potential genannt.
Node potentials sind zulässig wenn 𝑙(𝑎) >= 0 für alle 𝑎 ∈ 𝐴. Also Kantengewicht(länge) positiv ist.

Statement:
Ein (𝑠,𝑡)-path ist kürzer als ein anderer (𝑠,𝑡)-path unter der Berücksichtigung der Kantenlängen 𝑙h, genau dann wenn die selbe Aussage auch wahr ist für die Kantenlängen 𝑙. Also die shortest path Algorithmen funktionieren auch weiterhin.
Im Besonderen, der kürzeste Path unter Berücksichtigung der Kantenlängen 𝑙h ist exakt der selbe kürzeste Path unter Beachtung der Kantenlängen 𝑙. Also die Algorithmen geben sogar das selbe Ergebnis zurück. Wir haben nichts kaputt gemacht. Nur verbesser.

Beweis:
Set 𝑠 = 𝑣1 - 𝑣2 - ... - 𝑣k = 𝑡 die Knoten auf einem (𝑠,𝑡)-path. Gemein ist dass s = 𝑣1 und t = 𝑣k
Die Länge auf diesem Path unter Berücksichtigung von 𝑙h ist dann *einsetzen und umformen von 𝑙h und 𝑙 *
Also: Die Länge von einem (𝑠,𝑡)-path unter Berücksichtigung von 𝑙h und 𝑙, unterscheidet sich nur durch eine Konstante h(𝑡) - h(𝑠).

Ist ja auch klar: Wenn für jede Kante h(𝑣) subtrahiert und h(𝑤) addiert wird, und bei der nächsten Kante im Path w mit dem nächsten v tauscht. Dann heben sich die Termen in dieser Summe bis auf den ersten und letzten auf.

Anmerkung:
Admissible/zulässige node potential ermöglichen effiziente Algorithmen wie Dijkstra, welche nicht-negative Kanten Längen erwarten. Durch die node potentials kann man negative Kanten Längen ins positive ziehen.

G
𝐺
V
𝑉
A
𝐴
E
𝐸
K
𝐾
a
𝑎
k
𝑘
l
𝑙
m
𝑚
n
𝑛
p
𝑝
s
𝑠
t
𝑡
u
𝑢
v
𝑣
w
𝑤
Teilemenge

phi
φ
Element von

01.06.2019

C++Guns: RE: C++ Core Guidelines: std::array und std::vector sind die erste Wahl

Filed under: Allgemein — Tags: — Thomas @ 09:06

Ich finde es schade, dass der Artikel von Rainer Grimm bei heise.de C++ Core Guidelines: std::array und std::vector sind die erste Wahl so wenig Beachtung bekommt. Er wirft nicht nur ein paar interessante Fragen auf, sondern geht auf der Kernaussage von Performance ein: Don't think, use std::vector.

Natürlich hat sich der C++ Gründervater Bjarne Stroustrup schon seit Jahren (Jahrzehnten?) mit dem Thema beschäftigt. Es lohnt sich nachzulesen in Are lists evil?

Zuallererst: Benchmarks sind super schwer und keiner kann sie richtig machen. Daher werde ich im ersten Schritt
nur versuchen die Ergebnisse auf meinem Laptop nachzuvollziehen. Und im zweiten Schritt die Datenmenge oder die
Anzahl Zugriffe erhöhen, da eine Laufzeit von 0.07 Sekunden doch sehr gering ist. Da Linux ein Multiuser/Multithread
Betriebssystem ist, passiert allerlei Nebenbei. Und diese "Störungen" versuche ich durch eine längere Messzeit
zu auszublenden.

1) Reproduzieren
1.a) Erster Versuch
Beispiel Code runter laden, compilieren, starten, "Killed".

1.b)
Ich habe nicht genug RAM oder der RAM ist zugemüllt -_- Im Beispiel Code werden 100 Millionen Integer a 4 Byte
als Nutzdaten angegeben. Das ergibt ein Speicherverbrauch von _mindestens_ 400MB. std::vector und std::deque
funktionieren noch. Bei std::list steigt das Programm aus.
Der Grund ist einfach. Während std::vector einfach 400MB am Stück allokiert, passiert bei std::list schon mehr.
Ich wage zu behaupten, dass für jedes neu eingefügte Element einmal extra Speicher auf dem Heap allokiert wird.
Damit ist der Speicher Overhead natürlich gigantisch und das Programm steigt aus.

1.c)
Ein kurzer Blick in den Systemmonitor Zeigt, dass das Programm Firefox natürlich einige GB im RAM belegt. Also das Programm
beenden und schon zeigen sich 20 Instanzen von mysql die jeweils 170MB belegen. Wozu brauche ich eine Datenbank?
Wusste gar nicht, dass so etwas installiert ist. Also mysqld Server deinstallieren und beenden.

1.d) Zweiter Versuch
Immerhin läuft std::list jetzt durch, aber bei std::forward_list bricht das Programm mit der Fehlermeldung ab:
*** Error in `./a.out': free(): invalid pointer: 0x00000000464a1320 ***
Das weitere beenden von Programmen welche viel Speicher verbrauchen führe zu einem Absturz des Systems...

1.e) Dritter Versuch
Nach einem Neustart werden "nur" 832 von 7680MB als belegt angezeigt. Diesmal hat es funktioniert!

std::vector
time: 0.0352953930
res: 4999774274

std::deque
time: 0.0827120400
res: 4999774274

std::list
time: 0.2975692830
res: 4999774274

std::forward_list
time: 0.3002444010
res: 4999774274

Damit kann ich die Werte reproduzieren. std::vector ist wie zu erwarten am schnellsten. Und std::forward_list ist langsamer als std::list.

2) Längere Laufzeit
Die Datenmenge zu erhöhen würde kein Sinn machen. Also werde ich die Summe statt nur einmal, gleich 100 mal bilden.
Die zu erwartende Laufzeit liegt damit von 4 bis 30 Sekunden.
Um einen Vergleich anzustellen wie die Laufzeiten der Container sich relativ zu std::vector verhalten:

1.000 std::ector
0.427 std::deque
0.119 std::list
0.118 std::forward_list

std::vector
time: 3.4487668950
res: 499993166900

std::deque
time: 7.2533651530
res: 499993166900

std::list
time: 28.2407733910
res: 499993166900

std::forward_list
time: 30.5225665430
res: 499993166900

Hier die neuen relativen Werte:
1.000 std::vector
0.475 std::deque
0.122 std::list
0.113 std::forward_list

Also std::forward_list ist _wirklich_ langsamer als std::list. Es bleibt ein Rätsel.

Powered by WordPress