यहाँ एक वास्तविक दुनिया उदाहरण है जो मैं अभी सिग्नल प्रोसेसिंग / नियंत्रण प्रणाली से काम कर रहा हूँ:
मान लीजिए कि आपके पास कुछ संरचना है जो आपके द्वारा एकत्रित किए जा रहे डेटा का प्रतिनिधित्व करती है:
struct Sample {
time_t time;
double value1;
double value2;
double value3;
};
अब मान लीजिए कि आप उन्हें एक वेक्टर में भर देते हैं:
std::vector<Sample> samples;
... fill the vector ...
अब मान लीजिए कि आप किसी एक नमूने पर कई प्रकार के चरों में से कुछ फ़ंक्शन (माध्य का मतलब) की गणना करना चाहते हैं, और आप इस माध्य गणना को एक फंक्शन में बदलना चाहते हैं। सूचक-सदस्य इसे आसान बनाता है:
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);
नोट अधिक संक्षिप्त टेम्पलेट-फ़ंक्शन दृष्टिकोण के लिए 2016/08/05 को संपादित किया गया
और, ज़ाहिर है, आप इसे किसी भी फॉरवर्ड-इटरेटर और किसी भी प्रकार के मान के लिए गणना करने के लिए टेम्पलेट कर सकते हैं जो 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 - उपरोक्त कोड में प्रदर्शन निहितार्थ हैं
आपको ध्यान देना चाहिए, जैसा कि मैंने जल्द ही पता चला है, कि ऊपर दिए गए कोड में कुछ गंभीर प्रदर्शन निहितार्थ हैं। सारांश यह है कि यदि आप किसी टाइम सीरीज़ पर सारांश आँकड़ा की गणना कर रहे हैं, या एफएफटी आदि की गणना कर रहे हैं, तो आपको प्रत्येक चर के लिए मूल्यों को स्मृति में संचित करना चाहिए। अन्यथा, श्रृंखला पर पुनरावृत्ति करने से पुनर्प्राप्त किए गए प्रत्येक मूल्य के लिए कैश मिस हो जाएगा।
इस कोड के प्रदर्शन पर विचार करें:
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;
कई आर्किटेक्चर पर, एक उदाहरण Sample
कैश लाइन को भरेगा। तो लूप के प्रत्येक पुनरावृत्ति पर, एक नमूना को मेमोरी से कैश में खींच लिया जाएगा। कैश लाइन से 4 बाइट का उपयोग किया जाएगा और बाकी को फेंक दिया जाएगा, और अगले पुनरावृत्ति के परिणामस्वरूप एक और कैश मिस, मेमोरी एक्सेस और इसी तरह होगा।
ऐसा करने के लिए बहुत बेहतर:
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;
अब जब पहले x मान को मेमोरी से लोड किया जाता है, तो अगले तीन को भी कैश में लोड किया जाएगा (उपयुक्त संरेखण को दबाते हुए), जिसका अर्थ है कि आपको अगले तीन पुनरावृत्तियों के लिए लोड किए गए किसी भी मान की आवश्यकता नहीं है।
उपरोक्त SSE2 आर्किटेक्चर पर SIMD निर्देशों के उपयोग के माध्यम से उपरोक्त एल्गोरिथ्म में कुछ हद तक सुधार किया जा सकता है। हालाँकि, ये बहुत बेहतर काम करते हैं यदि मान सभी मेमोरी में सन्निहित हैं और आप चार नमूनों को एक साथ लोड करने के लिए एक एकल निर्देश का उपयोग कर सकते हैं (अधिक बाद में एसएसई संस्करण)।
YMMV - अपने एल्गोरिथ्म के अनुरूप अपने डेटा संरचनाओं को डिज़ाइन करें।