当前位置: 首页 > news >正文

优化程序中的数据:从数组到代数

前言

我们往往都希望优化我们的程序,使之达到一个更好的效果,程序优化的一个重点就是速度,加快速度的一个好办法就是使用并行技术,但是,并行时我们要考虑必须串行执行的任务,也就是有依赖关系的任务,任务中的重点往往是具体的数据,这些任务中的数据通常具有局部性和关联性。

而数据中数组具有代表性,现在,让笔者从数组开始,谈谈程序数据的优化。

从数据的存储内存开始

我们都知道计算机的基本内存结构如下:

处理器
cache高速缓存
多层cache
总线
内存

而内存的结构又可以继续划分:

速度从慢到快
虚拟内存-磁盘
物理内存
二级cache
一级cache
寄存器

虚拟内存是一个很伟大的发明,它借助内存管理单元(MMU),并利用分页机制将磁盘的一部分模拟为内存使用。它允许计算机使用硬盘空间来扩展实际的物理内存。这使得操作系统能够运行超过实际物理内存容量的程序。

而我们重点关注的地方在cache这里。

cache命中率

cache会从更低一级的内存结构中搬数据,如果数据访问是局部性很强(如访问同一数据块多次),则缓存命中率会较高,如果不命中,那么计算机会跑到下一级内存中寻找数据,这样程序运行效率就会非常低。

优化

得知了这一点后,我们可以考虑改善我们的程序写法了,以数组操作为例:

for(int i = 0; i<= 2; i++){for(int j = i; j<= 2; j++)Z[j][i] = 0;
}

在C语言中,二维数组的内存分布通常是按行优先(Row-major order)存储的,这意味着数组的行是连续存储在内存中的。具体来说,对于一个二维数组 Z,其内存布局是按以下方式排列的:

二维数组的内存分布

假设我们有一个二维数组 Z,其大小为 mn 列。数组元素在内存中的排列顺序如下:

Z[0][0], Z[0][1], ..., Z[0][n-1], Z[1][0], Z[1][1], ..., Z[1][n-1], ..., Z[m-1][0], ..., Z[m-1][n-1]

每一行的元素是连续存储的,然后依次存储下一行的元素。

那么,优化后的遍历方法如下:

for (int j = 0; j <= 2; j++) {for (int i = 0; i <= j; i++) {Z[j][i] = 0;}
}

上面的优化方法相信大家都能琢磨出来,但是,如果稍微改一下呢?

for(int i = 0; i<= 5; i++){for(int j = i; j<= 7; j++)Z[j][i] = 0;
}

按照原程序的遍历:

┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ 0,0 │ 1,0 │ 2,0 │ 3,0 │ 4,0 │ 5,0 │ 6,0 │ 7,0 │
│     │ 1,1 │ 2,1 │ 3,1 │ 4,1 │ 5,1 │ 6,1 │ 7,1 │
│     │     │ 2,2 │ 3,2 │ 4,2 │ 5,2 │ 6,2 │ 7,2 │
│     │     │     │ 3,3 │ 4,3 │ 5,3 │ 6,3 │ 7,3 │
│     │     │     │     │ 4,4 │ 5,4 │ 6,4 │ 7,4 │
│     │     │     │     │     │ 5,5 │ 6,5 │ 7,5 │
│     │     │     │     │     │     │ 6,6 │ 7,6 │
│     │     │     │     │     │     │     │ 7,7 │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

更好的遍历方法:

┌─────┬─────┬─────┬─────┬─────┬─────
│ 0,0 │ 										
│ 1,0 │ 1,1 │  									
│ 2,0 │ 2,1 │ 2,2 │  
│ 3,0 │ 3,1 │ 3,2 │ 3,3 │ 
│ 4,0 │ 4,1 │ 4,2 │ 4,3 │ 4,4 │ 
│ 5,0 │ 5,1 │ 5,2 │ 5,3 │ 5,4 │ 5,5 
| 6,0 | 6,1 | 6,2 | 6,3 | 6,4 | 6,5 
| 7,0 | 7,1 | 7,2 | 7,3 | 7,4 | 7,5 
└─────┴─────┴─────┴─────┴─────┴─────

局部性更好的程序如下,此时想要一眼看出来这样写就有点困难了,那我们要怎么推导数组的遍历式呢:

for (int j = 0; j <= 7; j++) {for (int i = 0; i <= (j < 5 ? j : 5); i++) {Z[j][i] = 0;}
}

引入线性代数

我们先看看各种值的范围:

i的范围: i>=0, i<=5
j的范围: j>=i, j<=7

尝试把它们写成线性方程:

1*i + 0*j + 0 >= 0
-1*i + 0*j + 5 >= 0-1*i + j + 0 >= 0
0*i + -1*j + 7 >= 0

矩阵如下:

|  1  0 |   | i |   >=   | 0 |
| -1  0 | * | j |   >=   | -5 |
| -1  1 |           >=   | 0 |
|  0 -1 |           >=   | -7 |

现在我们得到了矩阵,我们可以进一步得到多面体,先回顾一下矩阵与多面体的关系:

线性约束表示多面体

多面体可以通过一组线性不等式来定义,这些不等式可以表示为矩阵和向量的形式。例如,对于一个包含 n个变量的多面体,可以用一个 m×n 的矩阵A和一个m维的向量 b来表示:

Ax <= b

其中,x是变量向量,约束条件定义了多面体的边界。

顶点表示

多面体的顶点可以通过求解线性方程组(通常涉及矩阵的逆或者伪逆)来获得。这些顶点是满足约束条件的解。

矩阵操作多面体

线性变换

通过矩阵乘法,可以对多面体进行线性变换(如旋转、缩放、平移等)。例如,如果矩阵M描述了一个线性变换,那么多面体中的每一个点 x在变换后的新位置可以表示为Mx。

仿射变换

仿射变换是线性变换的推广,包括线性变换和平移。可以用如下形式表示:

y=Mx+t

其中,MM 是线性变换矩阵,t是平移向量。

好吧,其实矩阵和多面体与接下来要讲的算法也没多大关系,笔者只是想说明如何从不等式推导到线性代数并扩展到多面体和高维空间体的。

使用Fourier-Motzkin算法

Fourier-Motzkin算法是一种经常在多面体中用于求解线性不等式系统的消去算法,概括如下:

选择消去变量: 选择一个变量 xi作为消去变量。

分类不等式: 将所有不等式分为三类:

  • 包含 Xi的不等式,且 Xi的系数为正。
  • 包含 Xi的不等式,且 Xi的系数为负。
  • 不包含 Xi的不等式。

生成新不等式: 通过将第一类不等式和第二类不等式配对,消去 Xi

组合不等式: 将生成的新不等式与不包含 xi的不等式组合,得到一个新的线性不等式系统。

重复步骤: 对新的线性不等式系统重复上述步骤,直到所有变量都被消去。

应用该算法,我们重新得到范围:

0<=j, 0 <=5
j<=7

那么i和j的范围如下:

L(i):0
U(i):5,jL(i):0
U(J):7

有了这个范围,我们可以得到:

for (int j = 0; j <= 7; j++) {for (int i = 0; i <= min(5,j); i++) {Z[j][i] = 0;}
}

也就是:

for (int j = 0; j <= 7; j++) {for (int i = 0; i <= (j < 5 ? j : 5); i++) {Z[j][i] = 0;}
}

总结

从程序中的优化出发,由程序存储引出cache,再由cache命中率引出数据局部性的重要性,为了提高数据局部性,必须改变循环遍历方法。为了改变循环遍历方法,由不等式引出线性代数,再由线性代数引出多面体,最后使用算法计算约束,得到具有良好局部性的程序。
其实没啥好总结的,只写了一小段,还没写完开头呢,不过先更到这,该上床睡觉了。

http://www.lryc.cn/news/508427.html

相关文章:

  • 【电商搜索】CRM: 具有可控条件的检索模型
  • 使用 ffmpeg 拼接合并视频文件
  • 【信号滤波 (上)】傅里叶变换和滤波算法去除ADC采样中的噪声(Matlab/C++)
  • Idea内,光标显示问题
  • 回顾 python3中字符串
  • 代码随想录day23 | leetcode 39.组合总和 40.组合总和II 131.分割回文串
  • 全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
  • R机器学习:决策树算法的理解与实操
  • 解锁高效学习之道:从认知升级到实践突破
  • 2024年12月CCF-GESP编程能力等级认证Python编程三级真题解析
  • .NET Core 中使用 C# 获取Windows 和 Linux 环境兼容路径合并
  • 【SH】Ubuntu Server 24服务器搭建MySQL数据库研发笔记
  • 编译原理复习---正则表达式+有穷自动机
  • 知识图谱+RAG学习
  • 消息队列技术的发展历史
  • 每天40分玩转Django:Django部署
  • 搭建Elastic search群集
  • 解析 Ingress-Nginx 故障:排查思路与方法
  • 2024 楚慧杯 re wp
  • 【物联网技术与应用】实验10:蜂鸣器实验
  • 单片机:实现矩阵键盘控制LCD屏幕(附带源码)
  • 鸿蒙Next之包体积极限优化
  • Android实战经验篇-log工具
  • DPU编程技术解析与实践应用
  • 红帽认证的含金量和价值如何?怎么报名红帽认证考试?
  • VS Code Copilot 与 Cursor 对比
  • 蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)
  • 取多个集合的交集
  • 如何实现电子发票XML文件的合规性存档?
  • IOT、MES、WMS、MOM 和 EPMS 系统综合技术与业务文档