编程模型:线程级并行技术的深入解析
在当今的计算领域,提高程序执行效率和性能是至关重要的目标。为了实现这一目标,人们不断探索各种编程模型和技术,其中线程级并行技术是一个重要的研究方向。本文将深入探讨线程级推测(TLS)、辅助线程等编程模型,以及它们在提高程序性能方面的应用。
1. 线程级推测(TLS)概述
传统的软件代码并行化通常需要开发者使用编译器指令注释代码,或者使用线程包(如Pthreads)编写并行代码。事务性内存(TM)也是一种显式的并行编程模型,它通过事务来处理非数值应用中复杂的线程间通信模式,避免了使用锁,在很多情况下提高了可编程性和性能。然而,即使有TM的支持,程序员仍然需要手动识别线程并标记事务边界,这使得显式并行化在实践中变得复杂。
线程级推测(TLS)则是一种自动并行化顺序程序的技术,它只需要程序员识别程序中可能花费大量执行时间的区域,如循环、嵌套子例程或递归函数调用,将其作为推测并行化的候选区域,其余工作由TLS的软件和硬件完成。
2. 线程级并行的主要来源
在常规的顺序代码中,线程级并行(TLP)的主要来源包括嵌套子例程、递归函数调用和循环。其中,循环迭代的并行化相对更容易实现,因为子例程和函数调用通常会返回调用者所需的值,这使得并行化变得困难。如果调用者在为子例程创建线程后立即继续执行,它需要预测子例程的返回值,或者在需要子例程输出时停止执行。因此,并行化编译器通常更关注循环。
3. 循环并行化的挑战
循环迭代的并行化主要受到真正的循环携带数据依赖的阻碍。图8.18展示了三个具有不同并行化难度的循环:
-
图8.18(a)
:for循环中的语句没有循环携带依赖,并行性仅受循环迭代次数的限制。
for(i:=m;i<M;+++)
{
...
A[i]:=A[i]+B[i]...
...
}
- 图8.18(b) :对数组A的访问存在递归关系,将并行性限制为四个循环迭代。循环迭代必须以四个为一组执行,最大并行加速比为四。
for(i:=m;i<M;+++)
{
...
A[i]:=A[i-4]+B[i]...
...
}
- 图8.18(c) :在编译时无法并行化,因为数组A的索引j在编译时未知,它包含在一个可能动态修改的数组中。某些迭代中,索引j的值可能小于索引i的值,这会导致并行执行循环迭代时出现RAW(读后写)危险。
for(i:=m;i<M;+++)
{
...
j:=C[i]
A[i]:=A[j]+B[i]
...
}
4. 并行循环代码中的内存危险
图8.19展示了并行执行循环迭代时可能出现的内存危险,包括RAW、WAW(写后写)和WAR(写后读)危险。
Load A
Store B
Load B
Store A
Load A
Store B
Store A
Load B
Load A
Store B
Store A
Load B
Load A
Store B
Store A
Load B
Load A
Store B
Store A
Load B
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 1
Time
Time
...
WAW
RAW
WAR
(a)
(b)
Thread 1
Iteration 2
Thread 2
Iteration 3
Thread 3
Iteration 4
Thread 4
...
Load A
Store B
Load B
Store A
...
...
Load A
Store B
Load B
Store A
...
...
Load A
Store B
Load B
Store A
...
...
下面是这些危险的具体分析:
| 危险类型 | 描述 | 解决方法 |
| ---- | ---- | ---- |
| RAW | 后一个迭代读取了前一个迭代尚未写入的值,如线程2的Load A在线程1的Store A之前执行 | 延迟线程2,使Load等待Store,或在检测到违规后终止并重新执行线程2 |
| WAW | 多个线程对同一变量进行写操作,可能导致写入顺序混乱 | 确保写入操作按循环索引顺序执行 |
| WAR | 一个线程在另一个线程写入之前读取了变量,可能导致读取到旧值 | 延迟读取操作或重新执行受影响的线程 |
5. 循环的推测并行化
TLS主要针对那些循环携带依赖较少且在编译时难以预测的循环,如上述图8.18(c)中的循环。为了保证正确性,循环的指令必须按循环索引顺序执行。每个线程被分配一个序列号,最老的线程(头线程)序列号最低,其他线程都是推测性的,因为它们可能与仍在处理迭代的老线程存在RAW危险。当检测到RAW危险时,推测性线程必须被终止。
头线程必须先将所有结果提交到内存,下一个线程才能变为非推测性线程。线程完成后,其线程上下文可用于新的推测性线程。
6. 线程级推测的基本硬件支持
为了支持TLS,每个线程上下文配备了以下额外状态:
-
线程序列号寄存器
:用于标识线程的顺序。
-
推测活动位(SA)
:线程变为推测性时设置,变为非推测性时重置,对处理线程的每个内存访问至关重要。
-
推测失败位(SF)
:推测性线程开始时重置,检测到依赖违规时设置。当推测性线程完成执行后,等待成为头线程,然后检查SF位。如果SF位设置,则推测失败,线程终止所有序列号更高的后续线程,并以非推测性线程重新启动;如果SF位重置,则推测成功,线程变为非推测性,提交结果并退出。
为了解决RAW危险,TLS利用了大多数CMP常见的缓存层次结构。L1缓存存储推测值,共享L2缓存仅保存已提交(非推测性)的值。WAW和WAR危险通过在L1缓存中重命名内存来自动解决。
此外,为了实现TLS并处理L1缓存中的推测值,对MSI协议进行了扩展,增加了Squash总线请求。当对推测性缓存块进行存储命中时,会向所有L1缓存发送Squash总线请求,并携带线程序列号,以便每个缓存判断自己的推测性程度。当推测性线程收到序列号较低的线程的Squash请求时,设置其SF位,终止线程,并终止所有后续线程。
7. 线程级推测的优化
TLS的基本硬件支持相对简单,它主要针对具有大且不可预测依赖距离的真正循环携带依赖。当依赖中的两个内存访问地址很少(或可能永远)相等时,TLS可以成功并行化。此外,TLS可以与循环迭代之间的同步结合使用,以消除危险。
当地址已知相同或大部分时间相同时,且依赖距离较短,编译器可以尝试避免危险或施加同步来消除危险。例如,将循环索引的更新从循环体中移除,使其成为推测性线程分叉机制的一部分。
还有许多其他优化方法,如推测性线程违规时立即重启,而不是等待完全执行并变为非推测性;更高级的优化是只终止受RAW依赖违规影响的推测性线程部分,并仅重新执行受影响的部分。
8. 辅助线程
TM和TLS都依赖大量的硬件支持来检测线程之间的冲突访问,并终止错误推测的线程。为了简化这种复杂性,可以让推测性线程不进行任何架构状态修改,非推测性线程负责执行程序,推测性线程仅辅助程序执行,这些线程通常被称为辅助线程。
数据预取是辅助线程最常见的应用。数据预取在应用程序需要数据之前,将数据从内存推测性地加载到缓存中。为了有效地进行数据预取,辅助线程需要在应用程序获取数据之前提前知道数据访问地址,并且预取的地址必须精确,否则预取将被浪费。
大多数预取机制是硬连线、简单且自动的,但对于具有不可预测数据访问行为的应用程序,如数据库、多媒体和游戏,这些机制效果不佳。辅助线程可以预先计算这些难以预测的地址,其预取算法可以根据每个数据访问进行定制。
为了预先计算加载地址,与加载关联的辅助线程执行导致加载地址的程序指令子集,即加载的反向切片。编译器在编译时生成反向切片,并将其存储为应用程序二进制的一部分。编译器还可以指定何时启动辅助线程,并将切片的输入寄存器作为指令提供给运行时环境。辅助线程硬件根据编译器指定的指令决定是否启动辅助线程。
综上所述,线程级推测和辅助线程等编程模型为提高程序的并行性和性能提供了有效的方法。通过合理利用这些技术,可以更好地应对现代应用程序对计算资源的需求。
编程模型:线程级并行技术的深入解析
9. 辅助线程的数据预取流程
辅助线程在数据预取方面发挥着重要作用,下面详细介绍其工作流程:
1.
确定需预取的加载操作
:编译器通过性能分析,识别出那些可能发生缓存缺失的加载操作,即延迟加载。这些加载操作通常是在应用程序中频繁出现且容易导致性能瓶颈的部分。
2.
生成反向切片
:从延迟加载指令开始,编译器通过回溯程序的依赖图,找出计算加载地址所需的最接近的前序指令,形成反向切片。例如,对于指令
Load R1, 0(R2)
,编译器会回溯找到定义
R2
的指令,再继续回溯定义
R2
操作数的指令,以此类推,直到找到所有必要的指令。
3.
存储反向切片和指令
:将生成的反向切片存储为应用程序二进制的一部分,并在代码中插入特殊指令,指定何时启动辅助线程以及反向切片的输入寄存器。例如,在代码中的某个位置插入指令,指示在特定条件下启动辅助线程,并将相关寄存器的值作为输入传递给反向切片。
4.
运行时决策
:辅助线程硬件根据编译器指定的指令,判断是否启动辅助线程。如果满足启动条件,辅助线程开始执行反向切片。
5.
预取数据
:辅助线程执行反向切片,计算出加载地址,并提前将数据从内存预取到缓存中。这样,当主线程执行到加载操作时,数据已经在缓存中,减少了等待时间,提高了程序性能。
下面是一个简单的示例,展示了辅助线程的工作原理:
Thread 0 (main)
funcA() {
....
I1: R5 = R4 + 1
I2: R6 = R7 - 2
I3: R3 = R5 * R4
I4: R8 = R7 * 2
I5: R2 = R3 + R5
I6: Load R1, 0(R2)
}
Thread 1 (helper)
Slice1() {
R5 = R4 + 1
R3 = R5 * R4
R2 = R3 + R5
Prefetch 0(R2)
}
在这个示例中,辅助线程(Thread 1)执行反向切片
Slice1()
,计算出加载地址
0(R2)
并进行预取,而主线程(Thread 0)继续执行
funcA()
。当主线程执行到
I6
时,数据已经在缓存中,避免了缓存缺失带来的延迟。
10. 辅助线程的优势和局限性
辅助线程在提高程序性能方面具有显著优势,但也存在一定的局限性,具体如下:
| 方面 | 优势 | 局限性 |
| ---- | ---- | ---- |
| 性能提升 | 可以提前预取难以预测的地址数据,减少缓存缺失,提高程序的执行速度。特别是对于具有不可预测数据访问模式的应用程序,辅助线程能够显著改善性能。 | 辅助线程的创建和执行需要一定的额外开销,包括线程上下文切换和指令执行时间。如果预取的数据最终未被使用,这些开销将成为浪费。 |
| 可编程性 | 预取算法可以根据每个数据访问进行定制,而不是采用硬连线的方式,提高了灵活性和适应性。 | 需要编译器进行复杂的分析和指令插入,增加了编译时间和复杂度。对于一些小型应用程序,这种额外的开销可能不值得。 |
| 通用性 | 适用于各种类型的应用程序,特别是那些具有大内存足迹和不可预测数据访问行为的应用,如数据库、多媒体和游戏。 | 对于数据访问模式较为规则的应用程序,传统的预取机制可能已经足够,辅助线程的优势不明显。 |
11. 线程级并行技术的应用场景
线程级并行技术,包括线程级推测(TLS)和辅助线程,在多个领域都有广泛的应用:
1.
科学计算
:在科学计算中,如气象模拟、分子动力学模拟等,通常涉及大量的循环和数值计算。TLS可以自动并行化这些循环,提高计算速度。辅助线程可以用于预取计算所需的数据,减少内存访问延迟。
2.
数据处理
:在数据库系统中,数据的查询和处理需要频繁访问内存。辅助线程可以提前预取查询所需的数据,提高查询性能。TLS可以并行化数据处理任务,加速数据的分析和处理过程。
3.
多媒体处理
:多媒体应用程序,如视频编码、图像渲染等,通常具有大量的循环和并行计算任务。TLS可以并行化这些任务,提高处理速度。辅助线程可以预取视频帧或图像数据,减少解码和渲染的等待时间。
4.
游戏开发
:游戏中需要实时处理大量的图形、物理和逻辑计算。线程级并行技术可以并行化这些计算任务,提高游戏的帧率和响应速度。辅助线程可以预取游戏资源,如纹理、模型等,减少加载时间。
12. 线程级并行技术的未来发展趋势
随着计算机硬件和软件技术的不断发展,线程级并行技术也将不断演进和完善。以下是一些可能的发展趋势:
1.
硬件优化
:未来的处理器可能会进一步优化对线程级并行技术的支持,提供更多的硬件资源和更高效的缓存管理机制。例如,增加缓存容量、提高缓存命中率,以及优化线程调度和同步机制。
2.
软件算法改进
:研究人员将不断探索更高效的并行化算法和优化策略,提高线程级并行技术的性能和可编程性。例如,开发更智能的编译器,能够更准确地识别并行化的候选区域,并自动生成高效的并行代码。
3.
与其他技术的融合
:线程级并行技术可能会与其他新兴技术,如人工智能、机器学习等相结合,创造出更强大的计算能力。例如,利用机器学习算法优化线程调度和预取策略,提高系统的整体性能。
4.
跨平台应用
:随着移动设备和云计算的普及,线程级并行技术将越来越多地应用于不同的平台和环境。未来的技术将更加注重跨平台的兼容性和可移植性,使开发者能够更方便地在不同设备上实现并行化。
13. 总结
线程级并行技术,如线程级推测(TLS)和辅助线程,为提高程序的并行性和性能提供了有效的解决方案。TLS通过自动并行化顺序程序,减少了程序员的手动干预,提高了开发效率。辅助线程通过预取数据,减少了内存访问延迟,提高了应用程序的响应速度。
然而,这些技术也面临着一些挑战,如硬件复杂性、编译器优化难度和性能开销等。在实际应用中,需要根据具体的应用场景和需求,合理选择和使用这些技术,以达到最佳的性能提升效果。
未来,随着硬件和软件技术的不断进步,线程级并行技术将在更多领域得到广泛应用,并不断推动计算机性能的提升。开发者和研究人员需要密切关注这些技术的发展趋势,不断探索和创新,以应对日益增长的计算需求。
通过本文的介绍,希望读者对线程级并行技术有更深入的了解,并能够在实际开发中灵活运用这些技术,提高程序的性能和效率。
下面是线程级并行技术相关流程的 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{选择编程模型}:::decision
B -->|TLS| C(识别候选区域):::process
B -->|辅助线程| D(确定延迟加载):::process
C --> E(TLS软件和硬件并行化):::process
D --> F(生成反向切片):::process
E --> G(执行并行程序):::process
F --> H(存储反向切片和指令):::process
H --> I(运行时决策启动辅助线程):::process
I --> J(预取数据):::process
G --> K([结束]):::startend
J --> K
这个流程图展示了线程级推测(TLS)和辅助线程两种编程模型的主要流程,从开始选择编程模型,到具体的操作步骤,最后到程序结束。通过这个流程图,可以更直观地理解线程级并行技术的工作原理和流程。
超级会员免费看
10万+

被折叠的 条评论
为什么被折叠?



