[英]Performance: vector of classes or a class containing vectors
我有一個包含許多double值的類。 這存儲在向量中,其中類的索引很重要(它們從其他地方引用)。 該類看起來像這樣:
class A
{
double count;
double val;
double sumA;
double sumB;
vector<double> sumVectorC;
vector<double> sumVectorD;
}
vector<A> classes(10000);
需要盡可能快地運行的代碼是這樣的:
vector<double> result(classes.size());
for(int i = 0; i < classes.size(); i++)
{
result[i] += classes[i].sumA;
vector<double>::iterator it = find(classes[i].sumVectorC.begin(), classes[i].sumVectorC.end(), testval);
if(it != classes[i].sumVectorC.end())
result[i] += *it;
}
替代方案是代替一個巨大的循環,將計算分成兩個獨立的循環,例如:
for(int i = 0; i < classes.size(); i++)
{
result[i] += classes[i].sumA;
}
for(int i = 0; i < classes.size(); i++)
{
vector<double>::iterator it = find(classes[i].sumVectorC.begin(), classes[i].sumVectorC.end(), testval);
if(it != classes[i].sumVectorC.end())
result[i] += *it;
}
或者將類的每個成員存儲在這樣的向量中:
vector<double> classCounts;
vector<double> classVal;
...
vector<vector<double> > classSumVectorC;
...
然后運作:
for(int i = 0; i < classes.size(); i++)
{
result[i] += classCounts[i];
...
}
哪種方式通常會更快(跨x86 / x64平台和編譯器)? 預測和緩存行是最重要的事情嗎?
我在這里進行線性搜索(即查找)而不是哈希映射或二進制搜索的原因是因為sumVectors非常短,大約有4或5個元素。 分析顯示哈希映射較慢,二進制搜索稍慢。
由於兩種變體的實現看起來很容易,我會構建兩個版本並對它們進行分析以找到最快的版本。
經驗數據通常勝過猜測。
作為一個副作用:當前,最內層循環中的find()
對classes[i].sumVectorC
所有元素進行線性掃描,直到找到匹配的值。 如果該向量包含許多值,並且您沒有理由相信testVal
出現在向量的開頭附近,那么這將是緩慢的 - 考慮使用更快查找的容器類型(例如std::map
或其中一個非標准但通常實現的hash_map
類型)。
作為一般准則: 在低級實現優化之前考慮算法改進。
正如Lothar所說,你真的應該測試一下。 但是要回答你的上一個問題,是的,緩存未命中將成為一個主要問題。
此外,您的第一個實現似乎會在編碼時遇到load-hit-store停頓,但我不確定x86上有多少問題(這是XBox 360和PS3上的一個大問題)。
看起來優化find()將是一個巨大的勝利(配置文件肯定知道)。 根據不同的大小,除了用另一個容器替換向量之外,您還可以嘗試對sumVectorC進行排序並使用lower_bound形式的二進制搜索。 這會將線性搜索O(n)轉換為O(log n)。
如果你可以保證std::numeric_limits<double>::infinity
不是一個可能的值,確保數組在末尾用虛擬無限條目排序,然后手動編碼查找,以便循環條件是一個單一的測試:
array[i]<test_val
然后是一個平等測試。
然后你知道在未找到的情況下,查看值的平均數是(size()+ 1)/ 2。 當然,如果搜索陣列頻繁變化,那么保持排序的問題就成了問題。
當然,你沒有告訴我們很多關於sumVectorC或其他A的事情,所以很難確定並給出非常好的建議。 例如,如果sumVectorC永遠不會更新,那么很可能找到一個非常便宜的哈希(例如,轉換ULL和位提取),這對於適合double [8]的sumVectorC值是完美的。 然后,開銷是比特提取和1比較3或6
另外如果你有一個合理的sumVectorC.size()綁定(你提到4或5所以這個假設似乎不錯)你可以考慮使用聚合數組甚至只是一個boost::array<double>
並添加你自己的動態大小例如:
class AggregatedArray : public boost::array<double>{
size_t _size;
size_t size() const {
return size;
}
....
push_back(..){...
pop(){...
resize(...){...
};
這消除了對sumVectorC分配的數組數據的額外緩存行訪問。
在sumVectorC非常不經常更新的情況下,如果找到一個完美的哈希(在你的哈希算法類中)相對便宜,那么當sumVectorC改變時你可以帶來利潤。 這些小的查找可能會有問題,而算法的復雜性往往是無關緊要的 - 它是占主導地位的常數。 這是一個工程問題,而不是理論問題。
除非你能保證小地圖在緩存中,否則幾乎可以保證使用std :: map會使性能提高約130%,因為樹中的每個節點幾乎都在一個單獨的緩存行中
因此,不是每次搜索訪問(4次1 + 1次2)/ 5 = 1.2緩存行(前4個在第一個緩存行中,第5個在第二個緩存行中,您將訪問(1 + 2次2 + 2次3) )= 9/5)樹本身+ 1 =每次搜索2.8個緩存行(1是根節點1節點,2個節點是根節點的子節點,最后2個節點是根節點的孫子節點,加上樹本身)
所以我預測使用std :: map對於具有5個條目的sumVectorC來說需要2.8 / 1.2 = 233%
這就是我說的時候的意思:“這是一個工程問題而不是理論問題。”
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.