C优化篇之优化内存访问

    目前CPU运行速度远超过内存访问速度,且从趋势看这种速度差距还会越拉越大,提高内存访问效率将是软件优化重要而长期的课题。内存访问优化的一般性措施可大体分两方面:1)减少内存访问;2)调整代码使程序集中顺序地访问内存。

一、减少内存访问的措施包括:

a.充分利用寄存器

    充分利用寄存器缓存数据,是减少内存访问的思路之一。C程序编译后哪些元素由寄存器存储,哪些又会放进内存,取决于CPU以及对应的编译器规范。以ARM为例,对于遵循ATPCS规则的编译器:

    1)函数前4个参数放在寄存器里,超出4个则压入栈内存。

    2局部变量如果没有取址操作,或有取址但未赋给其他变量,就会被编译器优先安排寄存器存储,如寄存器已占完,则开始在寄存器和栈之间交换存储。

不同CPU及编译器有类似规范,注意下面几点能更充分地利用寄存器:

    1)如果函数参数过多,把多个参数组织成结构体,传递结构体指针。在很多平台上,这样能减少参数入栈的几率。

     2)register提示编译器把关键变量或循环内变量用寄存器缓存,register暗示编译器变量将被频繁使用,应将其保存在寄存器中,以加快其存储速度,但要注意它仅仅是个提示,很多时候编译器并不鸟它。

    3)把某些大函数拆分成小函数,防止因寄存器不足导致局部变量在栈和寄存器之间反复存取,类似内存和硬盘间的内容交换,浪费时间。大函数内局部变量多,情况复杂,编译器无法分析清每个局部变量的作用范围,常常做出很多无用的压栈出栈操作。

    4)如某热点函数内经常访问全局变量,可添加一个临时局部变量,诱导编译器将该全局变量内容读到寄存器中作为其影子,对寄存器进行相关操作,最后赋回全局变量,以减少内存访问。如:

    long product;

    void factorialA(long n)

    {

     long i;

     for(i = 1; i <= n;i++ ){  product *= i;  }

    }

    void factorialB(long n)

    {

     long i

      long x = 1;

     for(i = 1; i <= n;i++ ){  x *= i;  }

     product = x;

    }

    n较大时,上面两个函数性能有显著差别,这就是充分利用寄存器的好处。

    5)避免局部变量取址, 编译器处理局部变量时一般先尽量用通用寄存器缓存,但如果有局部变量取址操作,意味着该变量只能放在栈内存(通用寄存器没有内存地址概念)。如果该局部变量在循环中多次读写,此时也同样可考虑增加中间变量,用完后再写回。比如下例改动就能提高整体效率:

    void f(int *a);

    int g(int a);

    int test1(int i)

    {

      int j;

      f(&i);

      for(j =0;j<1000;j++)

         i += g(i);

      return i;

    }

修改后

    int test2(int i)

   {

      int temp = i;

      f(&temp);

      i = temp;

      for(j =0;j<1000;j++)

        i += g(i);

      return i;

    }

    test2中使用了变量的拷贝temp,把temp的地址传入函数f(),函数f()退出时再把temp回赋给i,这样变量i不存在取址操作,编译器就能把它用寄存器保存。这里循环内对i有数千次访问,循环体中的i放在寄存器相比放在栈内存,效率差别相当大。

b.消除指针链

    访问多级结构体成员变量时常要使用指针链,如:

    typedef struct { int x, y, z; }point;

    typedef struct {point *pos, *direct; }obj;

    void InitPos(obj *p)

    {

      p->pos->x = 0;

      p->pos->y = 0;

      p->pos->z = 0;

    }

    如果编译器不能确定p->pos->x不是p->pos的别名,代码中每次赋值操作都要重新访问p->pos(了解下restrict)。所以最好是手动把p->pos存到一个局部变量,改为:

    void InitPos(obj *p)

    {

      point *pos = p->pos;

      pos->x = 0;

      pos->y = 0;

      pos->z = 0;

    }

    这样只需一次p->pos内存访问,比之前省了两次。而且这不是节省两条普通指令,而是两条访问随机内存的指针链操作。

二、集中连续访问内存包括:

a.合理安排和调整循环次序

    循环中的内存访问多数都是性能热点,是连续集中还是断续分散访问,很大程度影响系统性能。有时仅仅调整循环次序,使分散内存访问变得连续,就能大幅提升性能。如:

    for(i=0;i<N;i++)

      for(j=0;j<N;j++)

        A(j,i) = B(j,i) + C(j,i) * D

变为:

    for(j=0;j<N;j++)

      for(i=0;i<N;i++)

        A(j,i) = B(j,i) + C(j,i) * D

    交换后, A, B, C均按其在内存中的排列顺序依次被访问,而不是像之前那样跳跃式访问。访问内存就象逛街购物,好不容易出来一次,自然要尽量把需要的东西都买回去,否则又要多跑,这中间需要时间代价。

b.使用连续内存的数据结构

    链表结构相比数组,对内存的占用更少且更灵活,但在内存访问密集型的应用中却会导致性能的明显下降,因为访问链表节点是分散随机地访问内存。与之对应访问数组内存则相对集中且连续,所以如果对链表的随机访问成为性能阻碍,不妨考虑用数组代替链表,或者从预分配的大块连续内存上分配链表节点,而不是用malloc随机申请内存块。注意:无论cachenon-cache系统中,把离散内存访问变为连续内存访问都能提高系统性能。至于如何写出cache-friendly的代码则是更高级的主题。

 

以上只是从内存角度出发,C语言级优化的几个基本着眼点,仅为大家抛砖引玉。

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
优化内存的方法有很多种,以下是一些常用的方法: 1. 使用动态内存分配:C语言中可以使用malloc()和free()函数动态地分配和释放内存。使用动态内存分配可以根据实际需要动态地分配和释放内存,避免内存浪费和内存泄漏。 2. 及时释放内存:在程序运行过程中,如果不再需要某个变量或者数据结构,应该及时释放相应的内存空间。这样可以避免内存浪费和内存泄漏,提高内存使用效率。 3. 使用缓存机制:在程序运行过程中,可以使用缓存机制来减少对内存的频繁访问。例如,可以将频繁访问的数据缓存到内存中,避免反复从磁盘或者网络中读取数据。 4. 压缩内存空间:在程序运行过程中,如果某个数据结构包含大量的空洞或者冗余数据,可以使用压缩算法来减少内存空间的占用。 下面是一个使用动态内存分配的示例代码: ```c #include <stdio.h> #include <stdlib.h> int main() { int n; int *ptr; // 读入 n 的值 scanf("%d", &n); // 动态分配 n 个 int 类型的内存空间 ptr = (int *)malloc(n * sizeof(int)); if (ptr == NULL) { printf("Memory allocation failed.\n"); return 1; } // 使用 ptr 指向的内存空间 // ... // 释放 ptr 指向的内存空间 free(ptr); return 0; } ``` 在这个示例代码中,我们使用了malloc()函数动态地分配了n个int类型的内存空间,并在使用完后使用free()函数释放了这些内存空间。这样可以避免静态分配内存的浪费和动态分配内存的不足,从而优化内存使用效率

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值