程序性能优化——五、程序编写时的优化(上)

对程序的编写进行优化是最常用、学习成本最低、硬件成本也最低的优化方式,所以我打算从这部分开始写具体的优化方式。以下从算法优化、数据结构优化、函数优化、循环优化、语句优化,五个方面展开论述

1算法优化

| 算法是程序的道,具体实现方式是程序的术,战略上的不足无法用战术来弥补。

1.1选择算法

在对具体问题进行编程进行优化时,必须考虑到所采用的算法是否有更好的版本。比如,排序问题:快速排序、桶排序之于冒泡排序;字符串匹配问题:KMP算法之于暴力匹配;有序搜索问题:二分查找之于暴力查找。

算法是否好,是基于程序优化的目的来说的(按照程序优化的流程,修改程序前必须要确定优化目的,详见《三、程序性能优化流程》)。比如,有的算法消耗时间少消耗空间大,有的则相反,有的在两者之间形成了时空的折中。

| 反者道之动,很多事情的终点都是一个优美的平衡。

1.2改进算法

从算法的过程出发。

你可以通过分析算法的执行过程来改进它。你可以观察算法在不同情况下的表现,找出其中的瓶颈和不足之处。你可能会发现一些冗余的步骤、效率低下的部分或者可以优化的地方。通过深入理解算法的运行原理,你可以提出改进的想法,例如重新设计某些步骤、引入更有效的数据结构或者优化算法的逻辑。

比如,在寻找一个数组具有最大和的子数组时,你原本的代码使用了三个循环来对每个子数组进行求和,后来你发现很多的求和步骤是冗余的,可以将中间过程存储下来,减少计算量。这就是从算法的过程出发。

从算法的编码出发。你可以通过修改算法的实现代码来改进它。相当于是完全改变了算法的过程,而不是在原基础上进行修改。

同样,在寻找一个数组具有最大和的子数组时,你转而使用分治法,或是线性规划的方法,大大提升了算法的效率。但是这样的方法只能另起炉灶找出,而不是在原有的算法上修改得到的。

2数据结构优化

程序=数据结构+算法。可见,数据结构的构造,对于程序的效率也是至关重要。

数据结构的分类

数据结构可以按照逻辑结构和存储结构进行分类。逻辑结构指的是元素之间的关系,比如线性结构、树结构、图结构、集合结构。逻辑结构和算法的设计过程紧密相关,影响的是算法的道。存储结构是指实际元素之间存储位置的关系,有顺序存储和非顺序存储两种,比如同样是线性结构,顺序存储的就是列表(数组),非顺序存储的就是链表。存储结构和算法的实现过程紧密相关,影响的是算法的术(有时候术优化的效果比道还好)。

| 大处着眼,小处着手

典型数据结构的对比

以下使用两个表格,分别对比了经典数据结构的优缺点和基本操作性能。
不同数据结构的优缺点

数据结构优点缺点
数组插入快查找慢、删除慢、大小固定
有序数组查找快插入慢、删除慢、大小固定
哈希表查找非常快、插入删除非常快存储空间使用率低、很少碰撞运行快速的哈希算法很难设计
链表插入删除快、大小灵活查找慢
后进先出适用于多种需要递归的场景大小固定、查找插入删除慢
队列先进先出保证了数据的顺序性大小固定、查找插入删除慢
动态分配内存查找慢
二叉树插入、查找、删除快对数据顺序敏感
可以实现很多现实问题存储和操作开销大

不同数据结构的操作性能对比

数据结构查找插入删除遍历
数组O(N)O(1)O(N)
有序数组O(log N)O(N)O(N)O(N)
链表O(N)O(1)O(N)
有序链表O(N)O(N)O(N)O(N)
二叉树O(log N)O(log N)O(log N)O(N)
二叉树(最坏)O(N)O(N)O(N)O(N)
平衡树O(log N)O(log N)O(log N)O(N)
哈希表O(1)O(1)O(1)

优化你的数据结构

0.选择合适的数据结构

1.使用更小的单位元素

| 若无必要,勿增实体

尽可能地使用能够满足情形的最小数据结构吧(能用short不用int)。第一、更小的数据结构意味着能有更多的数据保存在缓存中,访问会更加迅速;第二、在使用向量指令进行优化时(《九、单核优化》将会介绍),相比于大尺寸的数据结构,向量寄存器能够存储多的小尺寸数据结构,从而平行度更大;第三、在内存资源有限或者需要大规模数据存储时,使用更小的数据结构可以有效节省内存资源,降低系统的内存消耗,提高系统的性能和稳定性;第四、存储空间更小的数据结构通常意味着数据量更小,因此在数据传输过程中,可以减少数据量,加快数据传输的速度。

2.使用适合硬件结构的数据结构

并行数组是一种将多个数组合并成一个的数据结构,适合硬件结构的原因在于其对于向量化指令集(如SIMD)的友好性。在并行数组中,多个数组中相同索引位置的元素被合并成一个向量,这样可以利用向量化指令一次性操作多个元素,充分发挥处理器的并行计算能力。

3.提高数据访问局部性

3函数优化

别名消除

别名指的是两个指针指向相同的内存区域,此时编译器不能认识到这一点,所以处理的时候几乎不会自动优化。以下是几种别名消除的方法:

  1. 基于指针分析的别名消除

    // 不优化的写法
    int a = 5;
    int* ptr1 = &a;
    int* ptr2 = &a;
    
    *ptr1 = 10; // 修改通过 ptr1 访问的内存区域
    *ptr2 = 15; // 修改通过 ptr2 访问的内存区域,此时编译器无法确定 ptr1 和 ptr2 是否引用同一内存区域
    
    // 优化后的写法(别名消除)
    int a = 5;
    int* ptr = &a; // 将 ptr1 和 ptr2 合并为一个指针
    *ptr = 10; // 修改通过 ptr 访问的内存区域
    *ptr = 15; // 同样修改通过 ptr 访问的内存区域,消除了别名关系
    
  2. 基于静态分析的别名消除

    // 不优化的写法
    int a, b;
    int* ptr = &a;
    
    b = *ptr; // 读取通过 ptr 访问的内存区域
    
    // 优化后的写法(别名消除)
    int a, b;
    int* ptr = &a;
    
    b = a; // 直接将 a 的值赋给 b,消除了通过 ptr 的间接访问
    
  3. 基于程序转换的别名消除

    // 不优化的写法
    void swap(int* a, int* b) {
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    
    void foo() {
        int x, y;
        int* ptr = &x;
    
        swap(ptr, &y); // 调用 swap 函数交换 x 和 y 的值
    }
    
    // 优化后的写法(别名消除)
    void foo_optimized() {
        int x, y;
        int temp = x; // 直接使用局部变量 temp,避免了通过指针的间接访问
    
        x = y;
        y = temp;
    }
    

常数传播

简而言之就是让程序少做无用的,提前可以避免的运算、变量带值。这虽然看起来影响很熹微,但是在大循环中很有效。

举几个例子。

优化前

x = 10 + 5
y = x * 2

优化后

x = 15
y = x * 2

优化前

const PI = 3.14
radius = 5
area = PI * radius * radius

优化后

const PI = 3.14
radius = 5
area = 3.14 * radius * radius

虽然很简单,但是说真的很有效果,利用这个方法可以将许多后量子密码标准的源代码提升40%左右的速度。

传参优化

传递媒介有寄存器和栈两种,前者比后者快接近十倍,所以我们当然要尽可能地让寄存器去传递参数。有以下两种方法:

  1. 传递的参数尽可能存储空间小,个数尽可能少

  2. 对于存储空间较大,或是个数较多的参数,使用结构体存储这些参数,传参时只传递结构体的指针

内联替换

如果说常数传播优化是为了避免常数参数的取值操作,那么内联替换就是为了避免简单函数被调用产生时空开销。可以使用以下方式进行优化:

  1. 手动内联:程序员手动将函数调用替换为函数体,通常用于短小的函数或者需要频繁调用的函数。

    // 原始函数
    inline int add(int a, int b) {
        return a + b;
    }
    
    // 手动内联
    int result = 3 + 4;
    
  2. 编译器自动内联: 编译器根据一些启发式规则自动决定哪些函数适合内联。

    // 原始函数
    inline int square(int x) {
        return x * x;
    }
    
    // 函数调用
    int result = square(5);
    
  3. 内联缓存:将函数的返回值缓存起来,以避免重复计算。

    // 原始函数
    inline int square(int x) {
        return x * x;
    }
    
    // 内联缓存
    int result = square(5);
    result = square(6); // 在一些情况下,编译器可能会将上一次调用的结果缓存起来,避免重复计算
    

全局变量优化

全局变量的使用会影响编译器的自动优化,所以可以通过以下方式来优化:

  1. 将全局变量改为局部变量使用(全局变量会被改变的情况)
  2. 将全局变量的值直接写,也就是常数传播(全局变量不被改变的情况)

4循环优化(好累,前面的区域以后再来探索吧)

5语句优化(好累,前面的区域以后再来探索吧)

专栏安排(已有,或将有)

一、程序性能优化的意义

二、程序性能的度量指标

三、程序性能优化流程

四、程序性能的测量和分析

五、程序编写时的优化(上):算法优化、数据结构优化、函数优化

五、程序编写时的优化(下):循环优化、语句优化

六、访存时的优化(上):寄存器优化、缓存优化、内存优化

六、访存时的优化(下):磁盘优化、数据分布

七、编译与运行时的优化(上):编译器结构、编译选项、编译优化

七、编译与运行时的优化(下):数学库优化、运行时的优化

八、系统配置的优化

九、单核优化

十、OpenMP程序优化

十一、MPI程序优化

十二、…

如有不足之处,敬请批评指正

更欢迎在评论区留下你的见解,你的方法,如果有效我会增加在文章中,并@你。

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 卡尔曼滤波是一种用于实估计动态系统状态的算法。它在许多领域中被广泛应用,如航空航天、导航、机器人等。卡尔曼滤波算法通过结合系统的测量数据和模型预测值,来动态地更新系统的状态估计。它的核心思想是将已有的信息与新的观测结果进行加权平均,从而得到对系统真实状态更准确的估计。 在Matlab中,我们可以通过编写卡尔曼滤波的仿真程序来实现对系统状态的估计。首先,我们需要定义系统的状态方程和观测方程,以及系统的初始状态和噪声模型。然后,使用kalman函数来进行滤波处理,将观测数据输入到滤波器中,得到对状态的估计值。 具体而言,我们可以按照以下步骤来编写卡尔曼滤波的Matlab仿真程序: 1. 定义系统的状态方程和观测方程,并初始化系统状态和滤波器的状态估计。 2. 定义系统的噪声模型,包括过程噪声和观测噪声的协方差矩阵。 3. 生成系统的真实状态序列和对应的观测数据。 4. 使用kalman函数进行滤波处理,将观测数据输入到滤波器中,得到对状态的估计值。 5. 计算滤波器的误差协方差矩阵,评估滤波器性能。 6. 绘制真实状态序列、观测数据和滤波器估计值的曲线图,以及滤波器误差的方差曲线图。 在编写程序,我们还可以尝试不同的参数设置和噪声模型,以及对结果进行分析和优化。 总之,通过在Matlab中编写卡尔曼滤波的仿真程序,我们可以更好地理解卡尔曼滤波的原理和应用,并对其进行调试和优化,从而实现更准确的状态估计。 ### 回答2: 卡尔曼滤波是一种用于估计状态变量的方法,它通过将测量观测值与先验估计进行加权平均,来获得更准确的状态估计值。这种滤波方法常用于控制系统中,尤其是在传感器测量带有噪声的情况下。 卡尔曼滤波的原理主要包含两个步骤:预测和更新。在预测步骤中,使用系统的状态转移方程预测下一刻的状态变量;而在更新步骤中,根据已有的观测值和预测值之间的误差,计算卡尔曼增益,从而对预测值进行修正,得到更准确的状态估计。 Matlab是一种广泛使用的科学计算与数据分析工具,提供了丰富的数学函数和工具箱,可以方便地进行卡尔曼滤波的仿真。在Matlab中,可以使用kalman函数来实现卡尔曼滤波。具体步骤如下: 1. 定义系统的状态转移方程、观测方程和噪声协方差矩阵。 2. 初始化系统的状态向量和协方差矩阵。 3. 通过循环迭代,对每个刻进行滤波。 4. 在预测步骤中,使用状态转移方程进行状态的预测。 5. 在更新步骤中,计算观测值与预测值之间的误差,并计算卡尔曼增益。 6. 根据卡尔曼增益修正预测值,得到更准确的状态估计。 7. 更新协方差矩阵,并记录滤波结果。 Matlab还提供了一些用于可视化和分析滤波结果的函数,如plot函数和mean函数等。 通过使用Matlab进行卡尔曼滤波的仿真,我们可以观察到滤波结果与真实值的接近程度,评估滤波算法的性能并进行参数调整,以获得更准确的状态估计。这对于控制系统的设计和实际应用具有重要意义。 ### 回答3: 卡尔曼滤波是一种最优化的滤波方法,用于估计系统的状态。它通过融合系统的测量值和预测值,提供对未知状态的最优估计。卡尔曼滤波的原理是基于贝叶斯定理,它假设系统的状态满足线性动力学方程,并且状态的噪声满足高斯分布。在卡尔曼滤波中,有两个主要的步骤:预测步骤和更新步骤。 预测步骤是根据上一个刻的状态估计值,预测当前刻的状态估计值和协方差矩阵。更新步骤是通过测量值,根据预测的状态估计值和当前测量值之间的差异,进行状态的修正和协方差矩阵的更新。 Matlab中提供了卡尔曼滤波的仿真工具箱,可以通过设置系统模型、测量模型、协方差矩阵以及初始状态值等参数,实现对状态的估计。 随书程序是指在教科书中附带的示例程序。卡尔曼滤波的随书程序是指通过Matlab编写的卡尔曼滤波的代码示例。这些示例程序可以帮助读者理解卡尔曼滤波的原理和应用,并且可以通过修改参数和增加噪声等操作,进行仿真实验,观察估计结果的变化。 通过使用Matlab编写随书程序,读者可以更好地理解卡尔曼滤波的算法,掌握卡尔曼滤波的实现方法,并且可以在实际应用中进行调试和参数优化。 总之,卡尔曼滤波原理和应用的随书程序是一种辅助学习和实验的工具,通过使用Matlab编写,可以更好地理解卡尔曼滤波的算法,并且可以进行仿真实验,优化参数,提高对系统状态的估计精度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值