Parallel processing “可以吗?”;减少;函数编程中的函数并行化?

Parallel processing “可以吗?”;减少;函数编程中的函数并行化?,parallel-processing,functional-programming,simd,x86,sse,Parallel Processing,Functional Programming,Simd,X86,Sse,在函数式编程中,map函数的一个好处是它可以在并行中执行 let numbers = [0,1,2,3] let increasedNumbers = numbers.map { $0 + 1 } 因此,在4核硬件上,此代码和map的并行实现将允许同时处理这4个值 let numbers = [0,1,2,3] let increasedNumbers = numbers.map { $0 + 1 } 好的,现在让我们谈谈reduce函数 返回重复调用联合收割机的结果,并返回累计 值初始化为

在函数式编程中,
map
函数的一个好处是它可以在并行中执行

let numbers = [0,1,2,3]
let increasedNumbers = numbers.map { $0 + 1 }
因此,在4核硬件上,此代码和
map
的并行实现将允许同时处理这4个值

let numbers = [0,1,2,3]
let increasedNumbers = numbers.map { $0 + 1 }
好的,现在让我们谈谈
reduce
函数

返回重复调用联合收割机的结果,并返回累计 值初始化为初始值和self的每个元素,依次为。 返回联合收割机(联合收割机(…联合收割机(联合收割机)(初始,自[0]), self[1],…self[count-2]),self[count-1])

我的问题是:
reduce
功能是否可以并行执行? 或者,根据定义,它是只能按顺序执行的东西

例如:

let sum = numbers.reduce(0) { $0 + $1 }

最常见的缩减之一是所有元素的总和

((a+b) + c) + d == (a + b) + (c+d)  # associative
a+b == b+a                          # commutative
该等式适用于整数,因此您可以将操作顺序从一个长依赖链更改为多个短依赖链,从而允许多线程和SIMD并行

数学实数也是如此。在许多情况下,这是不可预料的,因此最终结果将非常接近,值得获得巨大的性能增益。对于C/C++编译器,这是由
-ffast math
选项启用的优化之一。(对于
-ffast math
的这一部分,有一个
-fassocialative math
选项,没有关于缺少无穷大和n的假设。)

如果一个大的负载不能获取多个有用的值,那么很难获得多大的SIMD加速比。Intel的AVX2增加了“聚集”负载,但开销非常高。在Haswell中,仅使用标量代码通常会更快,但后来的微体系结构确实有更快的聚集。因此,SIMD缩减在阵列或其他连续存储的数据上更为有效

现代SIMD硬件的工作原理是将2个连续的双精度浮点加载到向量寄存器中(例如,使用16B的向量,如s)。有一个压缩FP add指令,它将两个向量的对应元素相加。所谓的“垂直”向量操作(两个向量中的对应元素之间发生相同操作)比“水平”操作(将一个向量中的两个
double
s相加)便宜得多


因此,在asm级别,有一个循环,将所有偶数元素相加为向量累加器的一半,将所有奇数元素相加为向量累加器的另一半。最后一个水平操作将它们组合在一起。因此,即使没有多线程,使用SIMD也需要关联操作(或者至少与关联操作足够接近,就像浮点操作一样)。如果输入中有一个近似模式,比如+1.001,-0.999,那么将一个大正数添加到一个大负数时的取消错误可能比每次取消都单独发生时严重得多

对于更宽的向量或更窄的元素,向量累加器将容纳更多的元素,从而增加SIMD的好处

现代硬件具有流水线执行单元,每个时钟可以支持一个(有时两个)FP矢量加法,但每个FP矢量加法的结果不足以支持5个周期。饱和硬件的吞吐量能力需要在循环中使用多个累加器,因此有5个或10个单独的循环承载依赖链。(具体而言,英特尔Skylake采用4c延迟和每0.5c吞吐量一次的矢量FP乘法、加法或FMA(融合乘法加法)。4c/0.5c=8个FP加法同时运行,以使Skylake的FP数学单元饱和。每个操作可以是8个单精度浮点、4个双精度浮点、一个16B矢量或一个标量的32B矢量。(保持多个操作的运行也可以加速标量运算,但如果有任何可用的数据级并行性,您可能可以将其矢量化,也可以使用多个累加器。)有关x86指令计时、管道描述和asm优化内容,请参阅。但请注意,这里的所有内容都适用于带NEON的ARM、PPC Altivec和其他SIMD体系结构。它们都有向量寄存器和类似的向量指令

举个具体的例子,.它只使用一个累加器,因此它缺少Skylake的8倍吞吐量。clang更聪明一些,可以获得Skylake最大吞吐量的1/4。请注意,如果从编译选项中去掉
-ffast math
,FP循环使用
addss
(添加标量单)整数循环仍然会自动矢量化,因为整数数学是关联的

实际上,内存带宽在大多数情况下是限制因素。Haswell和更高版本的Intel CPU可以从一级缓存每周期承受两个32B的负载。共享三级缓存是另一回事:它比主存快得多,但其带宽由所有内核共享。这使得缓存阻塞(aka)对于一级缓存或二级缓存来说,这是一个非常重要的优化,它可以在处理超过256k的数据时以较低的成本完成。与其生成然后减少10MB的数据,不如生成128k的数据块,并在它们仍在二级缓存中时减少它们,而不是生成程序必须将它们推送到主存,而缩减程序必须将它们放回内存。W在使用更高级别的语言时,最好的选择可能是希望实现能为您做到这一点。不过,从CPU的实际功能来看,这是您理想的愿望

请注意,所有SIMD加速功能都适用于在连续内存块上运行的单个线程。您(或您的函数式语言的编译器!)可以而且应该使用这两种技术,使每个线程都有多个线程