所以我有这个函数用来计算统计数据(min / max / std / mean)。现在的问题是它通常在10,000乘15,000矩阵上运行。矩阵在类中存储为vector<vector<int> >
。现在创建和填充所述矩阵变得非常快,但是当它归结为统计部分时,它变得非常缓慢。
E.g。一次读取一个像素的地理像素的所有像素值大约需要30秒。 (它涉及大量复杂的数学运算,以便将像素值正确地映射到相应的点),以计算整个矩阵的统计数据,大约需要6分钟。
void CalculateStats()
{
//OHGOD
double new_mean = 0;
double new_standard_dev = 0;
int new_min = 256;
int new_max = 0;
size_t cnt = 0;
for(size_t row = 0; row < vals.size(); row++)
{
for(size_t col = 0; col < vals.at(row).size(); col++)
{
double mean_prev = new_mean;
T value = get(row, col);
new_mean += (value - new_mean) / (cnt + 1);
new_standard_dev += (value - new_mean) * (value - mean_prev);
// find new max/min's
new_min = value < new_min ? value : new_min;
new_max = value > new_max ? value : new_max;
cnt++;
}
}
stats_standard_dev = sqrt(new_standard_dev / (vals.size() * vals.at(0).size()) + 1);
std::cout << stats_standard_dev << std::endl;
}
我在做一些可怕的事吗?
修改的
要回复评论,T将是一个int。
编辑2
我修复了我的std算法,这是最终产品:
void CalculateStats(const std::vector<double>& ignore_values)
{
//OHGOD
double new_mean = 0;
double new_standard_dev = 0;
int new_min = 256;
int new_max = 0;
size_t cnt = 0;
int n = 0;
double delta = 0.0;
double mean2 = 0.0;
std::vector<double>::const_iterator ignore_begin = ignore_values.begin();
std::vector<double>::const_iterator ignore_end = ignore_values.end();
for(std::vector<std::vector<T> >::const_iterator row = vals.begin(), row_end = vals.end(); row != row_end; ++row)
{
for(std::vector<T>::const_iterator col = row->begin(), col_end = row->end(); col != col_end; ++col)
{
// This method of calculation is based on Knuth's algorithm.
T value = *col;
if(std::find(ignore_begin, ignore_end, value) != ignore_end)
continue;
n++;
delta = value - new_mean;
new_mean = new_mean + (delta / n);
mean2 = mean2 + (delta * (value - new_mean));
// Find new max/min's.
new_min = value < new_min ? value : new_min;
new_max = value > new_max ? value : new_max;
}
}
stats_standard_dev = mean2 / (n - 1);
stats_min = new_min;
stats_max = new_max;
stats_mean = new_mean;
这仍然需要大约120-130秒,但这是一个巨大的进步:)!
答案 0 :(得分:29)
您是否尝试过编写代码?
你甚至不需要花哨的探查器。只需在那里粘贴一些调试时序语句。
我告诉你的任何事情都只是一种有根据的猜测(可能是错误的)
由于您访问矢量内容的方式,您可能会遇到大量缓存未命中。您可能希望将某些结果缓存到size()但我不知道这是否是问题。
答案 1 :(得分:8)
我只是简介它。 90%的执行时间都在这一行:
new_mean + =(value - new_mean)/(cnt + 1);
答案 2 :(得分:7)
您应该计算第一个循环中的值,最小值,最大值和计数的总和, 然后通过除以总和/计数来计算一次操作中的平均值, 然后在第二个循环中计算std_dev的总和
那可能会快一点。
答案 3 :(得分:5)
我发现的第一件事是你在循环中评估vals.at(row).size()
,这显然不应该提高性能。它也适用于vals.size()
,但当然内循环更糟。如果vals
是向量的向量,最好使用迭代器或者至少保留外向量的引用(因为带有索引参数的get()
肯定会耗费相当长的时间)。
此代码示例应用于说明我的意图; - )
for(TVO::const_iterator i=vals.begin(),ie=vals.end();i!=ie;++i) {
for(TVI::const_iterator ii=i->begin(),iie=i->end();ii!=iie;++ii) {
T value = *ii;
// the rest
}
}
答案 4 :(得分:4)
答案 5 :(得分:3)
这很慢,因为您正在调试代码。
使用VS2008在Windows XP上构建和运行代码:
在下面的评论中,你说你没有使用优化,这似乎在这种情况下产生很大的不同 - 通常它不到十分之一,但在这种情况下它会慢一百倍。
我使用的是VS2008而不是2005,但它可能类似:
在Debug构建中,每次访问都有两个范围检查,每个范围检查使用非内联函数调用调用std::vector::size()
并需要分支预测。函数调用和分支都有开销。
在Release版本中,编译器优化了范围检查(我不知道它是否只是丢弃它们,或者根据循环的限制进行流量分析),并且向量访问变为少量内联指针算术没有分支。
没有人关心调试版本的速度有多快。无论如何,您应该对发布版本进行单元测试,因为这是必须正常工作的构建。如果您尝试单步执行代码时没有所需的所有信息,请仅使用Debug构建。
发布的代码在&lt;在我的电脑上1.5秒,测试数据为15000 x 10000个整数,均等于42.您报告它的运行速度比它慢230倍。您使用的是10 MHz处理器吗?
虽然还有其他建议可以让它更快(例如将其移动到使用SSE,如果所有值都可以使用8位类型表示),但显然还有其他一些因素会让它变慢。
在我的机器上,既没有提升对行的向量的引用并且提升行的大小的版本,也没有使用迭代器的版本具有任何可测量的好处(使用迭代器的g ++ -O3可重复使用1511ms;悬挂和原始版本都需要1485毫秒)。未优化意味着它以7487毫秒(原始),3496毫秒(悬挂)或5331毫秒(迭代器)运行。
但是,除非你是在一个功耗非常低的设备上运行,或者正在进行分页,或者运行的非优化代码附带调试器,否则它应该不会这么慢,并且无论是什么使它变慢都不太可能是您发布的代码。
(作为旁注,如果您使用偏差为零的值对其进行测试,则SD会显示为1)
答案 6 :(得分:3)
内循环中有太多计算:
用于描述性统计(均值,标准
偏差)唯一需要的是计算总和
值和平方和值之和。从这些
两个总和可以计算平均值和标准差
在外循环之后(与第三个值一起,
样本数量 - n是您的新/更新代码)。该
方程可以从定义中导出或找到
在网上,例如维基百科。例如,平均值是
只是值之和除以n。对于n版本(在
与n-1版本相反 - 但是n很大
这种情况所以使用哪一个并不重要)
标准差为:
sqrt(n * sumOfSquaredValue -
sumOfValue * sumOfValue)。
因此只有两个浮点
需要添加和一个乘法
内循环。溢出不是这些总和的问题
双打的范围是10 ^ 318。特别是你会的
摆脱expensive floating point division那个
另一个答案中报告的分析显示。
一个较小的问题是最小值和最大值是 每次都重写(编译器可能会也可能不会 防止这种情况)。随着最小化迅速变小 最大值很快变大,只有两个比较 应该在大多数循环迭代中发生:使用 if语句而不是确定。可以说,但是 另一方面,它是微不足道的。
答案 7 :(得分:2)
我会改变访问数据的方式。假设您使用std::vector
作为容器,可以执行以下操作:
vector<vector<T> >::const_iterator row;
vector<vector<T> >::const_iterator row_end = vals.end();
for(row = vals.begin(); row < row_end; ++row)
{
vector<T>::const_iterator value;
vector<T>::const_iterator value_end = row->end();
for(value = row->begin(); value < value_end; ++value)
{
double mean_prev = new_mean;
new_mean += (*value - new_mean) / (cnt + 1);
new_standard_dev += (*value - new_mean) * (*value - mean_prev);
// find new max/min's
new_min = min(*value, new_min);
new_max = max(*value, new_max);
cnt++;
}
}
这样做的好处是,在你的内循环中,你不是在咨询外部vector
,而只是在内部。{1}}。
如果容器类型是一个列表,这将明显加快。因为get / operator []的查找时间对于列表是线性的,对于向量是常量。
编辑,我将调用移到了end()
。
答案 8 :(得分:1)
在每个循环之前移动.size()调用,并确保在编译时启用了优化。
答案 9 :(得分:1)
如果矩阵存储为向量矢量,那么在外部for循环中,您应该直接检索第i个向量,然后在内部循环中对其进行操作。试一试,看看它是否能提高性能。
答案 10 :(得分:0)
我不确定vals
是什么类型,但vals.at(row).size()
如果自己遍历集合则需要很长时间。将该值存储在变量中。否则,它可能使算法更像O(n³)而不是O(n²)
答案 11 :(得分:0)
我认为我会重写它以使用const迭代器而不是row和col索引。我会为row_end和col_end设置一个const const_iterator来进行比较,以确保它不会在每个循环结束时进行函数调用。
答案 12 :(得分:0)
正如人们所提到的那样,它可能是get()
。例如,如果它访问邻居,你将完全粉碎缓存,这将大大降低性能。您应该剖析或只考虑访问模式。
答案 13 :(得分:0)
我修改了算法以摆脱几乎所有的浮点除法。
警告:未经检验的代码!!!
void CalculateStats()
{
//OHGOD
double accum_f;
double accum_sq_f;
double new_mean = 0;
double new_standard_dev = 0;
int new_min = 256;
int new_max = 0;
const int oku = 100000000;
int accum_ichi = 0;
int accum_oku = 0;
int accum_sq_ichi = 0;
int accum_sq_oku = 0;
size_t cnt = 0;
int v1 = 0;
int v2 = 0;
v1 = vals.size();
for(size_t row = 0; row < v1; row++)
{
v2 = vals.at(row).size();
for(size_t col = 0; col < v2; col++)
{
T value = get(row, col);
int accum_ichi += value;
int accum_sq_ichi += (value * value);
// perform carries
accum_oku += (accum_ichi / oku);
accum_ichi %= oku;
accum_sq_oku += (accum_sq_ichi / oku);
accum_sq_ichi %= oku;
// find new max/min's
new_min = value < new_min ? value : new_min;
new_max = value > new_max ? value : new_max;
cnt++;
}
}
// now, and only now, do we use floating-point arithmetic
accum_f = (double)(oku) * (double)(accum_oku) + (double)(accum_ichi);
accum_sq_f = (double)(oku) * (double)(accum_sq_oku) + (double)(accum_sq_ichi);
new_mean = accum_f / (double)(cnt);
// standard deviation formula from Wikipedia
stats_standard_dev = sqrt((double)(cnt)*accum_sq_f - accum_f*accum_f)/(double)(cnt);
std::cout << stats_standard_dev << std::endl;
}
答案 14 :(得分:0)
在这里参加派对有点晚了,但有几点:
你在这里有效地做数值工作。我对数值算法知之甚少,但我知道参考和专家支持通常很有用。这个discussion thread提供了一些参考;和数字食谱是一项标准(如果过时)的工作。
如果您有机会重新设计矩阵,您可以尝试使用valarray和切片而不是矢量矢量;立即想到的一个优点是,您可以保证平坦的线性布局,这使得缓存预取和SIMD指令(如果您的编译器可以使用它们)更有效。
答案 15 :(得分:0)
在内部循环中,您不应该测试大小,不应该进行任何划分,并且迭代器也可能是昂贵的。事实上,一些展开会很好。 当然,你应该注意缓存局部性。
如果你的循环开销足够低,那么在单独的传递中进行它可能是有意义的:一个得到总和(你除以得到均值),一个得到平方和(你与之结合)得到方差的总和),得到最小和/或最大的一个。原因是简化了内部展开循环中的内容,因此编译器可以将内容保存在寄存器中。
我无法编译代码,所以我无法确定问题。