优化程序性能(3)——提高并行性

在之前的学习中,程序的性能是受运算单元的延迟限制的。正如我们表明的,执行加法和乘法的功能单元是完全流水线化的,这意味着它们可以每个时钟周期开始一个新操作,并且有些操作可以被多个功能单元执行。硬件具有以更高速率执行乘法和加法的潜力,但是代码不能利用这种能力,即使是使用循环展开也不能,这是因为我们将积累值放在一个单独的变量acc中,在前面的计算完成之前,都不能计算acc的新值(顺序依赖)。虽然计算acc值的功能单元能够每个时钟周期开始一个新操作,但是它只会每L(L是合并操作的延迟)个周期开始一条新操作。
为了打破这种顺序相关,得到比延迟界限更好性能的方法我们可以考虑设置多个积累变量。
对于一个可结合和可交换的合并运算来说,比如说整数加法或乘法,我们可以通过将一组合并运算分割成两个或更多的部分,并在最后合并结果来提高性能。

/* 2 x 2 loop unrolling */
void combine6(vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length-1;
data_t *data = get_vec_start(v);
data_t acc0 = IDENT;
data_t acc1 = IDENT;

/* Combine 2 elements at a time */
for(i = 0; i < limit; i+=2){
acc0 = acc0 OP data[i];
acc1 = acc1 OP data[i+1];
}

/* Finish any remaining elements */
for(; i < limit ; i++){
acc0 = acc0 OP data[i];
}
*dest = acc0 OP acc1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上述代码既使用了两次循环展开,以使每次迭代合并更多的元素,也是用了两路并行,将索引值为偶数的元素累积在变量acc0中,而索引值为奇数的元素累积在变量acc1中。我们将它称为“2x2循环展开”。比较只做循环展开和既做循环展开同时也使用两路并行这两种方法,我们得到下面的性能:

我们看到所有情况都得到了改进,整数乘、浮点加、浮点乘改进了约2倍,而整数加也有所改进。最棒的是,我们打破了由延迟界限设下的限制。处理器不再需要延迟一个加法或乘法操作以待前一个操作完成。

实际上,程序正在利用功能单元的流水线能力,将利用率提高到两倍。唯一的例外是整数加。我们已将CPE降低到1.0以下,但是还是有太多的循环开销,而无法达到理论界限0.50。

通常,只有保持能够执行该操作的所有功能单元的流水线都是满的,程序才能达到这个操作的吞吐量界限。对延迟为L,容量为C的操作而言,这就要求循环展开因子k≥C·L。比如,浮点乘由C=2,L=5,循环展开因子就必须为k≥10.浮点加有C=1,L=3,则在k≥3时达到最大吞吐量。

现在来探讨另一种打破顺序相关从而使性能提高到延迟界限之外的方法——重新结合变换。我们看到过做kx1循环展开的combine5 没有改变合并向量元素形成和或者乘积中执行的操作。对代码做很少的改动,我们可以从根本上改变合并执行的方式,也极大地提高程序的性能。下面给出一个函数combine7,它与combine5的展开代码的唯一区别在于内循环中元素合并的方式。在combine5中,合并使以下面这条语句来实现的:
acc = (acc OP data[i]) OP data[i+1];
而在combine7中,合并是以这条语句来实现的
acc = acc OP (data[i] OP data[i+1] );
差别仅仅在于两个括号是如何放置的。我们称之为重新结合变换,因为括号改变了向量元素与累计值acc的合并顺序,产生了我们称为“2x1a”的循环展开形式。

/* 2 x 1a loop unrolling */
void combine7(vec_ptr v,data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length-1;
data_t *data = get_vec_start(v);
data_t acc = IDENT;

/* Combine 2 elements at a time */
for(i = 0; i < limit; i +=2) {
acc = acc OP (data[i] OP data[i+1]);
}
/* Finish any remaining elements */
for(; i < length ;i++){
acc = acc OP data[i];
}
*dest = acc;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
再来测试CPE时,我们得到令人吃惊的结果:

整数加的性能几乎与使用kx1展开的版本(combine5)的性能相同,而其他三种情况则与使用并行累计变量的版本(combine6)相同,是kx1扩展的性能的两倍。这已经突破了延迟界限造成的限制。
这是因为关键路径上只有n/2个操作。每次迭代内的第一个乘法都不需要等待前一次迭代的累计值就可以执行。因此,最小可能的CPE减少了两倍。
注意:对于整数加法和乘法,这些运算是可结合的,这表示这种重新变换顺序对结果没有影响。对于浮点数情况,必须再次评估这种重新结合是否有可能严重影响结果。

总的来说,重新结合变换能够减少关键路径上操作的数量,通过更好地利用功能单元的流水线能力可以得到更好的性能。大多数编译器不会尝试对浮点运算做重新结合,因为这些运算不保证是可结合的。
---------------------

原文地址:https://www.cnblogs.com/ly570/p/11001441.html

时间: 2024-08-30 13:46:31

优化程序性能(3)——提高并行性的相关文章

深入理解计算机系统(5.1)------优化程序性能

你能获得的对程序最大的加速比就是当你第一次让它工作起来的时候. 在讲解如何优化程序性能之前,我们首先要明确写程序最主要的目标就是使它在所有可能的情况下都能正常工作,一个运行的很快的程序但是却是错误的结果是没有任何用处的,所以我们在进行程序性能优化之前,首先要保证程序能正常运行,且结果是我们需要的. 而且在很多情况下,让程序跑的更快是我们必须要解决的问题.比如一个程序要实时处理视频帧或者网络包,那么一个运行的很慢的程序就不能解决此问题.再比如一个计算任务计算量非常大,需要数日或者数周,如果我们哪怕

浅谈优化程序性能(下)

前言 在上一篇随笔中,我们谈到最小化一个计算中的操作数量不一定会提高它的性能.现在,就让我们来解开为什么会出现这种情况的原因吧. 处理器体系结构 在计算机的处理器中,处理一条指令包括很多操作,可以分为取指(fetch).译码(decode).执行(execute).访存(memory).写回(write back)和更新程序计数器(PC update)等几个阶段.这些阶段可以在流水线上同时进行,如下图所示: 上图中,F.D.E.M 和 W 分别代表上述五个阶段.当然,现代的处理器比这个示例要复杂

《深入理解计算机系统》 优化程序性能的几个方法

本文几个优化程序性能的方法出自CSAPP第五章,通过不断修改源代码,试图欺骗编译器产生有效的代码 我们先引入度量标准每元素的周期数(CPE),表示程序性能. 我们先定义一个数据结构   data_t 代表数据类型 1 typedef struct{ 2 long len; 3 data_t *data; 4 }vec_rec,*vec_prt; 以及常数IDENT和OP以便在后续的代码中进行不同的操作 //对所有向量的元素求和 #define IDENT 0 #define OP + //对所有

优化程序性能(CSAPP:5)

[前言]虽然现在没有接触过大型项目,但是工作了会注重性能.学习一下,应该能更好更快的理解别人写的经典优秀的代码.结合CSAPP和自己的理解,总结一下. 一.程序优化综述 1.高效程序的特点 (1)适当的算法和数据结构.方法和数据的组织形式无疑是最关键的,是优化的基础: (2)代码能够被编译器转化成高效的可执行代码.需要深入了解使用的编译器的优化方法,和常见的优化策略: (3)运用现代并行编程技术.多核以及硬件支持提供更大的加速可能,例如GPU: 2.优化程序的一般步骤 (1)消除不必要的工作,例

浅谈优化程序性能(上)

前言 我们知道,多项式定义为: 在几何学中,多项式是最简单的平滑曲线.简单是指它仅由乘法及加法构成,平滑是因为它类同口语中的平滑,以数学术语来说,它是无限可微,即它的所有高次微分都存在.事实上,多项式的微分也是多项式.简单及平滑的特点,使多项式在数值分析.图论,以及电脑绘图等,都发挥极大的作用.多项式求值是解决许多问题的核心技术.以数值分析为例,多项式函数常常用作对数学库中的三角函数求近似值. 现在,让我们来用 C 语言写一个对多项式求值的函数吧. 直接的算法 直接按照多项式的定义使用循环求值:

记一次使用ConcurrentDictionary优化程序性能的经验总结

项目情形 最近做项目发现有个业务逻辑性能效率巨慢, 实际上是扫描cosmos上面16个文件夹下面的数据, 每个folder下面大概分为100来个对应user的fodler, 然后对应user folder下面存放的是user的数据. 原逻辑是一个folder一个folder去scan, 然后将统计的数据按照 user和size存放到一个dictionary中, 最后汇总统计并且发邮件. 其中影响效率的部分有当前运行环境与cosmos的交互上, 不同的环境快慢不同. 另外一个就是code逻辑是串行

iOS 程序性能优化

前言 转载自:http://www.samirchen.com/ios-performance-optimization/ 程序性能优化不应该是一件放在功能完成之后的事,对性能的概念应该从我们一开始写代码时就萦绕在我们脑子里.了解 iOS 程序性能优化的相关知识点,从一开始就把它们落实到代码中是一种好的习惯. 初级技巧 使用复用机制 在我们使用 UITableView 和 UICollectionView 时我们通常会遇到「复用 Cell」这个提法,所谓「复用 Cell」就是指当需要展示的数据条

C++应用程序性能优化(二)——C++对象模型

C++应用程序性能优化(二)--C++对象模型 一.C++对象模型与性能优化 对象模型是面向对象程序设计语言的重要方面,会直接影响面向对象语言编写程序的运行机制以及对内存的使用机制,因此了解对象模型是进行程序性能优化的基础.只有深入理解C++对象模型,才能避免程序开发过程中一些不易发现的内存错误,从而改善程序性能,提高程序质量. 二.C++程序的内存分布 1.程序内存分布简介 通常,计算机程序由代码和数据组成,因此代码和数据也是影响程序所需内存的主要因素.代码是程序运行的指令,比如数学运算.比较

web应用程序性能优化

web应用程序基本上都是在浏览器地址栏输入一段网站,然后进入,最后浏览器显示你想要的东西. 这就是用户所能体会到的东西.那作为程序员我们看到了什么呢? 一次HTTP 请求主要的流程是: 1.DNS服务器解析域名(浏览器地址栏的地址)获取相应的IP地址.端口号. 服务名. 2.客户端根据解析后的地址向服务啊发送请求(建立与服务器的联接). 3.服务器根据用户的请求信息处理请求,并做出响应. 4.浏览器更具服务器响应的数据(HTML/css/js)渲染页面. 那要优化程序性能,作为程序员我们能优化哪