【梳理】计算机组成与设计 第3章 算术 第3节 数据级并行(内附文档高清截图)

配套教材:
Computer Organization and Design: The Hardware / Software Interface (5th Edition)
这是专业必修课《计算机组成原理》的复习指引。建议将本复习指导与博客中的《简明操作系统原理》配合复习。
在本文的最后附有复习指导的高清截图。需要掌握的概念在文档截图中以蓝色标识,并用可读性更好的字体显示 Linux 命令和代码。代码部分语法高亮。
计算机组成原理不是语言课,本复习指导对用到的编程语言的语法的讲解也不会很细致。如果不知道代码中的一些关键字、指令或函数的具体用法,你应该自行查找相关资料。


第三节 数据级并行

24、随着图形和音频的需求越来越强劲,CPU架构师们实现了数据级并行(data level parallelism,DLP),也称子字(亚字)并行(subword parallelism)。数据级并行在实际应用中一般说成更具体的方法,比如向量化(矢量化,vectorization)或者SIMD(single instruction multiple data,单指令多数据)。向量化计算是一种特殊的并行计算的方式,相比于一般程序在同一时间只执行一个操作的方式,它可以在同一时间执行多次操作,通常是对不同的数据同时执行同样的一个或一批指令,或者说把指令应用于一个数组 / 向量。也就是说,向量化使得一次可以对多个数据进行操作。

25、ARM的NEON指令集就是一种向量化的指令集。它可以并行8-bit、16-bit、32-bit和64-bit的整数,以及32-bit的浮点,但不可以并行64-bit的浮点。Intel的MMX / SSE / AVX也是向量化的指令集。NEON支持的基本操作如下图:(I代表定点数(小数点位置不变),S和U代表有符号和无符号整数)

Intel在SSE2(2001)时期就支持对双精度的并行化。SSE / SSE2的浮点指令如下。其中xmm是SSE指令专用的寄存器,SS = 单个未向量化的单精度浮点,SD = 单个未向量化的双精度浮点,PS = 打包的单精度浮点,PD = 打包的双精度浮点(S = scalar,P = packed)。A代表128-bit操作数在内存中对齐(aligned),U代表未对齐。H和L分别代表128-bit寄存器的高半部分和低半部分。

后来Intel发布了AVX指令集,同时支持将更多的整数和浮点放在一个寄存器里同时预算。

26、C++早已支持了通过硬件进行矢量化运算。下面是一个DGEMM(Double precision general matrix multiply,双精度通用矩阵乘法)运算及其矢量化版本:
void dgemm(int n, double* A, double* B, double* C) {
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j) {
double cij = C[i + j * n]; /* cij = C[i][j] /
for (int k = 0; k < n; ++k)
cij += A[i + k * n] * B[k + j * n]; /
cij += A[i][k]*B[k][j] /
C[i + j * n] = cij; /
C[i][j] = cij */
}
}

void dgemm(int n, double *A, double *B, double C) {
for (int i = 0; i < n; i += 4)
for (int j = 0; j < n; ++j) {
__m256d c0 = _mm256_load_pd(C + i + j * n); /
c0 = C[i][j] /
for (int k = 0; k < n; ++k)
c0 = _mm256_add_pd(c0, /
c0 += A[i][k]*B[k][j] /
_mm256_mul_pd(_mm256_load_pd(A + i + k * n),
_mm256_broadcast_sd(B + k + j * n)));
_mm256_store_pd(C + i + j * n, c0); /
C[i][j] = c0 */
}
}
为了能够使用硬件支持的向量化指令,在GCC和MSVC(Microsoft Visual C++ Compiler)上应该分别引用头文件<x86intrin.h>和<intrin.h>。
它们在Sandy Bridge CPU上产生的汇编代码分别如下:

  1. vmovsd (%r10),%xmm0 ; Load 1 element of C into %xmm0

  2. mov %rsi,%rcx ; register %rcx = %rsi

  3. xor %eax,%eax ; register %eax = 0

  4. vmovsd (%rcx),%xmm1 ; Load 1 element of B into %xmm1

  5. add %r9,%rcx ; register %rcx = %rcx + %r9

  6. vmulsd (%r8,%rax,8),%xmm1,%xmm1 ; Multiply %xmm1, element of A

  7. add $0x1,%rax ; register %rax = %rax + 1

  8. cmp %eax,%edi ; compare %eax to %edi

  9. vaddsd %xmm1,%xmm0,%xmm0 ; Add %xmm1, %xmm0

  10. jg 30 <dgemm+0x30> ; jump if %eax > %edi

  11. add $0x1,%r11d ; register %r11 = %r11 + 1

  12. vmovsd %xmm0,(%r10) ; Store %xmm0 into C element

  13. vmovapd (%r11),%ymm0 ; Load 4 elements of C into %ymm0

  14. mov %rbx,%rcx ; register %rcx = %rbx

  15. xor %eax,%eax ; register %eax = 0

  16. vbroadcastsd (%rax,%r8,1),%ymm1 ; Make 4 copies of B element

  17. add $0x8,%rax ; register %rax = %rax + 8

  18. vmulpd (%rcx),%ymm1,%ymm1 ; Parallel mul %ymm1,4 A elements

  19. add %r9,%rcx ; register %rcx = %rcx + %r9

  20. cmp %r10,%rax ; compare %r10 to %rax

  21. vaddpd %ymm1,%ymm0,%ymm0 ; Parallel add %ymm1, %ymm0

  22. jne 50 <dgemm+0x50> ; jump if not %r10 != %rax

  23. add $0x1,%esi ; register % esi = % esi + 1

  24. vmovapd %ymm0,(%r11) ; Store %ymm0 into 4 C elements
    高度矢量化的代码可以让运行速度成倍提升,有时的提升甚至接近4倍。

27、浮点数加法不满足结合律。因为浮点数是近似的表示,而且计算过程中也会损失精度。举例:设c = –1.5×1038,a = 1.5×1038,b = 1.0。则:

28、对整数运算适用的并行方法不一定适用于浮点。一个原因也是因为浮点数的精度限制,另一个原因是调度器影响。有的操作系统的调度器会视任务的多少而将分配给当前进程的核心数变动。不同的CPU核心数会对浮点的计算顺序有一定的影响,于是使得结果有一点不同。
一种避免方法是将算法编写两个版本:串行版和并行版,并检验它们给出的结果相差多少,是否对结果有不能忽视的影响。其实这涉及到数值分析(numerical analysis)领域了。

29、MIPS没有减立即数的指令,减一个正的立即数会被转换成加它的相反数。所以MIPS指令中的立即数都是做带符号扩展的。

30、有的人认为只有理论数学家需要考虑浮点精度的问题。这么想就大错特错了!

1994年,报纸的大标题上赫然刊登了Intel Pentium处理器的重大问题。当时,Pentium使用了一个标准的一步生成多位商的浮点除法算法:结合专门的表,使用除数和被除数的最高位来猜测商的下两位。如果猜测错误,就做额外处理。
但是Intel的工程师认为表中有5项从来不会被用到,于是将逻辑电路进行了充分优化。后来发生的一系列事件表明:这一步,确实走错了。这个优化导致了浮点的有效位的第12位到52位可能出错。
弗吉尼亚的University of Lynchburg数学教授Thomas Nicely在1994年9月发现了这个bug。他向Intel的技术支持部门询问,但未获得回应。于是他将这个bug发布到Internet上。这个网帖导致Intel在后来不得不召开媒体发布会。当时Intel声称这个bug无伤大雅,只影响理论数学家;使用计算机制作电子表格的用户平均27000年才会碰见1次这样的错误。但IBM Research很快发表反驳,声称平均每24天这样的错误就会被电子表格用户遇到1次。在12月21日,Intel认输了,并发布了如下声明:
“我们为最近处理被曝光的Pentium处理器出错的方式进行诚挚的道歉。Intel Inside符号代表您的计算机中的微处理器拥有最佳的性能和质量。数千Intel员工努力工作来确保这点实现。但是没有任何微处理器是绝对完美的。Intel相信技术上的一个极小错误已经开始了它的长期影响。虽然Intel坚决信任当前Pentium处理器的品质,但我们已认识到许多用户表示了担忧。我们会解决这些忧虑。在所有提出相关要求的用户的计算机的生命周期内,Intel在任何时刻都可以免费把当前版本的Pentium处理器更换为新版,新版将纠正浮点除法的错误。”
分析家们指出,这次召回令Intel损失了约5亿美金(1994年)。Intel的工程师们的圣诞节奖金也泡汤了。这个故事引发了我们的诸多思考:如果Intel能够尽早修复,损失会少多少?Intel接下来需要花费多少开销来挽回失掉的名声?处理器作为应用极其广泛的产品,许多工作都依赖其可靠性。CPU的厂家在修复缺陷方面应当负怎样的责任?

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章