Đây là một ví dụ thực tế mà tôi đang làm việc ngay bây giờ, từ các hệ thống xử lý / điều khiển tín hiệu:
Giả sử bạn có một số cấu trúc đại diện cho dữ liệu bạn đang thu thập:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
Bây giờ giả sử rằng bạn nhét chúng vào một vectơ:
std::vector<Sample> samples;
... fill the vector ...
Bây giờ giả sử rằng bạn muốn tính toán một số hàm (nói trung bình) của một trong các biến qua một phạm vi mẫu và bạn muốn tính toán phép tính trung bình này thành một hàm. Con trỏ thành viên làm cho nó dễ dàng:
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);
Lưu ý Đã chỉnh sửa 2016/08/05 cho cách tiếp cận chức năng mẫu ngắn gọn hơn
Và, tất nhiên, bạn có thể tạo mẫu để tính giá trị trung bình cho bất kỳ trình lặp chuyển tiếp và bất kỳ loại giá trị nào hỗ trợ bổ sung với chính nó và chia cho 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 - Đoạn mã trên có ý nghĩa về hiệu suất
Bạn nên lưu ý, như tôi sớm phát hiện ra, đoạn mã trên có một số hàm ý hiệu suất nghiêm trọng. Tóm tắt là nếu bạn đang tính toán một thống kê tóm tắt theo chuỗi thời gian hoặc tính toán FFT, v.v., thì bạn nên lưu trữ các giá trị cho từng biến liên tục trong bộ nhớ. Nếu không, việc lặp lại chuỗi sẽ gây ra lỗi nhớ cache cho mỗi giá trị được truy xuất.
Hãy xem xét hiệu suất của mã này:
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;
Trên nhiều kiến trúc, một ví dụ về Sample
sẽ điền vào một dòng bộ đệm. Vì vậy, trên mỗi lần lặp của vòng lặp, một mẫu sẽ được kéo từ bộ nhớ vào bộ đệm. 4 byte từ dòng bộ đệm sẽ được sử dụng và phần còn lại bị vứt đi, và lần lặp tiếp theo sẽ dẫn đến một lỗi bộ nhớ cache khác, truy cập bộ nhớ, v.v.
Tốt hơn nhiều để làm điều này:
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;
Bây giờ khi giá trị x đầu tiên được tải từ bộ nhớ, ba giá trị tiếp theo cũng sẽ được tải vào bộ đệm (giả sử căn chỉnh phù hợp), nghĩa là bạn không cần bất kỳ giá trị nào được tải cho ba lần lặp tiếp theo.
Thuật toán trên có thể được cải thiện phần nào thông qua việc sử dụng các hướng dẫn SIMD trên các kiến trúc SSE2. Tuy nhiên, những công việc này nhiều tốt hơn nếu các giá trị nằm liền kề nhau trong bộ nhớ và bạn có thể sử dụng một lệnh duy nhất để tải bốn mẫu với nhau (nhiều hơn trong các phiên bản SSE sau này).
YMMV - thiết kế cấu trúc dữ liệu của bạn cho phù hợp với thuật toán của bạn.