Inilah contoh dunia nyata yang saya kerjakan saat ini, dari pemrosesan sinyal / sistem kontrol:
Misalkan Anda memiliki beberapa struktur yang mewakili data yang Anda kumpulkan:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Sekarang anggaplah Anda memasukkan mereka ke dalam vektor:
std::vector<Sample> samples;
... fill the vector ...
Sekarang anggaplah Anda ingin menghitung beberapa fungsi (katakanlah mean) dari salah satu variabel pada rentang sampel, dan Anda ingin memasukkan perhitungan rata-rata ini ke dalam suatu fungsi. Pointer-ke-anggota memudahkan:
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);
Catatan Diedit 2016/08/05 untuk pendekatan fungsi-templat yang lebih ringkas
Dan, tentu saja, Anda bisa templat untuk menghitung rata-rata untuk forward-iterator dan semua tipe nilai yang mendukung penambahan dengan dirinya sendiri dan pembagian dengan 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);
EDIT - Kode di atas memiliki implikasi kinerja
Anda harus mencatat, karena saya segera menemukan, bahwa kode di atas memiliki beberapa implikasi kinerja yang serius. Ringkasannya adalah bahwa jika Anda menghitung statistik ringkasan pada deret waktu, atau menghitung FFT dll, maka Anda harus menyimpan nilai untuk setiap variabel yang bersebelahan dalam memori. Jika tidak, mengulangi seri akan menyebabkan cache hilang untuk setiap nilai yang diambil.
Pertimbangkan kinerja kode ini:
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;
Pada banyak arsitektur, satu instance dari Sample
akan mengisi baris cache. Jadi pada setiap iterasi loop, satu sampel akan ditarik dari memori ke dalam cache. 4 byte dari garis cache akan digunakan dan sisanya dibuang, dan iterasi berikutnya akan menghasilkan cache lain yang hilang, akses memori dan sebagainya.
Jauh lebih baik untuk melakukan ini:
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;
Sekarang ketika nilai x pertama dimuat dari memori, tiga berikutnya juga akan dimuat ke dalam cache (seandainya penyelarasan yang sesuai), artinya Anda tidak perlu nilai apa pun dimuat untuk tiga iterasi berikutnya.
Algoritme di atas dapat ditingkatkan sedikit lebih jauh melalui penggunaan instruksi SIMD pada misalnya arsitektur SSE2. Namun, ini bekerja jauh lebih baik jika semua nilai bersebelahan dalam memori dan Anda dapat menggunakan instruksi tunggal untuk memuat empat sampel bersama-sama (lebih banyak di versi SSE nanti).
YMMV - desain struktur data Anda agar sesuai dengan algoritma Anda.