2.1.1 数据并行化和自动算子并行化(AOP)

数据并行化意味着多个线程对不同的数据执行相同的任务。

数据并行化的概念如 图 2.1 所示。

图 2.1: 利用数据并行化最小化响应时间。在这里,中值滤波器同时应用于图像的不同部分。

首先,数据被分割成多个大小大致相同的数据块。然后,由不同的线程分别对每个数据块执行任务。最后,在最后一个线程完成后,将所有线程的结果连接起来。

默认情况下,几乎所有算子都会自动执行数据并行化。 ! 需要注意的是,在很多情况下,HALCON 的自动算子并行化 (AOP) 已经足够高效,因此手动执行数据并行化并不会带来进一步的运行时改进。为了解释这一概念,下面将以中值滤波器为例,说明如何手动实现数据并行化。

2.1.1.1 示例: 模拟 AOP

HDevelop 示例程序 hdevelop/System/Parallelization/simulate_aop.hdev 展示了如何实现中值滤波器的数据并行化(另见 图 2.1),并将运行时间与顺序处理和 HALCON 的 AOP 所需的运行时间进行了比较。

要使用 AOP,系统参数 "parallelize_operators" 必须设置为 "真" (默认值)。使用的线程数可以通过将系统参数 "thread_num" 设置为介于 1 和可用内核数之间的数值来明确设置。默认情况下,所有可用内核都用于 AOP。

set_system ('parallelize_operators', 'true')
median_image (Image, ImageMedianAop, 'circle', Radius, 'mirrored')

下面的代码展示了通过模拟 AOP 实现数据并行化的过程。由于需要模拟 AOP,因此首先要关闭 AOP。然后,将图像分割成大小大致相同的片段。具体做法是将各个线程要处理的图像域缩小到输入图像域的一个子区域。使用限定符 par_start(另见 HDevelop 用户指南),启动并行运行的各个线程。在每个线程中,计算各自图像部分的中值。不同线程的结果将通过向量变量收集(另见 HDevelop 用户指南)。

get_system ('processor_num', Cores)
MaxNumberOfSubthreads := 20
MaxThreads := min([Cores,MaxNumberOfSubthreads])
set_system ('parallelize_operators', 'false')
for Threads := 1 to MaxThreads by 1
        get_region_runs (Image, Row, ColumnBegin, ColumnEnd)
        RunsPerThread := |Row| / Threads
        * Split up the image and process each region by a separate thread:
        for Thread := 0 to Threads - 1 by 1
            IndexStart := Thread * RunsPerThread
            if (Thread < Threads - 1)
                IndexEnd := IndexStart + RunsPerThread - 1
            else
                IndexEnd := |Row| - 1
            endif
            gen_region_runs (Region, Row[IndexStart:IndexEnd], \
                             ColumnBegin[IndexStart:IndexEnd], \
                             ColumnEnd[IndexStart:IndexEnd])
            reduce_domain (Image, Region, ImageReduced)
            par_start<ThreadID.at(Thread)> : median_image (ImageReduced, \
                                  ImageMedianThread.at(Thread), 'circle', \
                                  Radius, 'mirrored')
        endfor
        convert_vector_to_tuple (ThreadID, ThreadIDs)
endfor

为了等待所有参与的线程结束,会调用算子 par_join。然后,各个线程的结果会被复制到一个图像中。

par_join (ThreadIDs)
* Free references to thread IDs.
ThreadID.clear()
ThreadIDs := []
* Merge results
full_domain (ImageMedianThread.at(0), ImageMedianPar)
for ImagePart := 1 to Threads - 1 by 1
    overpaint_gray (ImageMedianPar, ImageMedianThread.at(ImagePart))
endfor

图 2.2 展示了4个内核的机器在不同情况下的运行时间。与算子自动并行化相比,手动并行化没有优势。由于 HDevelop 带来的一些开销,模拟 AOP 处理的运行时间甚至略大于 AOP 处理的运行时间。但两者都明显快于顺序处理。

图 2.2: 在不使用 AOP、使用 AOP 和使用并行编程模拟 AOP 的情况下,在具有 4 个内核/8 个硬件线程的机器上应用中值滤波器的运行时间。