单核并行编程:SIMD组件性能提升和基准测试

当事情变得如此复杂时,您通常知道这是英特尔的错。 来源: Pixabay

SIMD汇编指令使您可以在单个内核中并行处理大量数据。 我已经说过一次 ,我会再说一遍:编程是我们最不可思议的事情。

Shell命令就像小型Cantrip,Python脚本对Tulpas几乎没有帮助。 我们甚至有自己的守护程序! 但是,每当我们需要将性能压缩到最后一个字节时–当我们知道一个错误的步伐都会使程序速度大大降低时……那是最黑暗的黑魔法的汇编程序出现的时候。

由于您可能没有注意到,我最近没写太多(顺便说一句,如果这是您第一次读我,欢迎光临!很高兴见到您!)。 这是因为上周我进行了非常严苛的考试,并且我必须做很多准备。 该主题称为“计算机组织II”,与时俱进是一个巨大的挑战。
因此,我决定参加练习时做的其中一项练习,然后将其变成一篇文章。 这样,我可以用一块石头砸掉两把剪刀(杀死鸟很糟糕,你应该感到很难过)。

在不拖延的情况下,让我们直接进行讨论。 和往常一样,此Github项目提供了代码。

什么是处理器指令?

我们编写的所有代码(无论是用Python,Java还是C语言编写)最终都将被解释或编译为我们的CPU的微型原子指令(从程序员的角度)。
这些指令数以千计,它们每个都做很小的事情,直接与硬件交互。

例如,一条指令可以将一个值写入内存(变量赋值转换为该值),将其打开或关闭,或执行逻辑

我的PC具有Intel处理器,这也是我们在课堂上学习的体系结构,因此,对所有使用阅读器的ARM感到抱歉,今天我的包容性还不够。

编写这些指令的语言(将1:1转换为文字二进制)称为汇编语言。

将C转换为汇编:让我们成为编译器已有一段时间。

在本文中,我们将使用非常小的C函数。 这是完整的代码:

该函数将指向字节流的指针(一个char重一个字节),一个 char和一个want char作为参数,并假设该流以0结尾(如果不是,则崩溃为分段错误)的情况),然后逐字节进行迭代,将每个“ have”实例替换为want。就C而言,它的速度就和它一样快–并且远比Python快(当我运行一些基准测试时) ,此函数的Python版本花费了两分钟的时间,而输入大小在C中花费了6秒。

经过编译器后,此功能在汇编语言中是什么样的? 可能是这样的:

运行该汇编函数而不是C版本不应提高我们的性能。 由于编译器知道一些我们可能不知道的技巧,并且对这种代码进行了一些优化,因此它甚至可能降低它的效率。

有一种通常不会使用的优化,但是当它使用时,它永远不会充分利用它。

SIMD指令:单指令,多数据

每当我们想到并行性时,我们就会想到多核进程,甚至集群。 但是,如果我们使一个核心一次完成很多事情,该怎么办? 这就是英特尔几十年前的想法,从那时起图像处理的世界就不一样了。

您会看到,通常数据存储在通用寄存器中,就像我们刚刚使用的寄存器一样,存储在我们的CPU中。 它们中大多数都是64位大小,因此可以存储long, floatint。 好吧,从技术上讲,是两个int ,但是仍然不足以值得并行使用它们的说明。

但是,大多数处理器具有更大的可用寄存器: XMM ,其上具有128位。 这足以容纳16个完整字节!

让我们开始这场派对。 来源: Pixabay

想象一下,如果一次可以处理16个字节,并使我们的程序快16倍,该怎么办。 从内存中读取一次并获取16个不同的字节? 校验。 批量处理它们并将它们一次又一次写入内存中? 校验。 可能性是无止境。

尤其是在图像或信号处理中,这为更快地进行并发计算打开了可能性。 就像,整个数量级更快。 您知道平凡的并发功能是什么? 处理字节流,它们之间没有依赖性。

但是,有一个陷阱:如果常规的Intel指令对您而言似乎违反直觉或丑陋,请准备好SIMD。 他们的名字令人目结舌,而且我们大多数人都不能连续写出5个名字,除非在官方手册中查找它们(值得庆幸的是免费提供 )。

处理了所有这些警告之后,让我指导您完成刚才显示的功能的SIMD实现。
让我警告您:它虽然不漂亮,但是却非常快。

第一个示例:使用SIMD汇编指令获取字符串的长度。

首先,我对流进行一次迭代以将其放大。 为简单起见,此函数假定流的大小可被16整除(末尾加零,至少一个0字节表示流的末尾),因为否则我只需要添加一个新的if并运行size函数的非SIMD版本。
假设我们的用户愿意在将阵列传递给我们之前对其进行填充以使其可分割,以换取性能提升和节省的治疗时间。

如果您需要呕吐或去洗个澡,我会明白的。 将本文添加为书签,然后在一个小时后返回。
但是,我们每16个字节从内存读取一次。
这使得该程序比C版本快16倍!

现在,在第二部分中,让我们实际执行被要求的操作:让我们替换一些字节!

而已。 由于布尔操作是逐字节进行的,因此比较也是并发的,因此我们实际上可以完全并行地处理每组16个字节。

免责声明:对于那些真正关心性能的人,请注意,我已经进行了2次内存读取,每次都读取相同的数据:一次计算长度,然后进行另一次替换。

一次完成所有操作将是最佳选择,但是代码会更丑陋而不是那么具有教育意义。 这就是我们希望该程序比C版本仅快8倍而不是16倍的原因。它仍然是一个非常不错的改进。

使用SIMD的速度有多快? 基准!

为了运行这些基准测试,我仅在一个小程序(可从Github项目获得 )中使用time.h C库。 我所做的就是:

  • 选择一个数组大小(16的倍数)
  • 使用给定大小的交替值初始化字节数组
  • 运行该函数的C版本,并测量完成循环所需的时间。
  • 对SIMD Assembly版本执行相同的操作。

我用1.6e7、1.6e8和1.6e9的输入大小重复了这些步骤。 我停在那里,是因为下一站所花的时间比我希望等待的时间长一点,但是趋势非常明显:

INPUT_SIZE | ASM  | C (Seconds)
1.6e7 | 0.005 | 0.069
1.6e8 | 0.050 | 0.687
1.6e9 | 0.466 | 6.868

它比我预期的还要线性! 比率约为13,因此也比我估计的还要好。 我想这与减少内存读取有关,但是请随时在注释中保留自己的假设。

结论

我的第一个结论是,学习汇编很有趣。 我的第二个结论是,我无法通过该考试。

出于实用性考虑,我的第三个结论是编写SIMD程序非常有效,并且每当我们需要更快地执行某些操作时,我们都可以尝试使用它。

或者,如果您更喜欢高级语言,则可以寻找在其实现中已经使用SIMD指令的框架(咳嗽,NumPy,熊猫)。

无论如何,我认为了解低级内容和处理器的内部工作原理可以帮助我们编写更好的代码,并更好地了解事物在后台的运行方式。

今天就这些。 我希望您发现这篇文章很有趣,甚至有用。 如果您想进一步阅读本主题,建议您考虑一个可能对并行处理有用的问题,请尝试使用SIMD编写它,然后每两个步骤查阅Intel手册以了解新的说明。

像往常一样,如果您在我的代码中发现任何错误,或者我可以进一步优化此错误的方法,甚至是错字,请在评论中告知我! 对于任何积极的反馈也是如此,这一直是人们所赞赏的。

我待会见,继续编码!

Twitter Medium 上关注我, 以继续接收更多文章和教程。 请在您使用的任何社交媒体上分享此文章。
也许您有一位最近打算了解更多组装知识的朋友? 用这个打他!

最初于 2018年10月6日 发布在 www.dataden.tech

From: https://hackernoon.com/harnessing-the-power-of-simd-sse-assembly-instructions-for-good-fdaa8ce34e9a

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值