SIMD编程代码库的维护成本


14

题:

软件行业的共识是,干净,简单的代码对于代码库和拥有它的组织的长期生存至关重要。这些属性导致较低的维护成本,并增加了继续使用代码库的可能性。

但是,SIMD代码与通用应用程序代码不同,我想知道是否有专门针对SIMD代码的简洁代码的相似共识。


我的问题的背景。

我为各种图像处理和分析任务编写了大量的SIMD(单指令,多个数据)代码。最近,我还不得不将少量这些功能从一种体系结构(SSE2)移植到另一种体系结构(ARM NEON)。

该代码是为收缩包装的软件编写的,因此,如果没有不受限制的重新分配权限(例如MATLAB),就不能依赖专有语言。

典型代码结构的示例:

  • 使用OpenCV的矩阵类型Mat进行所有内存,缓冲区和生命周期管理。
  • 检查输入参数的大小(尺寸)后,将获得指向每行像素起始地址的指针。
  • 像素计数和来自每个输入矩阵的每一行像素的起始地址都传递给一些低级C ++函数。
  • 这些低级C ++函数使用SIMD内部函数(用于Intel ArchitectureARM 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,替换vtblvtblq),但是足以导致一些代码编译失败。

在技​​能和培训方面

  • 尚不清楚正确培训新程序员编写和维护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代码,从而导致严重的性能下降。
        (供应商的回应是有必要避免编译器错误。)

程序员的问题: 低延迟代码有时是否必须“丑陋”? 是相关的,几年前我曾写过该问题的答案以解释我的观点。

但是,该答案几乎是“过早优化”观点的“安抚”,即:

  • 根据定义,所有优化都是过早的(或者自然而然是短期的)),并且
  • 具有长期利益的唯一优化是朝着简单化迈进。

但是,这种观点在这篇ACM文章中受到质疑。


所有这些使我问:
SIMD代码与通用应用程序代码不同,并且我想知道对于SIMD代码的简洁代码的价值是否存在类似的行业共识。


2
有性能要求吗?您可以不使用SIMD来满足性能要求吗?如果不是这样,这个问题就没有意义了。
查尔斯E.格兰特

4
这对于一个问题来说太长了,很可能是因为其中很大一部分实际上是试图回答该问题,甚至对于一个答案也很久(部分原因是它涉及的范围比大多数合理答案要多得多)。

3
除了优化的替代方案之外,我还希望同时拥有干净/简单/缓慢的代码(用于概念的初步证明和以后的文档目的)。这使得它易于理解(因为人们可以只读取干净/简单/慢速代码),也易于验证(通过将优化版本与手动/在单元测试中的干净/简单/慢速版本进行比较)
Brendan 2014年

2
@Brendan我参加过类似的项目,并使用了简单/慢速代码的测试方法。尽管这是一个值得考虑的选择,但它也有局限性。首先,性能差异可能会令人望而却步:使用未经优化的代码进行的测试可能要运行数小时甚至数天。其次,对于图像处理,可能会发现,当优化的代码产生略有不同的结果时,逐位比较根本无法工作-因此人们将不得不使用更复杂的比较,例如ef均方根差
gnat

2
我投票决定将这个问题作为离题的话题结束,因为这不是帮助中心中所述的概念性编程问题。
durron597

Answers:


6

我没有为自己编写太多的SIMD代码,而是数十年前编写了许多汇编代码。使用SIMD内在函数的AFAIK本质上是汇编程序,只需将“ SIMD”替换为“ assembly”一词,即可重新表达您的整个问题。例如,您已经提到的要点,例如

  • 该代码要比“高级代码”开发10到100倍

  • 它绑定到特定的体系结构

  • 代码从不“干净”也不容易重构

  • 您需要专家来编写和维护它

  • 调试和维护很困难,而发展却非常艰难

绝对不是SIMD的“特殊”-这些观点对于任何一种汇编语言都是正确的,它们都是“行业共识”。并且软件行业中的结论也与汇编程序几乎相同:

  • 如果不需要,请不要编写它-尽可能使用高级语言,让编译器进行艰苦的工作

  • 如果编译器不够用,请至少将“低级”部分封装在某些库中,但要避免将代码散布到整个程序中

  • 由于几乎不可能编写“自我文档”汇编程序或SIMD代码,因此请尝试通过大量文档来平衡这一点。

当然,“经典”汇编或机器代码的情况确实有所不同:如今,现代编译器通常从高级语言生成高质量的机器代码,通常比手动编写的汇编代码更好地进行了优化。对于当今流行的SIMD架构,可用的编译器的质量远远不及AFAIK-也许永远无法达到,因为自动矢量化仍是科学研究的主题。例如,请参阅本文,其中描述了编译器和开发人员之间在优化方面的差异,并提出了创建优质SIMD编译器可能非常困难的想法。

正如您在问题中已经描述的那样,当前最新的库也存在质量问题。因此,恕我直言,我们最希望的是,在未来的几年中,编译器和库的质量将提高,也许SIMD硬件将不得不改变以变得更加“编译器友好”,也许是支持更容易向量化的专用编程语言(例如Halide,您两次提到过)将会变得越来越流行(这是否已经是Fortran的优势?)。根据Wikipedia的说法,SIMD大约在15到20年前成为了“大众产品”(而当我正确地解释文档时,Halide还不到3岁)。将此与需要成熟的“经典”汇编语言的编译器进行比较。根据这篇维基百科文章直到将编译器的性能超过了人类专家的水平(在生成非并行机器代码方面),它花了将近30年的时间(从1970年到1990年代末)。因此,我们可能只需要等待10至15年,直到启用SIMD的编译器发生同样的情况。


根据我对Wikipedia文章的阅读,似乎业内普遍达成共识,即由于必须记住许多技术细节,“认为底层优化的代码难以使用”
gnat 2014年

@gnat:是的,绝对可以,但是我想如果我将此添加到我的答案中,我应该在OP早已提到的其他问题中提及其他问题。
布朗

同意,您的答案中的分析看起来就足够好了,并补充说参考文献可能会“超载”它
gnat 2014年

4

我的组织已经解决了这个确切的问题。我们的产品在视频领域,但是我们编写的许多代码都是对静态图像也适用的图像处理。

我们通过编写我们自己的编译器来“解决”(或“解决”)问题。这并不像最初听起来那样疯狂。它有一组受限的输入。我们知道所有代码都适用于图像,主要是RGBA图像。我们设置了一些约束,例如输入和输出缓冲区永远不会重叠,因此没有指针别名。像这样的东西。

然后,我们用OpenGL阴影语言(glsl)编写代码。它被编译为标量代码,SSE,SSE2,SSE3,AVX,Neon,当然还有实际的glsl。当我们需要支持新平台时,我们会更新编译器以输出该平台的代码。

我们还对图像进行切片以提高缓存的一致性,诸如此类。但是通过将图像处理保持在较小的内核中,并使用glsl(甚至不支持指针),我们可以大大降低编译代码的复杂性。

这种方法并不适合每个人,它有自己的问题(例如,您需要确保编译器的正确性)。但这对我们来说效果很好。


听起来🔥🔥!您出售或单独提供此产品吗?(也是'AVC'= AVX吗?)
艾哈迈德·法西

抱歉,是的,我是说AVX(我会解决这个问题。)。我们目前不将编译器作为独立产品出售,尽管将来可能会发生。
user1118321 '16

别开玩笑,听起来真的很整洁。我所看到的最接近的事情是CUDA编译器过去如何制作可在CPU上运行的“串行”程序进行调试-我们希望可以将其推广为一种编写多线程和SIMD CPU代码的方式,但是唉。我能想到的下一个最接近的东西是OpenCL,您是否评估过OpenCL并发现它不如GLSL-all-all编译器?
艾哈迈德·法西

1
我认为OpenCL在开始时并不存在。(或者,如果有的话,那是相当新的东西。)因此,它并没有真正融入其中。
user1118321 '16

0

如果您考虑使用高级语言,似乎并不会增加过多的维护开销:

Vector<float> values = GetValues();
Vector<float> increment = GetIncrement();

// Perform addition as a vector operation:
List<float> result = (values + increment).ToList();

List<float> values = GetValues();
List<float> increment = GetIncrement();

// Perform addition as a monadic sequence operation:
List<float> result = values.Zip(increment, (v, i) => v + i).ToList();

当然,您将不得不面对该库的局限性,但是您不会自己维护它。可以在维护成本和性能胜利之间取得良好的平衡。

http://blogs.msdn.com/b/dotnet/archive/2014/04/07/the-jit-finally-proposed-jit-and-simd-are-getting-married.aspx

http://blogs.msdn.com/b/dotnet/archive/2014/05/13/update-to-simd-support.aspx


根据我的阅读,使用外部库的选项已经由asker调查并解决:“具有广泛商业用途的库似乎没有启用SIMD的功能...”
gnat 2014年

@gnat我实际上已经阅读了整个段落,而不仅仅是顶级的要点,并且张贴者没有提及任何通用的SIMD库,只是计算机视觉和图像处理的库。更不用说对高级语言应用程序的分析了,尽管问题标题中没有C ++标记并且没有C ++特殊性,但完全没有分析。这使我相信,尽管我的问题不会被视为首要问题,但它可能会增加价值,使人们意识到其他选择。
2014年

1
据我了解,OP正在询问是否存在广泛用于商业用途的解决方案。尽管我很欣赏您的提示(也许我可以在这里将lib用于一个项目),但从我的角度来看,RyuJIT远非“公认的行业标准”。
布朗

也许@DocBrown,但他的实际问题被表述为更通用:“ ...关于SIMD代码的简洁代码的价值的行业共识...”。我怀疑是否有任何(官方)共识,但是我认为高级语言可以减少“常用”和SIMD代码之间的差异,就像C ++让您忘记汇编一样,从而降低了维护成本。

-1

我过去做过汇编编程,最近没有做SIMD编程。

您是否考虑过使用类似于Intel的SIMD感知编译器?英特尔®C ++编译器矢量化指南是否有趣?

您的一些评论(例如“气球弹出”)建议使用编译器(如果您没有一个热点,则可以从中获得好处)。


根据我的阅读,这种方法是由asker尝试的,请参见问题中有关编译器错误/缺陷的提及
gnat 2014年

OP没有说他们是否尝试过Intel编译器,这也是Programmers.SE主题的主题。大多数人都没有尝试过。不是每个人都适合;但它可能适合OP的业务/问题(较低的编码/设计/维护成本,性能更好)。
ChrisW 2014年

我在问题中读到的东西都表明,问问者了解英特尔和其他架构的编译器:“某些架构保持完美的向后兼容性(英特尔);有些不足……”
gnat 2014年

那句话中的“英特尔”是指英特尔芯片设计者,而不是英特尔编译器编写者。
ChrisW 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.