繁体   English   中英

性能:类的向量或包含向量的类

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM