นี่คือตัวอย่างในโลกแห่งความจริงที่ฉันกำลังทำงานอยู่ตอนนี้จากระบบประมวลผลสัญญาณ / ควบคุม:
สมมติว่าคุณมีโครงสร้างบางอย่างที่แสดงถึงข้อมูลที่คุณกำลังรวบรวม:
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);
แก้ไข - โค้ดด้านบนมีความเกี่ยวข้องกับประสิทธิภาพ
คุณควรทราบตามที่ฉันค้นพบในไม่ช้าว่ารหัสข้างต้นมีความเกี่ยวข้องกับประสิทธิภาพบางอย่าง สรุปคือถ้าคุณกำลังคำนวณสถิติสรุปในอนุกรมเวลาหรือคำนวณ FFT เป็นต้นคุณควรเก็บค่าสำหรับตัวแปรแต่ละตัวต่อเนื่องกันในหน่วยความจำ มิฉะนั้นการวนซ้ำในซีรี่ส์จะทำให้แคชพลาดสำหรับทุกค่าที่ดึงมา
พิจารณาประสิทธิภาพของรหัสนี้:
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 แรกจากหน่วยความจำแล้วสามค่าถัดไปจะถูกโหลดลงในแคชด้วย (หากมีการจัดตำแหน่งที่เหมาะสม) หมายความว่าคุณไม่จำเป็นต้องโหลดค่าใด ๆ สำหรับการทำซ้ำสามครั้งถัดไป
อัลกอริทึมข้างต้นสามารถปรับปรุงให้ดียิ่งขึ้นผ่านการใช้คำสั่ง SIMD ในสถาปัตยกรรม SSE2 อย่างไรก็ตามสิ่งเหล่านี้ทำงานได้มากดีกว่าหากค่าทั้งหมดอยู่ติดกันในหน่วยความจำและคุณสามารถใช้คำสั่งเดียวเพื่อโหลดตัวอย่างสี่ตัวอย่างเข้าด้วยกัน (เพิ่มเติมในรุ่น SSE รุ่นใหม่กว่า)
YMMV - ออกแบบโครงสร้างข้อมูลของคุณเพื่อให้เหมาะกับอัลกอริทึมของคุณ