Mandelbrot集合的多线程并行计算加速

Mandelbrot集合的多线程并行计算加速

最近研究并行计算,找了CS149的网课看看,顺便做做作业QAQ

代码下载:Mandelbrot集合的多线程并行计算加速代码文件

目录

  • Mandelbrot集合的多线程并行计算加速
    • [1、Mandelbrot 集合介绍](#1、Mandelbrot 集合介绍)
      • [1.1 Mandelbrot 图像的产生过程(计算视角)](#1.1 Mandelbrot 图像的产生过程(计算视角))
        • [1️⃣ 选定计算区域(复平面上的矩形)](#1️⃣ 选定计算区域(复平面上的矩形))
        • [2️⃣ 建立像素网格(离散化)](#2️⃣ 建立像素网格(离散化))
        • [3️⃣ 像素 → 复数点的映射](#3️⃣ 像素 → 复数点的映射)
        • [4️⃣ 对每个点做 Mandelbrot 迭代](#4️⃣ 对每个点做 Mandelbrot 迭代)
        • [5️⃣ 发散判定(阈值判断)](#5️⃣ 发散判定(阈值判断))
        • [6️⃣ 最大迭代次数限制(防止无限循环)](#6️⃣ 最大迭代次数限制(防止无限循环))
        • [7️⃣ 迭代次数 → 像素值](#7️⃣ 迭代次数 → 像素值)
    • 2、Mandelbrot图并行计算
      • [2.1、连续块划分(block / chunk)](#2.1、连续块划分(block / chunk))
      • [2.2、交错划分(cyclic / interleaved)](#2.2、交错划分(cyclic / interleaved))
    • 2.3、性能对比

1、Mandelbrot 集合介绍

对复数z = x + yi:
z n + 1 = z n 2 + c , z 0 = 0 z_{n+1} = z_n^2 + c,\quad z_0 = 0 zn+1=zn2+c,z0=0

如果在不断迭代后 |z| 超过某个阈值(通常是 2),就认为这个点 不属于 Mandelbrot 集

好,这里我按**"图是怎么一步一步算出来的"来讲,用流程化 + 偏实现视角**的描述,正好对应你代码里在做的事(而不是纯数学定义)。


1.1 Mandelbrot 图像的产生过程(计算视角)

1️⃣ 选定计算区域(复平面上的矩形)

首先,在复平面中选一个有限的矩形区域作为"观察窗口":

复制代码
x ∈ [x0, x1]
y ∈ [y0, y1]

这个矩形决定了你最终看到的是 Mandelbrot 集的哪一块(缩放、平移本质上就是在改这个矩形)。


2️⃣ 建立像素网格(离散化)

接着,指定输出图像大小:

复制代码
width  ×  height

这一步的含义是:

把连续的复平面矩形 离散成一个规则网格,每个网格点对应一个像素。

由此得到步长:

复制代码
dx = (x1 - x0) / width
dy = (y1 - y0) / height

3️⃣ 像素 → 复数点的映射

对图像中的每一个像素 (i, j)

复制代码
x = x0 + i * dx
y = y0 + j * dy
c = x + yi

也就是说:
每个像素都代表复平面中的一个复数 c


4️⃣ 对每个点做 Mandelbrot 迭代

对当前复数点 c,执行如下迭代:

复制代码
z0 = 0
zn+1 = zn² + c

这是一个局部、互不依赖的计算过程,因此非常适合并行。


5️⃣ 发散判定(阈值判断)

在迭代过程中,每一步都会检查:

复制代码
|zn| > threshold ?
  • 通常 threshold = 2
  • 一旦超过阈值,就认为该点 已经发散
  • 记录此时的迭代次数 n

6️⃣ 最大迭代次数限制(防止无限循环)

为了避免无限计算,引入最大迭代次数,例如:

复制代码
maxIterations = 255

规则是:

  • 如果在 255 次迭代内发散
    → 返回实际发散的迭代次数
  • 如果 迭代 255 次仍未发散
    → 停止迭代,认为该点"属于 Mandelbrot 集内部或边界附近"

7️⃣ 迭代次数 → 像素值

最终:

复制代码
output[j * width + i] = iteration_count

这个值通常被用于:

  • 直接作为灰度值
  • 或作为调色函数的输入(生成彩色分形图)

2、Mandelbrot图并行计算

2.1、连续块划分(block / chunk)

每个线程拿一整块连续的行

复制代码
行号:   0  1  2  3  4  5  6  7  8  9 10 11
        ───────────────────────────────
线程0:  █  █  █  █
线程1:              █  █  █  █
线程2:                          █  █  █  █

等价于:

  • thread 0 → 行 0,1,2,3
  • thread 1 → 行 4,5,6,7
  • thread 2 → 行 8,9,10,11
特点

✅ cache 友好

✅ 实现简单

❌ 如果不同区域算得"慢/快"不均,会负载不平衡

代码
cpp 复制代码
void workerThreadStartBlock(WorkerArgs * const args) {

    int tid = args->threadId;
    int T   = args->numThreads;
    int H   = (int)args->height;

    int rowsPerThread = H / T;
    int remainder     = H % T;

    int startRow = tid * rowsPerThread + (tid < remainder ? tid : remainder);
    int numRows  = rowsPerThread + (tid < remainder ? 1 : 0);

    if (numRows <= 0) return;

    mandelbrotSerial(
        args->x0, args->y0, args->x1, args->y1,
        (int)args->width, (int)args->height,
        startRow, numRows,
        args->maxIterations,
        args->output
    );
}

2.2、交错划分(cyclic / interleaved)

行号按线程号取模

规则:

复制代码
行 j 交给线程 (j % T)

示意图
复制代码
行号:   0  1  2  3  4  5  6  7  8  9 10 11
        ───────────────────────────────
线程0:  █        █        █        █
线程1:     █        █        █        █
线程2:        █        █        █        █

等价于:

  • thread 0 → 行 0, 3, 6, 9
  • thread 1 → 行 1, 4, 7, 10
  • thread 2 → 行 2, 5, 8, 11

这就是你看到的那句话:

线程 t 算 t, t+T, t+2T... 行

代码
cpp 复制代码
void workerThreadStartCyclic(WorkerArgs * const args) {

    int tid = args->threadId;
    int T   = args->numThreads;
    int H   = (int)args->height;

    // Interleaved partitioning: Thread tid is responsible for row tid, tid+T, tid+2T, ...
    for (int j = tid; j < H; j += T) {
        mandelbrotSerial(
            args->x0, args->y0, args->x1, args->y1,
            (int)args->width, (int)args->height,
            j,                 // startRow
            1,                 // numRows
            args->maxIterations,
            args->output
        );
    }
}

2.3、性能对比

运行代码结果如下:

bash 复制代码
[mandelbrot serial]:            [692.776] ms
Wrote image file mandelbrot-serial.ppm
[mandelbrot thread block]:              [92.090] ms
Wrote image file mandelbrot-thread.ppm
[mandelbrot thread cyclic]:             [49.905] ms
                                (7.52x speedup: serial vs thread block, 16 threads)
                                (13.88x speedup: serial vs thread cyclic, 16 threads)

实际上交错划分性能更好

简单来说,Mandelbrot 图像中,不同位置的点发散速度不同,导致不同图像行的计算量不均匀

block(连续块)划分 中,复杂区域的多行可能集中分配给某一个线程,使该线程成为瓶颈,其他线程空闲等待,从而降低整体性能。

cyclic(交错)划分将不同行均匀分配给各个线程,把计算量较大的区域打散到多个线程中,显著改善负载均衡,因此整体执行时间更短、加速比更高。

相关推荐
zmzb01032 小时前
C++课后习题训练记录Day91
开发语言·c++
老鼠只爱大米2 小时前
LeetCode经典算法面试题 #114:二叉树展开为链表(递归、迭代、Morris等多种实现方案详细解析)
算法·leetcode·二叉树·原地算法·morris遍历·二叉树展开
今儿敲了吗2 小时前
07| 高精度除法
c++
Ivanqhz2 小时前
现代异构高性能计算(HPC)集群节点架构
开发语言·人工智能·后端·算法·架构·云计算·边缘计算
HyperAI超神经2 小时前
覆盖天体物理/地球科学/流变学/声学等19种场景,Polymathic AI构建1.3B模型实现精确连续介质仿真
人工智能·深度学习·学习·算法·机器学习·ai编程·vllm
执着2593 小时前
力扣hot100 - 144、二叉树的前序遍历
数据结构·算法·leetcode
范纹杉想快点毕业3 小时前
嵌入式系统架构之道:告别“意大利面条”,拥抱状态机与事件驱动
java·开发语言·c++·嵌入式硬件·算法·架构·mfc
陳10303 小时前
C++:map和set的使用
开发语言·c++
苏宸啊3 小时前
list底层实现
c++·list