FFmpeg 汇编语言课程第一课精读:寄存器体系、x86inc.asm 与手写 SIMD 向量函数
教程文档【免费下载链接】asm-lessonsFFmpeg Assembly Language Lessons项目地址https://gitcode.com/GitHub_Trending/as/asm-lessons点击查看免费下载本篇文章基于本仓库 FFmpeg School of Assembly Language 课程系列的第一课lesson_01/index.fr.md同时提供 英文版、西班牙语版、土耳其语版 与 中文版展开。课程面向希望深入 FFmpeg 多媒体处理内核的开发者系统讲解汇编语言在 FFmpeg 中的真实写法从什么是汇编/SIMD、寄存器体系到借助 x86inc.asm 编写第一个可运行的 SIMD 向量函数。读完本文你将掌握 FFmpeg 手写汇编的基本术语、Intel 语法的阅读方式、XMM 向量寄存器的数据布局并能够逐行读懂一个真实的向量加法内核。课程概览与前置知识本课程是 FFmpeg 官方社区汇编语言教学系列的第一课目标是给你打下汇编语言在 FFmpeg 中如何被使用的基础并让你看清计算机里真正发生着什么。课程将贯穿多条课目配套练习assignments对应发布当前仓库 README 说明练习尚未上传全部课程完成后你将具备向 FFmpeg 提交汇编代码的能力。学习本课需要具备两项前置知识C 语言基础尤其是指针汇编与 C 的互操作、地址解引用都依赖指针概念高中数学水平标量scalar与向量vector、加法、乘法等基本运算概念。课程作者在文档开头特别强调在 FFmpeg 社区里甚至高中生都写过汇编代码——学习这门语言一半是术语一半是真正的学习难度并不像外界渲染的那样高不可攀。汇编语言与 SIMD先弄清核心概念汇编语言assembly language是一种让你书写的代码与 CPU 实际处理的指令一一对应的编程语言。人类可读的汇编文本经过汇编器assembler的汇编assembled过程转换为 CPU 可以直接理解的二进制数据即机器码machine code。汇编代码也常被简称为 assembly 或 asm。FFmpeg 中绝大多数汇编代码属于SIMDSingle Instruction Multiple Data单指令多数据有时也被称为向量编程vector programming一条指令同时作用于多个数据元素。与之相对大多数编程语言一次只处理一个数据元素称为标量编程scalar programming。SIMD 之所以非常适合图像、视频和音频处理是因为这类数据在内存中按顺序大量排列而 CPU 内部正好有专门的指令可以高效处理这种顺序数据。在 FFmpeg 中以下术语几乎可以互换使用含义完全相同——手写一个汇编函数一次性处理多个数据元素汇编函数assembly functionSIMD向量化vectorise汇编内核assembly kernels部分项目使用为什么 FFmpeg 坚持手写汇编课程从三个层面回答了为什么不用更省事的方式1. 性能多媒体处理的核心诉求手写汇编在多媒体场景下获得10 倍甚至更高的速度提升非常常见这对实时无卡顿播放视频至关重要同时它还能节省能耗、延长设备电池寿命。更重要的是视频编码和解码函数是地球上被调用最频繁的函数之一——无论终端用户还是大型公司的数据中心都在高频使用因此哪怕是微小的性能改进累加起来也非常可观。2. 为什么不使用 intrinsics网上经常能看到开发者使用intrinsics类似 C 语言、但实际映射到汇编指令的函数目的是加快开发速度。FFmpeg 的选择恰恰相反不使用 intrinsics而是全部手写汇编。课程给出的理由包括intrinsics 通常比手写汇编慢约 10%15%取决于编译器intrinsics 的支持者对此有异议课程原文也如实标注了这一争议对 FFmpeg 而言每一分额外性能都很重要intrinsics 代码因使用匈牙利命名法Hungarian Notation而难以阅读这被视为其劣势之一。3. 为什么不依赖编译器自动向量化网上不少自封的专家声称编译器可以自动完成向量化、手写毫无必要。课程的建议是至少在学习的阶段忽略这些说法。以 dav1d 项目的近期测试为例编译器自动向量化大约只能带来2 倍左右的加速而手写版本可以达到8 倍。补充内联汇编inline assembly为何被冷落FFmpeg 中仍有个别地方出于历史原因保留了内联汇编inline assembly即不放在独立汇编文件、而是直接混写在 C 代码中的汇编Linux 内核等项目中也有针对特定场景的使用。但 FFmpeg 这类项目的主流观点是这种代码难以阅读、编译器支持不广泛、难以维护。因此本课程教学的核心是独立汇编文件中的手写汇编而非内联汇编。目标平台与语法x86-64 Intel 语法课程系列将聚焦于x86 64 位汇编即常说的amd64虽然名字带 amd它在 Intel CPU 上同样适用。其他 CPU 架构如 ARM、RISC-V各有自己的汇编语言课程未来可能扩展覆盖。x86 汇编在网络上存在两种语法流派ATT 语法更古老相对难读Intel 语法更清晰易读。本课程统一采用Intel 语法这也是 FFmpeg 手写汇编使用的语法。需要特别注意的是Intel 语法中源操作数在右、目的操作数在左行为类似 C 的memcpy即把右边的数据拷贝到左边例如mov r0q, 3可以直读为r0q 3。参考资料与学习建议课程给出了一条略显反直觉的建议像 Stack Overflow 这类在线资源和大多数书籍对学习 FFmpeg 汇编帮助有限。原因有二本课程采用手写汇编 Intel 语法本身已偏离许多主流资料大量在线资料聚焦于操作系统编程或硬件编程通常不使用 SIMD 代码。FFmpeg 汇编的核心是高性能图像处理课程采用的是非常独特的汇编编程路径。正因如此作者建议许多书籍会先花大量篇幅讲计算机体系结构再讲汇编——如果你就是想学体系结构那无可厚非但对本课程而言这就像在学开车之前先去研究发动机。不过有一本参考书例外The Art of 64-bit AssemblyRandall Hyde后半部分用可视化图形展示 SIMD 指令及其行为对理解指令效果非常有帮助课程将其列为推荐参考。此外课程配有 Discord 服务器用于答疑入口见 README.md 和课程文档。寄存器基础GPR 与向量寄存器寄存器registers是 CPU 内部可以处理数据的区域。CPU 并不直接对内存进行操作而是遵循加载到寄存器 → 处理 → 写回内存的流程。在汇编中一般来说你无法直接把数据从一个内存位置拷贝到另一个内存位置数据必须先经过寄存器中转。通用寄存器GPR第一种寄存器是通用寄存器General Purpose Register, GPR。之所以叫通用是因为它可以存放两类东西数据最大 64 位的值内存地址即指针。GPR 中的值可以通过加法、乘法、移位等操作进行运算。大多数汇编书籍会用整章篇幅讲解 GPR 的细节与历史——这是因为 GPR 在操作系统编程、逆向工程等领域非常重要。但在 FFmpeg 的手写汇编中GPR 更像脚手架大多数时候其复杂细节并不需要会被 x86inc.asm 等机制抽象掉下文详述。向量SIMD寄存器向量寄存器顾名思义一个寄存器里可以同时容纳多个数据元素。本课程介绍了几种常见的向量寄存器寄存器名称位宽现状mmMMX64 位历史产物如今很少使用xmmXMM128 位广泛可用ymmYMM256 位使用时有若干注意事项zmmZMM512 位可用性非常有限视频压缩与解压缩中的绝大多数计算都是基于整数的因此本课程将围绕整数运算展开。以 16 字节的 XMM 寄存器为例它既可以被看作 16 个独立的字节abcdefghijklmnop也可以被看作 8 个 word16 位整数abcdefgh或 4 个 doubleword32 位整数abcd或 2 个 quadword64 位整数ab数据类型尺寸速查以下加粗的字母后缀在后续课程中会反复出现在指令助记符里务必牢记bytes —— 8 位数据words —— 16 位数据doublewords —— 32 位数据quadwords —— 64 位数据doublequadwords —— 128 位数据课程原文特别提醒这些加粗的字符在接下来的内容中非常重要——它们正是paddb、movdqu、punpcklbw等指令中b/w/d/q后缀的含义来源。x86inc.asmFFmpeg 汇编开发者的抽象层在很多示例中你都会看到%include x86inc.asm。x86inc.asm是 FFmpeg、x264、dav1d 共同使用的轻量抽象层作用是让汇编程序员的生活更轻松。它的能力很多本课首先接触到的一项关键能力是为 GPR 贴上r0、r1、r2这样的标签这样你就不必记忆具体寄存器的名字。正如前文所说GPR 在 FFmpeg 汇编中通常只是脚手架这种抽象让代码大幅简化。第一个标量汇编片段看懂一条指令在做什么在进入 SIMD 之前课程先用一段纯人工构造的标量汇编片段每条指令只处理一个数据元素演示基本指令mov r0q, 3 inc r0q dec r0q imul r0q, 5逐行解读mov r0q, 3将立即数immediate value3 存入寄存器r0以 quadword 形式。立即数是指直接写在汇编代码里的值而不是从内存中取出的值。注意 Intel 语法中源操作数提供数据的值或位置在右被传送到目的操作数接收数据的位置在左行为类似memcpy你也可以直接读作r0q 3。r0的q后缀表示该寄存器按 quadword64 位使用inc r0q自增r0q变为 4dec r0q自减r0q回到 3imul r0q, 5乘以 5。最终r0q中保存的值为15。这里还引入了一个术语mov、inc这类人类可读、由汇编器转换为机器码的指令称为助记符mnemonics。网上和书籍中常以大写形式MOV、INC出现它们与小写形式完全等价。FFmpeg 的惯例是小写助记符、大写保留给宏使用。第一个 SIMD 向量函数 add_values 逐行精读下面这段是课程的第一个 SIMD 函数功能是把两个输入缓冲区src与src2中对应的字节相加结果写回src%include x86inc.asm SECTION .text ;static void add_values(const uint8_t *src, const uint8_t *src2) INIT_XMM sse2 cglobal add_values, 2, 2, 2, src, src2 movu m0, [srcq] movu m1, [src2q] paddb m0, m1 movu [srcq], m0 RET%include x86inc.asm这是由 x264、FFmpeg、dav1d 社区共同开发的头文件提供了辅助工具、预定义名称和宏例如下文出现的cglobal用于简化汇编编写。SECTION .text指明接下来的代码放入可执行代码段。与之相对的是.data段用于放置常量数据。在后续课程lesson_03/index.fr.md中还会看到宏SECTION_RODATA用于声明只读数据段。注释与INIT_XMM sse2;static void add_values(const uint8_t *src, const uint8_t *src2) INIT_XMM sse2第一行是注释——汇编中分号;等价于 C 中的//这里展示了该函数在 C 中的原型。第二行INIT_XMM sse2将函数初始化为使用XMM 寄存器 SSE2 指令集。这是必需的因为后面的paddb属于 SSE2 指令。SSE2 的更多细节会在下一课展开下一课 lesson_02/index.fr.md 讲授分支与循环指令集的历史演进则在 lesson_03/index.fr.md 中有系统梳理。cglobal add_values, 2, 2, 2, src, src2这一行至关重要它定义了一个名为add_values的 C 函数可被 C 代码直接调用。逐个参数解读第一个2函数接受 2 个参数即src、src2第二个2本函数将使用 2 个 GPR含参数占位。某些情况下需要更多 GPR就必须告诉x86util申请更多第三个2告诉x86util将使用 2 个 XMM 寄存器即m0、m1最后两个src, src2两个函数参数对应的标签名。课程还提醒较老代码可能没有参数标签而是直接用r0、r1等 GPR 地址来指代参数。加载loadmovu m0, [srcq]与movu m1, [src2q]movu m0, [srcq] movu m1, [src2q]movu是movdqumove double quadunaligned非对齐移动双四字的缩写。对齐alignment会在后续课程详细讲解目前可以把movu理解为从[srcq]做一次 128 位传输。这里的方括号[]表示对srcq中保存的地址进行解引用等价于 C 中的*src这就是所谓的加载load。需要注意两点q后缀指的是指针的尺寸——在 C 中它对应 64 位系统下sizeof(*src) 8而 x86asm 足够聪明在 32 位系统上会自动改用 32 位指针但底层加载操作始终是 128 位的我们没有用向量寄存器的完整名字这里是xmm0而是用抽象形式m0。在后续课程中你将看到这种抽象意味着同一份代码可以一次编写、适配多种 SIMD 寄存器尺寸例如通过INIT_YMM avx2切换到 YMM 寄存器。核心计算paddb m0, m1paddb m0, m1paddb读作p-add-bpacked add bytes把两个寄存器中的每个字节逐一相加。命名规则前缀p表示packed打包用于区分向量指令与标量指令后缀b表示按字节进行运算。运算示意如下——两个 XMM 寄存器各 16 个字节按对应位置相加abcdefghijklmnopqrstuvwxyzaaabacadaeafaqbrcsdteufvgwhxiyjzkaalabmacnadoaepaf值得注意的是这里一条指令就完成了 16 个字节的加法——这正是 SIMD 的核心价值也是本课标题单指令多数据的直观体现。存储storemovu [srcq], m0movu [srcq], m0这是与加载对应的**存储store**操作把计算结果写回srcq指针所指向的内存地址。RET与函数指针RETRET是一个宏表示函数返回。课程强调了一个 FFmpeg 汇编的显著惯例几乎所有的 FFmpeg 汇编函数都是直接修改传入参数所指向的数据而不是通过返回值返回结果。正如你将在配套练习中看到的那样练习的目标是创建指向汇编函数的函数指针并在可用时调用它们——这正是 FFmpeg 运行时 CPU 特性检测 函数指针分派的机制基础lesson_03/index.fr.md 对此有更深入的说明。延伸阅读本课程系列的后续脉络第一课完成的是概念 第一个函数的铺垫后续课程在同一个仓库中继续深化与本课内容紧密衔接lesson_02/index.fr.md——分支、循环与寻址介绍标签label与跳转jmp、FLAGS 寄存器、cmp/jl/jg等循环写法、只读常量SECTION_RODATA、db/dw/times、偏移量offset、[base scale*index disp]寻址语法以及单指令完成乘法加法的lealesson_03/index.fr.md——指令集、对齐与扩展梳理从 MMX、SSE、SSE2 到 AVX/AVX2/AVX512 的指令集演进历史讲解 FFmpeg 的运行时 CPU 检测、指针偏移技巧、mova/av_malloc对齐、punpcklbw范围扩展、pcmpgtb符号扩展、packuswb打包以及视频处理中最重要的指令pshufb洗牌。如果你希望从全局了解这套课程的结构与翻译情况可以查看仓库根目录的 README.md其中说明了本仓库包含与各课对应的课程与练习练习暂未上传并提供了多语言版本的入口。回到第一课的起点汇编语言本身并不神秘掌握它需要的是一半术语、一半实践。本课已经为你建立起了寄存器、指令助记符、Intel 语法与 x86inc.asm 的完整认知框架——下一步就是打开 lesson_02/index.fr.md用循环和寻址让向量处理真正跑起来。赞分享教程文档【免费下载链接】asm-lessonsFFmpeg Assembly Language Lessons项目地址https://gitcode.com/GitHub_Trending/as/asm-lessons点击查看免费下载相关推荐Readest Android 后台 TTS 锁屏媒体控制修复实战从 startService 进程内化到 serde 参数陷阱Readest Android 后台 TTS 锁屏媒体控制修复实战从 startService 进程内化到 serde 参数陷阱 导读 本文基于 Reades教程文档在 learn-go-with-tests 中学习整数运算用 TDD 编写 Add 函数与可测试的 Go 文档示例在 learn go with tests 中学习整数运算用 TDD 编写 Add 函数与可测试的 Go 文档示例 导读 本文基于 learn go with教程文档FFmpeg 汇编语言课程 asm-lessons 第三课SIMD 指令集演进、运行时 CPU 检测与数据重排实战FFmpeg 汇编语言课程 asm lessons 第三课SIMD 指令集演进、运行时 CPU 检测与数据重排实战 FFmpeg 汇编语言课程asm les教程文档上一篇终极RPG Maker MV解密指南3步提取加密游戏资源的完整教程下一篇5步掌握Umi-OCR开源免费的离线文字识别工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →