现代C:极致优化(下):如何实现高性能的 C 程序?
引用在上一讲中我介绍了几个用于编写高性能 C 代码的实用技巧。今天我们继续聊这个话题来讨论其他几种常见的 C 代码和程序优化技巧它们分别是利用循环展开、使用条件传送指令、尾递归调用优化以及为编译器指定更高的编译优化等级。技巧五循环展开Loop Unrolling为了让你更好地理解“循环展开”这个优化技巧背后的原理我们先从宏观角度看看 CPU 是如何运作的。早期的 CPU 在执行指令时是以串行的方式进行的也就是说一个指令的执行开始需要等待前一个指令的执行完全结束。这种方式在实现上很简单但存在的问题也十分明显由于指令的执行是一个涉及多个功能单元的复杂过程而在某一时刻CPU 也只能够对指令进行针对当前所在阶段的特定处理。那么将 CPU 处理指令的流程划分为不同阶段并让它对多条指令同时进行多种不同处理这样是否可以进一步提升 CPU 的吞吐量呢事实正是如此。现代 CPU 为了进一步提升指令的执行效率通常会将单一的机器指令再进行拆分以达到指令级并行的目的。比如对于一个基本的五级 RISC 流水线来说CPU 会将指令的执行细分为指令提取IF、指令译码ID、指令执行EX、内存访问MEM以及寄存器写回WB共五个步骤。在这种情况下当第一条机器指令经过了指令提取阶段的处理后即使该条指令还没有被完全执行完毕CPU 也可以立即开始处理下一条机器指令。因此从宏观上来看机器指令的执行由串行变为了并行程序的执行效率得到了提升。其中指令提取是指从内存中读取出机器指令字节的过程。CPU 根据得到的指令字节在译码阶段从相应的寄存器中获得指令执行所需要的参数。而在执行阶段ALU 可以选择执行指令明确的操作或者是计算相关内存引用的有效地址等操作。随后在访存阶段根据指令要求CPU 可以将数据写回内存或从内存中读出所需数据。类似地在写回阶段CPU 可以将指令执行得到的结果存入寄存器。而当五个阶段全部执行完毕后CPU 会更新指令指针PC将其指向下一个需要执行的指令。你可以通过下图来直观地理解这个过程那么如何将 CPU 的吞吐量最大化呢相信你心中已经有了答案。我们需要做的就是让 CPU 在执行程序指令时能够以满流水线的方式进行。但现实情况并非总是这样理想。这里我要介绍的代码优化技巧“循环展开”便与此有关。让我们先来看一段代码#define LEN 4096 int data[LEN] { ... }; int foo(void) { int acc 1; for (int i 0; i LEN; i) { acc acc * data[i]; } return acc; }在这段代码中我们定义了一个名为 data 的全局整型数组并在其中存放了若干个值。而函数 foo 则主要用来计算该数组中所有数字的乘积之和。此时如果我们在 main 函数中调用函数 fooCPU 在实际执行它的代码时for 循环的每一轮都会产生两个数据相关循环控制变量 i 的下一个值依赖于本次循环变量 i 在经过自增运算后得到的结果值。同样地计数变量 acc 的下一个值也依赖于该变量在当前循环中经过乘积计算后的结果值。而这两个数据相关会导致 CPU 无法提前计算下一轮循环中各个参与变量的值。而只有在寄存器写回或内存访问阶段执行完毕也就是变量 acc 和 i 的值被最终更新后CPU 才会继续执行下一轮循环。那么应该如何优化这个过程呢我们直接来看优化后的代码#define LEN 4096 int data[LEN] { ... }; int foo(void) { int limit LEN - 1; int i; int acc0 1; int acc1 1; for (i 0; i limit; i 2) { // 2x2 loop unrolling. acc0 acc0 * data[i]; acc1 acc1 * data[i 1]; } for (; i LEN; i) { // Finish any remaining elements. acc0 acc0 * data[i]; } return acc0 * acc1; }可以直观地看到参与到程序执行的局部变量变多了。而这里的主要改动是我们为函数 foo 中的循环结构应用了 2x2 循环展开。循环展开这种代码优化技术主要通过增加循环结构每次迭代时计算的元素个数来减少循环次数同时优化 CPU 的指令集并行与流水线调度。而所谓的 2x2 是指在优化后的代码中循环的步长变为了 2且循环累积值被分别存放在两个独立变量 acc0 与 acc1 中。循环展开带来的最显著优化就是减少了循环的迭代次数。使用多个独立变量存储累积值各个累积值之间就不会存在数据相关而这就增大了 CPU 多个执行单元可以并行执行这些指令的机会从而在一定程度上提升了程序的执行效率。需要注意的是循环展开一方面可以带来性能上的提升另一方面它也会导致程序代码量的增加以及代码可读性的降低。并且编译器在高优化等级下通常也会对代码采用隐式的循环展开优化。因此在大多数情况下我们并不需要手动地改变代码形式来为它应用循环展开除非是在那些你确定编译器没有进行优化并且手动循环展开可以带来显著性能提升的情况下。技巧六优先使用条件传送指令通常来说CPU 指令集中存在着一类指令它们可以根据 CPU 标志位的不同状态有条件地传送数据到某个特定位置这类指令被称为“条件传送指令”。举个例子指令 cmove 接收两个参数 S 和 R当 CPU 标志寄存器中的 ZF 置位时该指令会将 S 中的源值复制到 R 中。与条件传送指令类似的还有另外一类指令它们被称为“条件分支指令”。顾名思义这类指令在执行时会根据 CPU 标志位的不同状态选择执行程序不同部分的代码。比如指令 jz 该指令接收一个参数 L当 CPU 标志寄存器中的 ZF 置位时该指令会将下一条待执行指令修改为 L 所在内存位置上的指令。对于 C 代码中的某些逻辑使用条件传送指令与条件分支指令都能够正确完成任务。但在程序的执行效率上这两种方式却可能带来极大的差别。而这主要是由于条件分支指令可能会受到 CPU 分支预测错误带来的惩罚。现代 CPU 一般都会采用投机执行其中的一个场景是处理器会从它预测的分支可能会发生跳转的地方取出指令并提前对这些指令进行译码等操作。处理器甚至会在还未确认预测是否正确之前就提前执行这些指令。之后如果 CPU 发现自己预测的跳转位置发生错误就会将状态重置为发生跳转前分支所处的状态并取出正确方向上的指令开始重新处理。由此上述两种指令在 CPU 的内部执行上便产生了不同。由于条件分支指令会导致 CPU 在指令实际执行前作出选择而当 CPU 预测错误时状态的重置及新分支的重新处理过程会浪费较多的 CPU 周期进而使程序的运行效率下降。相对地条件传送指令不会修改处理器的 PC 寄存器因此它不会导致 CPU 需要进行分支预测也就不会产生这部分损失。至于 CPU 是如何进行分支预测的相关内容超出了这门课的范畴这里我就不详细介绍了。但你需要知道的是在发生类似问题时我们可以进一步观察程序并尝试使用条件传送指令优化这些逻辑。为了方便你理解我们来看个例子。你可以看看下面这段代码中函数 foo 的实现细节#define LEN 1024 void foo(int* x, int* y) { int i; for (i 0; i LEN; i) { if (x[i] y[i]) { int t x[i]; x[i] y[i]; y[i] t; } } }函数 foo 接收两个整型数组 x 与 y并依次比较这两个数组中位于相同索引位置上的元素最后将较大者存放到数组 y 的对应位置上。我们可以看到在遍历数组的过程中我们在循环结构内使用了 if 语句来判断数组 x 中的元素值是否大于数组 y 对应位置上的元素。而在代码实际编译时if 语句通常会由对应的条件分支指令来实现。因此在循环结构的“加持”下由 CPU 分支预测错误引发的惩罚在经过每一轮迭代的累积后都可能会变得更加可观、更加明显。下面我们就来使用条件传送指令优化这段代码。条件传送指令一般会用于实现 C 语法中的三元运算符 ?:因此对上述代码的优化过程也就显而易见#include stdio.h #define LEN 16 void foo(int* x, int* y) { int i; for (i 0; i LEN; i) { int min x[i] y[i] ? x[i] : y[i]; int max x[i] y[i] ? y[i] : x[i]; x[i] min; y[i] max; } }可以看到这里我们没有使用 if 语句来判断是否应该调整两个数组对应位置上的数字值而是直接使用三元运算符来将每一次迭代时的最大值与最小值结果计算出来并拷贝到数组中的相应位置上。通过这种方式我们虽然解决了 CPU 分支预测失败带来的惩罚但与此同时每一次循环中也会多了几次比较与赋值操作。你可能想问这样的一来一回真的可以提升性能吗我的回答是不要小看 CPU 指令并行处理能力的高效性但也不要小看 CPU 分支预测带来的性能损耗。技巧七使用更高的编译优化等级除了可以通过调整代码写法来优化程序运行外我们还可以为编译器指定更高优化等级的选项来让编译器自动为我们进行更多程序执行细节上的优化。以 GCC 为例它为我们提供了 -O0、-O1、-O2、-O3、-Os、-Ofast 等优化选项。我把它们各自的大致优化内容整理成了一张表格你可以参考技巧八尾递归调用优化Tail-Call Optimization尾递归调用优化也是一个重要的代码优化技巧。关于它的原理和代码编写方式我已经在 06 讲中为你介绍过如果你觉得记忆有些模糊了可以返回那一讲回顾下相关知识。总的来看尾递归调用优化通过将函数的递归调用过程优化为循环结构减少了程序执行时对 call 指令的调用次数进而减少了栈帧的创建与销毁过程提升了程序的执行性能。并且你需要注意尾递归调用优化的效果在那些函数体本身较小且递归调用次数较多的函数上体现得会更加明显。总结讲到这里今天的内容也就基本结束了。最后我来给你总结一下。今天我主要介绍了四种可用于实现高性能 C 程序的技巧循环展开让我们可以进一步利用 CPU 的指令级并行能力让循环体执行得更快优先使用条件传送指令让我们可以在一些特定的场景中防止使用条件分支指令带来的 CPU 周期浪费使用更高的编译优化等级让我们可以借编译器之手利用更多“黑科技”进一步优化我们的代码尾递归调用优化让我们可以用循环代替递归减少函数调用时的栈帧创建与销毁过程让递归进行得更快。

相关新闻

深入解析PRU-ICSS UART:从波特率配置到DMA优化的嵌入式实战

深入解析PRU-ICSS UART:从波特率配置到DMA优化的嵌入式实战

1. 项目概述与核心价值在嵌入式系统开发,尤其是工业控制和实时处理领域,串行通信的可靠性与效率是项目成败的关键。通用异步收发传输器(UART)作为最经典、最普遍的串行接口,其底层配置与性能优化往往是工程师们需要啃下…

2026/10/6 11:46:00 阅读更多 →
你的 super() 为何“认错爹”?——Python 继承链上参数错误的致命幻觉与纠正术

你的 super() 为何“认错爹”?——Python 继承链上参数错误的致命幻觉与纠正术

你的 super() 为何“认错爹”?——Python 继承链上参数错误的致命幻觉与纠正术 在 Python 的面向对象编程中,super() 是我们调用父类方法的御用通道。它让多重继承中的协作成为可能,也让代码摆脱了硬编码父类名的噩梦。然而,super…

2026/10/4 17:33:52 阅读更多 →
智谱清言Prompt工程黄金公式(已验证217个真实业务场景):3步写出高召回+低幻觉指令,今天不学明天就掉队

智谱清言Prompt工程黄金公式(已验证217个真实业务场景):3步写出高召回+低幻觉指令,今天不学明天就掉队

更多请点击: https://codechina.net 第一章:智谱清言Prompt工程黄金公式的认知基石 Prompt工程不是技巧的堆砌,而是对大语言模型认知机制的深度共情。智谱清言(GLM系列模型)基于双向注意力与自回归解码协同架构&#…

2026/10/4 10:16:56 阅读更多 →

最新新闻

代码级对抗攻击:AST扰动如何绕过SAST与CI门禁

代码级对抗攻击:AST扰动如何绕过SAST与CI门禁

1. 这不是“黑客炫技”,而是代码层攻防的日常切片“Code-Level Adversarial Attacks 相关工作”——看到这个标题,很多人第一反应是:又一个AI安全论文里的抽象概念?其实不然。它背后是一群人在真实代码世界里反复拆解、注入、绕过…

2026/10/12 5:44:22 阅读更多 →
自然数立方与连续奇数之和:推导证明与Python验证

自然数立方与连续奇数之和:推导证明与Python验证

任何一个自然数 m,它的立方都可以写成 m 个连续奇数之和。这句话我第一次读到时,第一反应是:真的假的?当时正好在翻等差数列求和公式,索性拿纸笔列了一串奇数:1、3、5、7、9、11、13、15、17、19、21……然…

2026/10/12 5:44:22 阅读更多 →
最新Nessus2026.10.8版本主机漏洞扫描/探测工具Windows/Linux

最新Nessus2026.10.8版本主机漏洞扫描/探测工具Windows/Linux

前言 Nessus号称是世界上最流行的扫描程序,全世界有超过75000个组织在使用它。该工具提供完整的电脑扫描服务,并随时更新其数据库。Nessus不同于传统的扫描软件,Nessus可同时在本机或远端上遥控,进行系统的分析扫描。对应渗透测试…

2026/10/12 5:44:22 阅读更多 →
psutil详解:用Python搞定CPU、内存与进程监控

psutil详解:用Python搞定CPU、内存与进程监控

前阵子公司一台线上服务CPU突然飙到100%,我用系统自带的任务管理器看了半天,除了一个pid能锁定,剩下的信息全靠猜。后来发现这个进程是哪个服务的、占了多少内存、网络连了哪里,全是黑盒。那次之后我花了两天把psutil完整过了一遍…

2026/10/12 5:44:22 阅读更多 →
编程循环控制核心:break与continue用法详解与多语言实战对比

编程循环控制核心:break与continue用法详解与多语言实战对比

做开发这几年,几乎每个项目里都会碰上循环控制的问题。for 循环本身很简单,真正让新手挠头、让老手翻车的,往往是循环体里的那两个关键字:break 和 continue。很多新手写循环,要么不敢用 break 导致白白跑完全部数据&a…

2026/10/12 5:44:22 阅读更多 →
mediamtx v1.21.2发布:UDP、JWT、RTSP、RTMP、HLS、WebRTC全面修复,稳定性与安全性再提升

mediamtx v1.21.2发布:UDP、JWT、RTSP、RTMP、HLS、WebRTC全面修复,稳定性与安全性再提升

2026年10月10日,mediamtx 发布 v1.21.2 最新版本。本次更新以“修复与改进”为主,覆盖通用逻辑、API、Media-Over-QUIC、RTSP、RTMP、HLS、WebRTC 以及依赖库升级等多个方向。 v1.21.2 没有引入新的功能模块,而是集中处理实际运行中可能出现的…

2026/10/12 5:43:21 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →