C语言jiesheng是什么?为何要学习它?

99ANYc3cd6
预计阅读时长 14 分钟
位置: 首页 C语言 正文

下面我将从内存、CPU、磁盘、代码本身等多个维度,为您详细讲解如何在C语言中进行“节省”(优化)。


核心原则:先分析,再优化

在进行任何优化之前,最重要的一步是分析瓶颈,不要凭感觉猜测哪里慢、哪里占内存多,要用工具来验证。

  • 内存分析工具valgrind (特别是它的 massifmemcheck 工具),gprof,平台自带的性能分析器。
  • CPU性能分析工具perf (Linux), Instruments (macOS), Visual Studio Profiler (Windows)。

记住一句名言:过早的优化是万恶之源。 确保你的代码是正确和清晰的,然后再针对分析出的瓶颈进行针对性优化。


节省内存

内存是程序运行时最主要的资源消耗之一。

选择合适的数据类型

这是最基本也是最有效的方法之一,不要无脑使用 intlong

  • 使用最小够用的类型

    • 如果一个变量只存储 0-255,用 uint8_t (在 <stdint.h> 中定义) 代替 int,前者只占1字节,后者通常占4字节。

    • 如果变量范围是 -3276832767,用 int16_t 代替 int

    • 示例

      #include <stdint.h>
      // 差:浪费了3个字节
      int student_count = 30; 
      // 好:精确使用,节省内存
      uint8_t student_count = 30; 
  • 注意 char vs int

    • char 通常占1字节,适合存储字符或小整数。
    • int 通常用于算术运算,因为它在CPU上进行运算时效率更高,如果一个变量需要频繁进行加减乘除,用 int 可能比用 uint8_t 更快,因为CPU一次操作一个 int(4字节)比操作4次 char(1字节)更高效,这是一个空间换时间的典型权衡。

结构体优化

结构体的内存布局对性能和内存占用有巨大影响。

  • 成员变量对齐

    • 编译器为了提高内存访问效率,会进行“内存对齐”,一个 struct { char c; int i; }char 后面可能会填充3个字节,然后才放 int,导致整个结构体占用8字节,而不是理论上5字节。
    • 优化方法:将类型按从大到小排列。struct { int i; char c; } 会占用8字节,而 struct { char c; int i; } 也可能占用8字节,但将大的类型放在一起可以减少“内部碎片”。
    • 使用 #pragma pack:可以强制取消对齐,但这会严重影响性能,仅在需要极致节省内存且对性能要求极低的情况下使用,如网络数据包的解析。
  • 使用位域

    • 如果结构体成员只需要几个比特(如开关状态、标志位),可以使用位域来压缩它们。
    • 示例
      struct Flags {
          uint8_t is_enabled : 1;   // 只占1位
          uint8_t is_visible : 1;   // 只占1位
          uint8_t padding   : 6;   // 剩下的6位
      };
      // 整个结构体只占用1个字节,而不是3个字节。

避免内存浪费

  • 精确计算数组大小:避免定义过大的静态数组。

    • int scores[1000]; // 如果实际只有100个学生,浪费了900个int的空间。
    • int scores[student_count]; // 使用动态内存或宏来定义精确大小。
  • 及时释放内存:使用 malloc/calloc 分配的内存,在不需要时必须用 free 释放,否则会造成内存泄漏。

共享内存

  • 使用指针和引用:当需要在多个地方访问同一块数据时,使用指针传递,而不是复制整个数据结构,这是C语言高效的核心。
  • 全局数据:对于在整个程序生命周期中都存在的数据,可以放在全局或静态存储区,避免重复创建和销毁。

节省CPU(提升性能)

节省CPU时间通常意味着代码执行得更快,这在很多场景下等同于“节省资源”。

算法与数据结构优化

这是最重要的性能优化手段,一个O(n²)的算法在数据量大时,无论怎么优化代码细节,都不如一个O(n log n)的算法。

  • 选择正确的数据结构
    • 需要频繁查找?用 哈希表 (平均O(1))。
    • 需要有序插入和查找?用 平衡二叉搜索树 (如红黑树, O(log n))。
    • 需要按顺序访问?用 数组 (O(1))。
    • 需要频繁在头部/中间插入/删除?用 链表 (O(n))。
  • 库函数:C标准库中的函数(如 qsort)通常是经过高度优化的,比自己实现的版本更快。

循环优化

  • 减少循环内的计算:将循环中不变的计算移到循环外面。

    • for (int i = 0; i < n; i++) {
          result += array[i] * (2 * 10); // 2*10 每次都算
      }
    • int constant = 2 * 10;
      for (int i = 0; i < n; i++) {
          result += array[i] * constant;
      }
  • 循环展开:手动减少循环次数,每次迭代处理多个数据,现代编译器通常会自动进行循环展开,所以手动优化可能效果不大,甚至可能影响编译器优化。

编译器优化

相信编译器,它是你的朋友。

  • 开启优化等级:在编译时使用 -O1, -O2, -O3 等选项。-O2 是速度和代码大小之间的良好平衡。
    gcc -O2 my_program.c -o my_program
  • 使用 conststatic
    • const:告诉编译器变量的值不会变,编译器可以进行更好的优化。
    • static:对于函数内部的变量,static 会让它在整个程序生命周期中只初始化一次,而不是每次进入函数都初始化,对于函数,static 限制其作用域在当前文件,有助于编译器进行优化。

避免昂贵的操作

  • 减少函数调用开销:对于非常短小且频繁调用的函数,可以考虑使用宏(#define)或 inline 关键字来建议编译器内联展开,消除函数调用的开销。

    • 注意:滥用 inline 会导致代码体积膨胀,需要权衡。

    • 示例

      // 使用宏 (注意参数可能被多次求值,有风险)
      #define MAX(a, b) ((a) > (b) ? (a) : (b))
      // 使用 inline (更安全)
      static inline int max(int a, int b) {
          return (a > b) ? a : b;
      }

节省磁盘空间

这部分主要针对编译出的可执行文件或库。

  • 编译优化:使用 -Os (优化代码大小) 选项,告诉编译器优先生成体积更小的代码。
    gcc -Os my_program.c -o my_program
  • 移除调试信息:发布版本中,使用 -s 选项移除所有符号和调试信息。
    gcc -Os -s my_program.c -o my_program
  • 使用 strip 命令:对已编译好的可执行文件进行“剥离”,移除调试符号。
    strip my_program

代码层面的“节省”

这里的“节省”指的是让代码更简洁、更易维护,从而节省开发时间和维护成本。

  • 模块化:将大函数拆分成小函数,每个函数只做一件事,这提高了代码的可读性、可复用性和可测试性。
  • 使用标准库:不要重复造轮子,C标准库提供了大量经过验证和优化的功能。
  • 清晰的命名:使用有意义的变量名和函数名,让代码自己“解释”自己。
  • 减少代码重复:使用函数、宏或配置来消除重复代码。
优化维度 核心方法 工具/技术
内存 选择合适数据类型 (int8_t, uint32_t),结构体对齐,位域,及时 free <stdint.h>, valgrind
CPU 优化算法与数据结构,减少循环内计算,开启编译器优化 (-O2),使用 inline qsort, perf, -O2, inline
磁盘 编译时优化代码大小 (-Os),移除调试信息 (-s, strip) gcc -Os, strip
代码 模块化,清晰命名,使用标准库,减少重复 良好的编程习惯

从“jiesheng”(节省)的角度来看,算法和数据结构的选择是投入产出比最高的优化,其次是合理的数据类型选择和编译器优化,其他的优化手段则应根据具体的性能分析结果,在必要时才进行。

-- 展开阅读全文 --
头像
一汽车织梦官网,如何实现汽车与梦想的编织?
« 上一篇 04-14
dede导航栏如何实现静态化?
下一篇 » 04-14

相关文章

取消
微信二维码
支付宝二维码

目录[+]