题:
软件行业的共识是,干净,简单的代码对于代码库和拥有它的组织的长期生存至关重要。这些属性导致较低的维护成本,并增加了继续使用代码库的可能性。
但是,SIMD代码与通用应用程序代码不同,我想知道是否有专门针对SIMD代码的简洁代码的相似共识。
我的问题的背景。
我为各种图像处理和分析任务编写了大量的SIMD(单指令,多个数据)代码。最近,我还不得不将少量这些功能从一种体系结构(SSE2)移植到另一种体系结构(ARM NEON)。
该代码是为收缩包装的软件编写的,因此,如果没有不受限制的重新分配权限(例如MATLAB),就不能依赖专有语言。
典型代码结构的示例:
- 使用OpenCV的矩阵类型(
Mat
)进行所有内存,缓冲区和生命周期管理。 - 检查输入参数的大小(尺寸)后,将获得指向每行像素起始地址的指针。
- 像素计数和来自每个输入矩阵的每一行像素的起始地址都传递给一些低级C ++函数。
- 这些低级C ++函数使用SIMD内部函数(用于Intel Architecture和ARM NEON),从原始指针地址加载并保存到原始指针地址。
- 这些低级C ++函数的特征:
- 唯一一维的(在内存中连续)
- 不处理内存分配。
(每个分配,包括临时性,都由外部代码使用OpenCV工具处理。) - 符号的名称长度(内部名称,变量名称等)的范围大约为10-20个字符,这是非常大的。
(读起来就像是技术泡沫)。 - 不鼓励重复使用SIMD变量,因为编译器在正确地解析不是以“单分配”编码方式编写的代码方面存在很多缺陷。
(我已经提交了几个编译器错误报告。)
SIMD编程的哪些方面会使讨论与一般情况有所不同?或者,为什么SIMD与众不同?
初期开发费用
- 众所周知,与随便编写的 C ++代码相比,具有良好性能的C ++ SIMD代码的初始开发成本约为10倍至100倍(具有较大的余量)。
- 正如在性能与可读/清洁代码之间进行选择的答案中所指出的那样?,大多数代码(包括随便编写的代码和SIMD代码)起初既不干净也不快速。
- 不鼓励对代码性能(在标量代码和SIMD代码中)进行进化改进(因为这被视为一种软件返工),并且没有跟踪成本和收益。
就倾向而言
(例如帕累托原则,又称80-20规则)
- 即使图像处理仅占软件系统的20%(在代码大小和功能上),图像处理也相对较慢(当以所花费的CPU时间的百分比表示),花费的时间超过80%。
- 这是由于数据大小的影响:典型的图像大小以兆字节为单位,而非图像数据的典型大小以千字节为单位。
- 在图像处理代码中,SIMD程序员受过训练,可以通过识别C ++代码中的循环结构来自动识别包含热点的20%代码。因此,从SIMD程序员的角度来看,“重要的代码” 100%是性能瓶颈。
- 通常在图像处理系统中,存在多个热点,并占用相当比例的时间。例如,可能有5个热点分别占总时间(20%,18%,16%,14%,12%)。为了获得高性能,需要在SIMD中重写所有热点。
- 这被概括为气球弹出规则:气球不能两次弹出。
- 假设有一些气球,说5个。抽取它们的唯一方法是将它们逐个弹出。
- 弹出第一个气球后,剩下的4个气球现在占总执行时间的更高百分比。
- 为了获得更大的收益,然后必须弹出另一个气球。
(这违背了80-20的优化规则:采摘了悬挂率最低的20%的水果后,可以获得良好的经济效果。)
在可读性和维护方面
SIMD代码显然难以阅读。
- 即使遵循每一项软件工程最佳实践,也是如此,例如命名,封装,const正确性(并使副作用显而易见),函数分解等。
- 即使对于有经验的SIMD程序员也是如此。
与等效的C ++原型代码相比,最佳SIMD代码非常扭曲(请参阅备注)。
- 扭曲SIMD代码的方法有很多,但是10种这样的尝试中只有1种会获得可接受的快速结果。
- (也就是说,为了获得高昂的开发成本,性能提高了4倍至10倍。实践中观察到更高的收益。)
(备注)
这是麻省理工学院卤化物项目的主要论文-逐字引用论文标题:
“从计划中解耦算法,以轻松优化图像处理管道”
在前向适用性方面
- SIMD代码严格绑定到单个体系结构。每个新体系结构(或每个SIMD寄存器扩展)都需要重写。
- 与大多数软件开发不同,每条SIMD代码通常都是出于一个不变的目的而编写的。
(除了移植到其他体系结构。) - 一些架构保持完美的向后兼容性(Intel);一些落下短由一个微不足道的量(ARM AArch64,替换
vtbl
用vtblq
),但是足以导致一些代码编译失败。
在技能和培训方面
- 尚不清楚正确培训新程序员编写和维护SIMD代码需要哪些知识先决条件。
- 在学校学习过SIMD编程的大学毕业生似乎鄙视并将其视为不切实际的职业道路。
- 反汇编读取和低级性能分析是编写高性能SIMD代码的两项基本技能。但是,尚不清楚如何系统地培训程序员这两项技能。
- 现代CPU体系结构(与教科书中所教的内容大相径庭)使培训更加困难。
在正确性和与缺陷相关的成本方面
- 单个SIMD处理功能实际上具有足够的凝聚力,可以通过以下方式建立正确性:
- 应用形式方法(用纸和笔),以及
- 验证输出整数范围(使用原型代码并在运行时之外执行)。
- 但是,验证过程非常昂贵(代码审查花费100%的时间,原型模型检查花费100%的时间),这使SIMD代码原本昂贵的开发成本增加了三倍。
- 如果某个错误设法通过了此验证过程,则几乎不可能“修复”(修复),除非替换(重写)可疑的缺陷功能。
- SIMD代码在C ++编译器(优化代码生成器)中饱受缺陷的困扰。
- 使用C ++ 表达式模板生成的SIMD代码还遭受编译器缺陷的困扰。
在颠覆性创新方面
学术界已经提出了许多解决方案,但是很少有看到广泛的商业用途。
- 麻省理工学院
- 斯坦福暗房
- NT2(数字模板工具箱)和相关的Boost.SIMD
具有广泛商业用途的图书馆似乎并未完全启用SIMD。
- 开源库对SIMD似乎不冷不热。
- 最近,从2.4.9版开始,我分析了许多OpenCV API函数后,对此有了第一手观察。
- 我介绍过的许多其他图像处理库也没有大量使用SIMD,否则会错过真正的热点。
- 商业图书馆似乎完全避免使用SIMD。
- 在某些情况下,我什至看到图像处理库将较早版本中的SIMD优化代码还原为较新版本中的非SIMD代码,从而导致严重的性能下降。
(供应商的回应是有必要避免编译器错误。)
- 在某些情况下,我什至看到图像处理库将较早版本中的SIMD优化代码还原为较新版本中的非SIMD代码,从而导致严重的性能下降。
- 开源库对SIMD似乎不冷不热。
程序员的问题: 低延迟代码有时是否必须“丑陋”? 是相关的,几年前我曾写过该问题的答案以解释我的观点。
但是,该答案几乎是“过早优化”观点的“安抚”,即:
- 根据定义,所有优化都是过早的(或者自然而然是短期的)),并且
- 具有长期利益的唯一优化是朝着简单化迈进。
但是,这种观点在这篇ACM文章中受到质疑。
所有这些使我问:
SIMD代码与通用应用程序代码不同,并且我想知道对于SIMD代码的简洁代码的价值是否存在类似的行业共识。