Oto rzeczywisty przykład, nad którym teraz pracuję, z systemów przetwarzania / kontroli sygnałów:
Załóżmy, że masz strukturę reprezentującą gromadzone dane:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Załóżmy teraz, że umieścisz je w wektorze:
std::vector<Sample> samples;
... fill the vector ...
Załóżmy teraz, że chcesz obliczyć jakąś funkcję (powiedzmy średnią) jednej ze zmiennych w zakresie próbek i chcesz uwzględnić to obliczenie średnie w funkcji. Wskaźnik do członka ułatwia:
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);
Uwaga Zmodyfikowano 2016/08/05, aby uzyskać bardziej zwięzłe podejście do funkcji szablonu
I oczywiście możesz szablonować go, aby obliczyć średnią dla dowolnego iteratora do przodu i dowolnego typu wartości, który obsługuje dodawanie z samym sobą i dzielenie według size_t:
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);
EDYCJA - Powyższy kod ma wpływ na wydajność
Należy zauważyć, jak wkrótce odkryłem, że powyższy kod ma poważne konsekwencje dla wydajności. Podsumowanie jest takie, że jeśli obliczasz statystyki podsumowujące na szeregach czasowych lub obliczasz FFT itp., Powinieneś przechowywać wartości dla każdej zmiennej w sposób ciągły w pamięci. W przeciwnym razie iteracja po serii spowoduje brak pamięci podręcznej dla każdej odzyskanej wartości.
Rozważ wydajność tego kodu:
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;
W wielu architekturach jedno wystąpienie Sample
wypełnia wiersz pamięci podręcznej. Tak więc przy każdej iteracji pętli jedna próbka zostanie wyciągnięta z pamięci do pamięci podręcznej. Zostaną wykorzystane 4 bajty z linii pamięci podręcznej, a resztę wyrzucone, a następna iteracja spowoduje kolejne pominięcie pamięci podręcznej, dostęp do pamięci i tak dalej.
Znacznie lepiej to zrobić:
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;
Teraz, gdy pierwsza wartość x jest ładowana z pamięci, kolejne trzy również zostaną załadowane do pamięci podręcznej (zakładając odpowiednie wyrównanie), co oznacza, że nie potrzebujesz żadnych wartości załadowanych dla następnych trzech iteracji.
Powyższy algorytm można nieco ulepszyć, stosując instrukcje SIMD np. W architekturach SSE2. Jednak działają one znacznie lepiej, jeśli wszystkie wartości są ciągłe w pamięci i można użyć jednej instrukcji, aby załadować razem cztery próbki (więcej w późniejszych wersjach SSE).
YMMV - zaprojektuj struktury danych zgodnie z algorytmem.