C++绘图Mandlebrot集,性能不好
C++ plotting Mandlebrot set, bad performance
我不确定是否有实际的性能提升,或者我的电脑是不是又旧又慢,但我还是会问的。
因此,我尝试制作一个程序,使用cairo库绘制Mandelbrot集。
绘制像素的循环如下所示:
vector<point_t*>::iterator it;
for(unsigned int i = 0; i < iterations; i++){
it = points->begin();
//cout << points->size() << endl;
double r,g,b;
r = (double)i+1 / (double)iterations;
g = 0;
b = 0;
while(it != points->end()){
point_t *p = *it;
p->Z = (p->Z * p->Z) + p->C;
if(abs(p->Z) > 2.0){
cairo_set_source_rgba(cr, r, g, b, 1);
cairo_rectangle (cr, p->x, p->y, 1, 1);
cairo_fill (cr);
it = points->erase(it);
} else {
it++;
}
}
}
这个想法是给所有刚刚脱离集合的点涂上颜色,然后将它们从列表中删除,以避免再次评估它们。
它确实正确地渲染了集,但渲染所需的时间似乎比需要的要长得多。
有人能发现循环的任何性能问题吗?还是它已经很好了?
提前感谢:)
解决方案
非常好的答案,谢谢:)-我最终得到了一种混合答案。考虑到建议,我意识到计算每个点,将它们放在向量中,然后提取它们是对CPU时间和内存的巨大浪费。因此,该程序现在只计算每个点的Z值,即使使用point_t或向量也是如此。它现在跑得快多了!
编辑:我认为kuroi-neko的答案中的建议也是一个非常好的主意,如果你不关心"增量"计算,但有固定的迭代次数。
-
您应该使用
vector<point_t>
而不是vector<point_t*>
。CCD_ 3是指向CCD_ 4的指针的列表。每个点都存储在存储器中的某个随机位置。如果对这些点进行迭代,那么访问内存的模式看起来完全是随机的。你会得到很多缓存未命中。
相反,
vector<point_t>
使用连续内存来存储点。因此,下一个点直接存储在当前点之后。这允许高效的缓存。 -
您不应该在内部循环中调用
erase(it);
。每次对
erase
的调用都必须将所有元素移动到您删除的元素之后。这具有O(n)运行时。例如,您可以向point_t
添加一个标志,指示不应再对其进行处理。在每次迭代后删除所有"非活动"点可能会更快。 -
使用
cairo_rectangle
绘制单个像素可能不是一个好主意。我建议您创建一个图像并存储每个像素的颜色。然后用一个draw调用绘制整个图像。
你的代码可能是这样的:
for(unsigned int i = 0; i < iterations; i++){
double r,g,b;
r = (double)i+1 / (double)iterations;
g = 0;
b = 0;
for(vector<point_t>::iterator it=points->begin(); it!=points->end(); ++it) {
point_t& p = *it;
if(!p.active) {
continue;
}
p.Z = (p.Z * p.Z) + p.C;
if(abs(p.Z) > 2.0) {
cairo_set_source_rgba(cr, r, g, b, 1);
cairo_rectangle (cr, p.x, p.y, 1, 1);
cairo_fill (cr);
p.active = false;
}
}
// perhaps remove all points where p.active = false
}
如果不能更改point_t
,则可以使用额外的vector<char>
来存储点是否已变为"非活动"。
Zn发散计算使算法速度变慢(当然,这取决于您正在处理的区域)。相比之下,像素绘制只是背景噪声。
您的循环存在缺陷,因为它使Zn计算速度变慢。
方法是在一个紧凑、优化的循环中计算每个点的散度,然后处理显示。
此外,永久存储Z是无用和浪费的
您只需要C作为输入,迭代次数作为输出。
假设你的points
数组只包含C值(基本上你不需要所有这些向量垃圾,但它也不会影响性能),你可以这样做:
for(vector<point_t>::iterator it=points->begin(); it!=points->end(); ++it)
{
point_t Z = 0;
point_t C = *it;
for(unsigned int i = 0; i < iterations; i++) // <-- this is the CPU burner
{
Z = Z * Z + C;
if(abs(Z) > 2.0) break;
}
cairo_set_source_rgba(cr, (double)i+1 / (double)iterations, g, b, 1);
cairo_rectangle (cr, p->x, p->y, 1, 1);
cairo_fill (cr);
}
试着在有和没有cairo的情况下运行它,你应该看不到执行时间的明显差异(除非你看到的是集合的一个空位)。
现在,如果你想走得更快,试着将Z=Z*Z+C计算分解为实部和虚部并进行优化。你甚至可以使用mmx或其他方法进行并行计算。
当然,获得另一个重要速度因素的方法是在可用的CPU核心上并行化算法(即,将显示区域划分为子集,并让不同的工作线程并行计算这些部分)。
不过,这并不像看上去那么明显,因为每个子图片都有不同的计算时间(黑色区域的计算速度非常慢,而白色区域几乎是即时计算的)
一种方法是分割大量矩形区域,并让所有工作线程从公共池中随机选择一个矩形,直到所有矩形都得到处理
这个简单的负载平衡方案可以确保当它的伙伴在显示器的其他部分忙碌时,没有CPU核心空闲。
优化性能的第一步是找出慢的地方。您的代码混合了三个任务——迭代以计算点是否逃逸,操纵要测试的点向量,以及绘制点。
将这三项行动分开,并衡量它们的贡献。您可以通过使用simd运算将转义计算并行化来优化转义计算。如果你想删除矢量,你可以通过不从矢量中擦除来优化矢量操作,但如果你想保留它,你可以将它添加到另一个矢量中(因为擦除是O(N)和加法O(1)),并通过使用点的矢量而不是指向点的指针来提高局部性,如果绘图速度较慢,则使用屏幕外位图,并通过操作后备内存设置点,而不是使用cairo函数。
(我本来打算发布这篇文章,但@Werner Henze已经在评论中提出了同样的观点,因此社区wiki)
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- OpenMP阵列性能较差
- 递归列出所有目录中的C++与Python与Ruby的性能
- 大小相等但成员数量不同的结构之间的性能差异
- 为什么constexpr的性能比正常表达式差
- 在类中使用随机生成器时出现性能问题
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 海湾合作委员会 ARM 性能下降
- GCC 和 Clang 代码性能的巨大差异
- 在容量内调整矢量大小时的性能影响
- 了解算法的性能差异(如果以不同的编程语言实现)
- 未达到的情况会影响开关外壳性能
- QStringList vs list<shared_ptr<QString>> 性能比较C++
- 是否总是可以将使用递归编写的程序重写为不使用递归的程序C++,性能观点是什么?
- 哪种方法更好,性能明智
- C++ 特征库:引用的性能开销<>
- 与多个 for 循环与单个 for 循环 wrt 相关的性能从多映射获取数据
- 基于范围的 for 循环range_declaration中各种说明符之间的性能差异
- std::p mr::memory_resource 如何与 std::container 产生性能差异?
- C++绘图Mandlebrot集,性能不好