深入 C 语言和程序运行原理
于航
PayPal 技术专家
21121 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
深入 C 语言和程序运行原理
15
15
1.0x
00:00/00:00
登录|注册

18|极致优化(上):如何实现高性能的 C 程序?

register 关键字
使用局部变量累积
指针唯一访问数据
多次调用
短小函数
缓存行组织
alignas 关键字
小循环体多迭代次数
数组行优先遍历
循环结构短步长访问
局部变量多次引用
空间局部性
时间局部性
优化示例
减少内存访问次数
使用限制
优化编译器生成机器码
避免 aliasing
宏内联
适用场景
减少函数调用开销
inline 关键字
数据对齐
优化策略
局部性原理
空闲时间与 IO 等待时间
用户与操作系统时间(CPU 时间)
程序总体运行时间
程序堆栈内存使用率
L1-L3 高速缓存使用率
主内存使用率
高速缓存的“抖动”
消除不必要内存引用
restrict 关键字
代码内联
利用高速缓存
运行时间
内存使用率
思考题
优化技巧
性能衡量指标
C 程序性能优化

该思维导图由 AI 生成,仅供参考

你好,我是于航。
我在开篇词中曾提到过,使用 C 语言正确实现的程序可以享受到最高的运行时性能。因此,如何编写具有“最高”执行性能的代码,是每个 C 程序员都在竭尽所能去探索的一个问题。那么,接下来的两讲,我们就来看看,如何编写高质量的 C 代码,来让我们的程序达到最佳的运行状态。
这一讲,我主要会为你介绍四个优化 C 代码的技巧,它们分别是利用高速缓存、利用代码内联、利用 restrict 关键字,以及消除不必要内存引用。

如何衡量程序的运行性能?

在开始正式介绍常用的性能优化技巧前,我们首先需要知道如何衡量一个应用程序的运行性能。
通常我们可以采用最简单和直观的两个宽泛指标,即内存使用率和运行时间。对于具有相同功能的两个程序,在完成相同任务时,哪一个的内存使用率更低,且运行时间更短,则程序的整体性能相对更好
我们可以将这两个指标再进一步细分。比如程序的内存使用率,可以细分为程序在单位时间内的主内存使用率、L1-L3 高速缓存使用率、程序堆栈内存使用率等指标。而运行时间也可以细分为程序总体运行时间(墙上时钟时间)、用户与操作系统时间(CPU 时间),以及空闲时间与 IO 等待时间等。
至于这些指标的制定和使用方式,属于性能监控的另一个话题,这里我们就不展开了。但你需要知道的是,无论程序多么复杂,运行时间和内存消耗这两个指标都是可用于观察程序性能情况的基本指标。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了优化 C 代码以实现高性能程序的技巧。首先,文章介绍了衡量程序性能的指标,包括内存使用率和运行时间,并对其进行了细分。接着,作者提出了四个优化 C 代码的技巧,分别是利用高速缓存、代码内联、`restrict` 关键字以及消除不必要内存引用。文章重点强调了高速缓存的重要性,以及如何通过满足局部性原理来优化代码,同时也介绍了代码内联的概念和对程序运行的影响。总的来说,本文为读者提供了一些实用的技术指导,帮助他们了解如何编写高性能的 C 代码。文章还提到了`restrict` 关键字的作用,以及消除不必要内存引用的优化方式。通过这些技巧,读者可以更好地理解如何优化 C 代码以提升程序性能。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入 C 语言和程序运行原理》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(7)

  • 最新
  • 精选
  • Geek_98aed8
    高速缓存的抖动: https://www.jianshu.com/p/3607c0f94526 函数中,因为CPU取块时,不同变量总不在同一个块中,导致每次都不命中,一直需要重新取;且反复取的块总是固定的几个,称为抖动 (本质是变量在内存中存放的方式不科学,这样理解?)

    作者回复: 这个理解是没错的,本质上是由于程序设计正好与所在平台的高速缓存策略(包括物理特性)产生冲突导致的。比如 CSAPP 中的一个例子: float foo(float x[8], float y[8]) { float sum = 0.0; int i; for (i = 0; i < 8; i++) sum += x[i] * y[i]; return sum; } 通常情况下,一个缓存行的大小即一个块大小。简单来看,假设在一共拥有 2 个高速缓存行(比如分在两个组),且每个缓存行大小为 16 字节的计算机中,上述代码逻辑在每一次交替访问数组 x 与 y 中的元素时,便可能会产生缓存抖动。但总的来看,缓存抖动在采用了“直接映射高速缓存”的体系中较为常见,而在“全相联高速缓存”及其他缓存策略中则相对较少发生。

    2022-02-12
    2
    2
  • 猪小擎
    void foo1(int *x, int *y, int *restrict z) { *x += *z; *z += 1; *y += *z; } int main(void) { int x = 10, y = 20, z = 30; foo1(&x, &y, &z); printf("%d %d %d", x, y, z); return 0; } O几都是 40,51,31.-Wall没有任何警告。如果编译器都不支持restrict的检查,这东西有意义吗?

    作者回复: 这段代码在 -O1 及以上的优化等级下应该就能看到,foo1 函数在使用与不使用 restrict 关键字时的区别。需要知道的是,若一个指针已被标记为 restrict ,但在实际使用时却发生了 aliasing,此时的行为是未定义的,编译器不保证会给出提示。

    2022-06-20
    1
  • qwerboo
    有没有一种可能,技巧四里dest因为总被访问,也会被cpu缓存。导致它其实并不是每次都会访问内存。

    作者回复: 是有这种优化可能的,但通常访问寄存器是快于访问高级缓存的。

    2022-06-07
  • 白凤凰
    (当从缓存行中间开始存放数据时,字段 y 可能需要占用三个缓存行)。上面那段代码便是如此。请问老师,上面这段代码是指 struct data { char x; alignas(64) char y[118]; }; 如果没有align(64),怎么就知道y是从缓存行中间开始存放的呢?不一定占用三个缓存行啊。

    作者回复: 是的,所以这里只是“可能”。因为对于这个 data 结构来说,由于它可以对齐到任意地址,因此编译器实际会如何布局,完全视程序整体实现而定。可以说是存在不确定的性能损耗风险。

    2022-01-24
  • i Love 3🍀
    请问一下,高速缓存的“抖动”和false-sharing是一回事吗?是不是都可以使用数据填充的方法来解决?

    作者回复: 看了一下你提到的 “False Sharing”,是的,两者的问题和解决方案实际上都是类似的,只不过是目标主体不同。一个是同个程序的不同代码,另一个是同个程序的不同线程。

    2022-01-24
  • Luke
    其实只要记住老师教的思路就行了,不一定每个关键字都要用起来,反而导致阅读代码的人费解。。。现代编译器挺强大了,时刻在脑海里记住局部性的原则,短平的循环体这些,优秀的习惯就会是很自然的事情了😁
    2022-09-22归属地:江苏
    3
  • ZR2021
    666,减少内存引用还是我头一次见到的优化方法,多谢老师!!!
    2022-01-25
    1
收起评论
显示
设置
留言
7
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部