在某个阈值后,Std::vector 填充时间从 0ms 变为 16ms?
Posted
技术标签:
【中文标题】在某个阈值后,Std::vector 填充时间从 0ms 变为 16ms?【英文标题】:Std::vector fill time goes from 0ms to 16ms after a certain threshold? 【发布时间】:2010-07-11 19:47:45 【问题描述】:这就是我正在做的事情。我的应用程序在拖动时从用户那里获取点,并实时显示填充的多边形。
它基本上是在 MouseMove 上添加鼠标位置。这个点是一个 USERPOINT 并且有 bezier 句柄,因为最终我会做 bezier,这就是为什么我必须将它们转移到向量中。
所以基本上是 MousePos -> USERPOINT。 USERPOINT 被添加到 std::vector<USERPOINT>
。然后在我的 UpdateShape() 函数中,我这样做:
DrawingPoints 是这样定义的:
std::vector<std::vector<GLdouble>> DrawingPoints;
Contour[i].DrawingPoints.clear();
for(unsigned int x = 0; x < Contour[i].UserPoints.size() - 1; ++x)
SetCubicBezier(
Contour[i].UserPoints[x],
Contour[i].UserPoints[x + 1],
i);
SetCubicBezier() 目前看起来像这样:
void OGLSHAPE::SetCubicBezier(USERFPOINT &a,USERFPOINT &b, int ¤tcontour )
std::vector<GLdouble> temp(2);
if(a.RightHandle.x == a.UserPoint.x && a.RightHandle.y == a.UserPoint.y
&& b.LeftHandle.x == b.UserPoint.x && b.LeftHandle.y == b.UserPoint.y )
temp[0] = (GLdouble)a.UserPoint.x;
temp[1] = (GLdouble)a.UserPoint.y;
Contour[currentcontour].DrawingPoints.push_back(temp);
temp[0] = (GLdouble)b.UserPoint.x;
temp[1] = (GLdouble)b.UserPoint.y;
Contour[currentcontour].DrawingPoints.push_back(temp);
else
//do cubic bezier calculation
因此,出于三次贝塞尔曲线的原因,我需要将 USERPOINTS 转换为 GlDouble[2](因为 GLUTesselator 采用静态双精度数组。
所以我做了一些分析。在 ~ 100 点,代码:
for(unsigned int x = 0; x < Contour[i].UserPoints.size() - 1; ++x)
SetCubicBezier(
Contour[i].UserPoints[x],
Contour[i].UserPoints[x + 1],
i);
执行耗时 0 毫秒。然后在 120 左右,它跳到 16ms 并且永不回头。我很肯定这是由于 std::vector。我该怎么做才能让它保持在 0ms。我不介意在生成形状时使用大量内存,然后在形状完成时删除多余的内存,或者类似的东西。
【问题讨论】:
您是否尝试过将性能与使用new
手动动态分配类似大小的数组进行比较?
我想用一个容器,因为它们很方便
@Jex:是的,同意,但衡量完成任务的几种方法的性能总是一个好主意。如果您将某些性能特征归咎于vector
,您应该将性能与自己分配内存进行比较,看看效果如何;这可以帮助您缩小瓶颈的范围。
看起来temp
的大小永远不会超过 4。如果是这样,自动数组会快得多。
@Jex:实际上,list
可以说是更糟糕的容器。 :) 它不会执行得更快,除非您经常从中间插入和移除东西。
【参考方案1】:
0ms 没有时间......没有任何事情可以立即执行。这应该是您可能想要检查计时方法而不是计时结果的第一个指标。
也就是说,计时器通常没有很好的分辨率。您的 16 毫秒前结果实际上可能只是 1 毫秒 - 15 毫秒被错误地报告为 0 毫秒。无论如何,如果我们能告诉你如何将它保持在 0 毫秒,我们就会变得富有和出名。
相反,找出循环的哪些部分花费的时间最长,然后优化它们。不要朝着任意的时间度量工作。我建议使用一个好的分析器来获得准确的结果。然后你不需要猜测什么是慢的(循环中的东西),而是可以实际看到什么部分是慢的。
【讨论】:
@Jex:或者您的算法导致分配过多。解决这些问题的方法不止一种。我要让我的答案是 CW,因为它更多地是关于时间,而不是关于优化。 “如果我们能告诉你如何将它保持在 0 毫秒,我们就会变得富有和出名” - 非常喜欢。 @Jex 或者你不知道你是什么东 - 这甚至可能是模糊的可能吗?寻找除了自己以外的东西来责备,这不是一个好工匠会做的事情。 Windows 计时器(GetTickCount
使用)的分辨率只有 15-16 毫秒,这可以解释结果。【参考方案2】:
您可以使用vector::reserve()
来避免DrawingPoints
中不必要的重新分配:
Contour[i].DrawingPoints.reserve(Contour[i].size());
for(unsigned int x = 0; x < Contour[i].UserPoints.size() - 1; ++x)
...
【讨论】:
这没有帮助 :-( 它仍然很慢【参考方案3】:如果您实际上仅对第二个代码 sn-p 计时(如您在帖子中所述),那么您可能只是从向量中读取。这意味着,原因不能是向量的重新分配成本。在这种情况下,可能是由于 CPU 的缓存问题(即可以以闪电般的速度从 cpu 缓存中读取小数据集,但是当数据集大于缓存时[或从不同的内存位置交替读取时],cpu必须访问 ram,这明显比缓存访问慢)。
如果您分析的代码部分将数据附加到向量,则在填充之前使用具有适当容量(向量中预期条目数)的 std::vector::reserve()。
但是,请注意分析/基准测试的两个一般规则:
1) 使用具有高分辨率精度的时间测量方法(正如其他人所说,您的计时器的分辨率太低)
2) 在任何情况下,多次运行代码 sn-p(例如 100 次),得到所有运行的总时间,然后除以运行次数。这会给你一些真实的数字。
【讨论】:
100 次是不够的。至少尝试 10,000 次,如果仍然难以察觉,请尝试 100 万次。 我做了 50 次,结果:(/50)100 分:2.36、150:4.36、250:9.4 @Jex:让我们看看,250x2xsizeof(double)=4000 字节,这低于典型的缓存行大小,但可能仍然太大。在这里进行详细的分析会超出空间和时间;-)。但您可以尝试使用单个向量,而不是每个轮廓一个向量(这提高了局部性并使 cpu 更容易管理 ram 访问) @frunsi:什么平台有 4000 字节的缓存线? @james:未来闪亮的新平台!?不,我的错,我的意思是缓存大小(不是缓存行大小)。【参考方案4】:这里有很多猜测。我想,猜测不错,但还是猜测。当你试图测量函数所花费的时间时,这并不能告诉你它们是如何使用它的。您可以看到如果您尝试不同的事情,时间会发生变化,并且您可以从中得出一些关于花费时间的建议,但您无法确定。
如果你真的想知道什么在花时间,你需要在它花时间的时候抓住它,并确定它在做什么。一种方法是通过该代码在指令级别单步执行它,但我怀疑这是不可能的。下一个最好的方法是获取堆栈样本。您可以找到基于堆栈样本的分析器。就个人而言,我依赖手动技术,for the reasons given here。
请注意,这并不是真正意义上的时间测量。这是关于找出为什么要花费额外的时间,这是一个非常不同的问题。
【讨论】:
以上是关于在某个阈值后,Std::vector 填充时间从 0ms 变为 16ms?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 C++11 会从 std::vector 填充构造函数的原型中移除默认值?