Hier ist ein reales Beispiel aus Signalverarbeitungs- / Steuerungssystemen, an dem ich gerade arbeite:
Angenommen, Sie haben eine Struktur, die die Daten darstellt, die Sie sammeln:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Angenommen, Sie füllen sie in einen Vektor:
std::vector<Sample> samples;
... fill the vector ...
Angenommen, Sie möchten eine Funktion (z. B. den Mittelwert) einer der Variablen über einen Bereich von Stichproben berechnen und diese Mittelwertberechnung in eine Funktion einbeziehen. Der Zeiger auf das Mitglied macht es einfach:
double Mean(std::vector<Sample>::const_iterator begin,
std::vector<Sample>::const_iterator end,
double Sample::* var)
{
float mean = 0;
int samples = 0;
for(; begin != end; begin++) {
const Sample& s = *begin;
mean += s.*var;
samples++;
}
mean /= samples;
return mean;
}
...
double mean = Mean(samples.begin(), samples.end(), &Sample::value2);
Hinweis Bearbeitet am 05.08.2016 für einen präziseren Ansatz mit Vorlagenfunktionen
Und natürlich können Sie eine Vorlage erstellen, um einen Mittelwert für jeden Forward-Iterator und jeden Werttyp zu berechnen, der die Addition mit sich selbst und die Division durch size_t unterstützt:
template<typename Titer, typename S>
S mean(Titer begin, const Titer& end, S std::iterator_traits<Titer>::value_type::* var) {
using T = typename std::iterator_traits<Titer>::value_type;
S sum = 0;
size_t samples = 0;
for( ; begin != end ; ++begin ) {
const T& s = *begin;
sum += s.*var;
samples++;
}
return sum / samples;
}
struct Sample {
double x;
}
std::vector<Sample> samples { {1.0}, {2.0}, {3.0} };
double m = mean(samples.begin(), samples.end(), &Sample::x);
BEARBEITEN - Der obige Code hat Auswirkungen auf die Leistung
Wie ich bald herausfand, sollten Sie beachten, dass der obige Code einige schwerwiegende Auswirkungen auf die Leistung hat. Die Zusammenfassung lautet: Wenn Sie eine Zusammenfassungsstatistik für eine Zeitreihe oder eine FFT usw. berechnen, sollten Sie die Werte für jede Variable zusammenhängend im Speicher speichern. Andernfalls führt das Durchlaufen der Serie zu einem Cache-Fehler für jeden abgerufenen Wert.
Betrachten Sie die Leistung dieses Codes:
struct Sample {
float w, x, y, z;
};
std::vector<Sample> series = ...;
float sum = 0;
int samples = 0;
for(auto it = series.begin(); it != series.end(); it++) {
sum += *it.x;
samples++;
}
float mean = sum / samples;
Auf vielen Architekturen eine Instanz von Sample
füllt eine eine Cache-Zeile. Bei jeder Iteration der Schleife wird ein Sample aus dem Speicher in den Cache gezogen. 4 Bytes von der Cache-Zeile werden verwendet und der Rest wird weggeworfen, und die nächste Iteration führt zu einem weiteren Cache-Fehler, Speicherzugriff und so weiter.
Viel besser, um dies zu tun:
struct Samples {
std::vector<float> w, x, y, z;
};
Samples series = ...;
float sum = 0;
float samples = 0;
for(auto it = series.x.begin(); it != series.x.end(); it++) {
sum += *it;
samples++;
}
float mean = sum / samples;
Wenn nun der erste x-Wert aus dem Speicher geladen wird, werden auch die nächsten drei in den Cache geladen (vorausgesetzt, die Ausrichtung ist angemessen), sodass für die nächsten drei Iterationen keine Werte geladen werden müssen.
Der obige Algorithmus kann durch die Verwendung von SIMD-Anweisungen auf z. B. SSE2-Architekturen etwas weiter verbessert werden. Diese funktionieren jedoch viel besser, wenn alle Werte im Speicher zusammenhängend sind und Sie mit einem einzigen Befehl vier Samples zusammen laden können (mehr in späteren SSE-Versionen).
YMMV - Entwerfen Sie Ihre Datenstrukturen entsprechend Ihrem Algorithmus.