写点什么

两种不同的并行

作者:Miracle
  • 2025-10-01
    四川
  • 本文字数:1516 字

    阅读完需:约 5 分钟

Mandelbrot 集合是一种神奇的分形结构,这种算法跟我们之前实现的 YUV 和 RGB 的转换有着本质的不同,我把它叫做均匀并行和非均匀并行,YUV 和 RGB 转换就是典型的均匀并行,在整个图像中,每个点的计算量是完全一样的,同样的算法步骤,最多有一些简单的分支,计算量没有很大的差别,

而 Mandelbrot 集合的计算则是完全不一样的典型场景,临近的两个点之间计算的次数差异非常大,如果距离稍微远一些,那计算量的差异就更恐怖了。

从数学上来说,利用逃逸次数作图,这种计算量的巨大差距正是 Mandelbrot 集合呈现出如此多变的细节的根本原因。

那么我们简单的 按照 dispatch 的 x y 坐标分配任务的 方式对于 Mandelbrot 这类非均匀并行的场景来说就很不适用了,对于小的计算量无所谓,对于大的计算量来说,就会出现次数很少的线程计算完毕了,还需要等待计算次数非常大的线程完成计算,这样 GPU 的算力利用率就会非常低。

对于这种类似的情况,我们需要 进行任务分配的机制。


spirv 的 workgroup 线程就是 i 针对这种场景而设置的

所谓的 workgroup 共享 数据就是 一个工作组内共享的独立数据,读写非常快,而且支持原子操作。

所以我们用下面的方式来实现 任务的分配

static task_mgr: u32;
复制代码

这是一个 任务获取的 workgroup 共享变量


pub fn main(params: Params, buf: Vec<u32>) {    let wrk = spirv_group(); //当前工作组的 id 这个数量由 unsafe { builder.dispatch([32, 32, 1]) }.unwrap(); 确定    let cx = wrk[0];    let cy = wrk[1];    let wrk_id = (cy * 32 + cx) as u32;    let cent_x = params.x + ((cx + 0.5) * 32 - 512) * params.step;    let cent_y = params.y + (512 - (cy + 0.5) * 32) * params.step;    let local_id = task_mgr.atomic_add();    while local_id < 1024u32 {        let local_x = local_id % 32;        let local_y = (local_id / 32) as u32;        let x = cent_x + local_x * params.step - 32 * params.step;        let y = cent_y + 32 * params.step - local_y * params.step;        let esc = escape(x, y, params.max_iter);        let pos = (cy * 32 + local_y) * 1024 + cx * 32 + local_x;        buf[pos] = esc;        local_id = task_mgr.atomic_add();    }}
复制代码

我们用上面的代码来完成 Mandelbrot 集合的计算

注意 我们使用 task_mgr.atomic_add();获取一个任务 ID 最多 1024 个

这样 我们把 32x32 块的计算 分配在一个原子变量上

不管每个组 启动多少个线程,可以远小于 1024


这些线程就像勤劳的老黄牛一样 一直通过原子操作获取 任务 ID 一直到 超过 1024

这样对于小的任务区别不大,但是如果 特别繁重的 计算任务 GPU 线程可以跑到死!!

我们用下面的代码启动 Shader

        println!("{:?}", b.import_module(&mut module, [32, 1, 1]));        let spirv_code = b.assemble();               let mut rt = spirv::Runtime::default();        let mut args = rt.get_args();        args.add_input(Params{x: -0.7454f64, y: 0.1103f64, step: 0.00000004f64, max_iter:12000});        let img = args.add_output_vec::<u32>(1024 * 1024);        rt.prepare(&spirv_code, args)?;        let start = std::time::Instant::now();        rt.run([32, 32, 1])?;        let elaspe = std::time::Instant::now() - start;        println!("use {}", elaspe.as_micros());        save_mandelbrot_png(&img.read()?, 1024, 1024, 12000, "mand.png")?;
复制代码

大家看到 我们 启动了 32 个线程每组,如果任务繁重,这就是更好的方案了

按照这个坐标计算出来的图像是这样的


完美!

用户头像

Miracle

关注

三十年资深码农 2019-10-25 加入

还未添加个人简介

评论

发布
暂无评论
两种不同的并行_Miracle_InfoQ写作社区