Wir haben eine ähnliche Diskussion über Tupel und Struktur und ich schreibe einige einfache Benchmarks mit Hilfe eines meiner Kollegen, um die Unterschiede in der Leistungsdauer zwischen Tupel und Struktur zu identifizieren. Wir beginnen zunächst mit einer Standardstruktur und einem Tupel.
struct StructData {
int X;
int Y;
double Cost;
std::string Label;
bool operator==(const StructData &rhs) {
return std::tie(X,Y,Cost, Label) == std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
}
bool operator<(const StructData &rhs) {
return X < rhs.X || (X == rhs.X && (Y < rhs.Y || (Y == rhs.Y && (Cost < rhs.Cost || (Cost == rhs.Cost && Label < rhs.Label)))));
}
};
using TupleData = std::tuple<int, int, double, std::string>;
Wir verwenden dann Celero, um die Leistung unserer einfachen Struktur und unseres Tupels zu vergleichen. Nachfolgend finden Sie den Benchmark-Code und die Leistungsergebnisse, die mit gcc-4.9.2 und clang-4.0.0 erfasst wurden:
std::vector<StructData> test_struct_data(const size_t N) {
std::vector<StructData> data(N);
std::transform(data.begin(), data.end(), data.begin(), [N](auto item) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, N);
item.X = dis(gen);
item.Y = dis(gen);
item.Cost = item.X * item.Y;
item.Label = std::to_string(item.Cost);
return item;
});
return data;
}
std::vector<TupleData> test_tuple_data(const std::vector<StructData> &input) {
std::vector<TupleData> data(input.size());
std::transform(input.cbegin(), input.cend(), data.begin(),
[](auto item) { return std::tie(item.X, item.Y, item.Cost, item.Label); });
return data;
}
constexpr int NumberOfSamples = 10;
constexpr int NumberOfIterations = 5;
constexpr size_t N = 1000000;
auto const sdata = test_struct_data(N);
auto const tdata = test_tuple_data(sdata);
CELERO_MAIN
BASELINE(Sort, struct, NumberOfSamples, NumberOfIterations) {
std::vector<StructData> data(sdata.begin(), sdata.end());
std::sort(data.begin(), data.end());
// print(data);
}
BENCHMARK(Sort, tuple, NumberOfSamples, NumberOfIterations) {
std::vector<TupleData> data(tdata.begin(), tdata.end());
std::sort(data.begin(), data.end());
// print(data);
}
Mit clang-4.0.0 gesammelte Leistungsergebnisse
Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec |
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort | struct | Null | 10 | 5 | 1.00000 | 196663.40000 | 5.08 |
Sort | tuple | Null | 10 | 5 | 0.92471 | 181857.20000 | 5.50 |
Complete.
Und Leistungsergebnisse, die mit gcc-4.9.2 gesammelt wurden
Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec |
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort | struct | Null | 10 | 5 | 1.00000 | 219096.00000 | 4.56 |
Sort | tuple | Null | 10 | 5 | 0.91463 | 200391.80000 | 4.99 |
Complete.
Aus den obigen Ergebnissen können wir das klar erkennen
Tupel ist schneller als eine Standardstruktur
Binäre Produkte von clang haben eine höhere Leistung als gcc. clang-vs-gcc ist nicht der Zweck dieser Diskussion, daher werde ich nicht ins Detail gehen.
Wir alle wissen, dass das Schreiben eines == oder <oder> Operators für jede einzelne Strukturdefinition eine schmerzhafte und fehlerhafte Aufgabe sein wird. Ersetzen Sie unseren benutzerdefinierten Komparator durch std :: tie und führen Sie unseren Benchmark erneut aus.
bool operator<(const StructData &rhs) {
return std::tie(X,Y,Cost, Label) < std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
}
Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec |
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort | struct | Null | 10 | 5 | 1.00000 | 200508.20000 | 4.99 |
Sort | tuple | Null | 10 | 5 | 0.90033 | 180523.80000 | 5.54 |
Complete.
Jetzt können wir sehen, dass die Verwendung von std :: tie unseren Code eleganter macht und es schwieriger ist, Fehler zu machen. Wir verlieren jedoch etwa 1% Leistung. Ich werde vorerst bei der std :: tie-Lösung bleiben, da ich auch eine Warnung zum Vergleichen von Gleitkommazahlen mit dem benutzerdefinierten Komparator erhalte.
Bis jetzt haben wir noch keine Lösung, um unseren Strukturcode schneller laufen zu lassen. Schauen wir uns die Swap-Funktion an und schreiben Sie sie neu, um zu sehen, ob wir eine Leistung erzielen können:
struct StructData {
int X;
int Y;
double Cost;
std::string Label;
bool operator==(const StructData &rhs) {
return std::tie(X,Y,Cost, Label) == std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
}
void swap(StructData & other)
{
std::swap(X, other.X);
std::swap(Y, other.Y);
std::swap(Cost, other.Cost);
std::swap(Label, other.Label);
}
bool operator<(const StructData &rhs) {
return std::tie(X,Y,Cost, Label) < std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
}
};
Mit clang-4.0.0 gesammelte Leistungsergebnisse
Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec |
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort | struct | Null | 10 | 5 | 1.00000 | 176308.80000 | 5.67 |
Sort | tuple | Null | 10 | 5 | 1.02699 | 181067.60000 | 5.52 |
Complete.
Und die mit gcc-4.9.2 gesammelten Leistungsergebnisse
Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
Group | Experiment | Prob. Space | Samples | Iterations | Baseline | us/Iteration | Iterations/sec |
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort | struct | Null | 10 | 5 | 1.00000 | 198844.80000 | 5.03 |
Sort | tuple | Null | 10 | 5 | 1.00601 | 200039.80000 | 5.00 |
Complete.
Jetzt ist unsere Struktur etwas schneller als die eines Tupels (ungefähr 3% mit clang und weniger als 1% mit gcc). Wir müssen jedoch unsere angepasste Swap-Funktion für alle unsere Strukturen schreiben.
tuple
is-Implementierung ist definiert, daher hängt sie von Ihrer Implementierung ab. Persönlich würde ich nicht damit rechnen.