云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

企业网站_学习服务器_免费领

小七 141 0

使用SIMD将jpetran的速度提高一倍

在CloudFlare,我们花了很大的精力来加速客户的网站建设,这已经不是什么秘密了。一种方法是缩小网站上的图片大小。这就是我们波兰产品的用途。它使用开放源代码工具,如jpetran、gifsicle和pngcrush获取各种图像并使它们变小。然而,这些工具在计算上是昂贵的,并且使它们运行得更快,使我们的服务器运行得更快,随后我们的客户的网站也会更快。最近,我注意到我们花了十倍的时间来"抛光"jpeg图像,而不是我们在抛光PNG。我们已经通过使用我们的zlib的增压版本来改进pngcrush的性能。因此,现在是时候看看可以为jpegtran(libjpeg发行版的一部分)做些什么了。快速分析为了快速得到结果,我通常使用Linux perf实用程序。它为代码中的热点提供了一个很好的(如果很简单的话)视图。我用这个图像作为基准。由ESO提供的CC BY 4.0图像perf record./jpetran-outfile/dev/null-progressive-optimize-copy none测试.jpeg我们得到:性能报告54.90%lt JPEGRANlibjpeg.so文件.9.1.0[.]编码28.58%lt JPEGRANlibjpeg.so文件.9.1.0[.]先对单片机进行编码真 的。这是83.5%的时间花在两个函数上!快速看一下,这两个函数都处理jpeg图像的Huffman编码。这是一种相当简单的压缩技术,其中频繁出现的字符被短编码代替,而很少出现的字符得到更长的编码。测试文件所用的时间为6秒。从哪里开始?首先让我们看看最常用的函数encode_mcu_AC_refine。它是最重的功能,因此优化它将使我们受益最大。函数有两个循环。第一个循环:对于(k=cinfo->Ss;k>16位整数乘以Al位_mm_storeu_si128((\u m128i*)&absvalues[k],x1);//存储如您所见,我们只需要两条指令就可以对八个值执行简单的转换,而且不需要分支作为额外的奖励。下一个棘手的部分是更新EOB,因此它指向最后一个非零索引。为此,我们需要找到x1中非零值最高的索引,如果有一个相应的更新EOB。我们可以很容易地将x1中的所有值与一个值进行比较:x1=_mm_cmpeq_epi16(x1,_mm_set1_epi16(1));//与1比较此操作的结果是一个掩码。对于所有相等值,所有位都设置为1,对于所有非相等值,所有位都被清除为0。但是,如何找到位都是1的最高索引呢?好吧,有一条指令提取一个um128i值中每个字节的最高位,并将其放入一个常规整数。从这里,我们可以通过查找该整数中的顶部集合位来找到索引:unsigned int idx=_mm_movemask_epi8(x1);//提取字节掩码EOB=idx?k+16-\u builtin_clz(idx)/2:EOB;//计算索引就这样。EOB将正确更新。这个简单的优化给了我们什么?再次运行jpetran会显示相同的图像现在需要5.2秒!这是1.15倍的加速。但现在没有理由停下来。继续前进接下来,循环遍历我们刚刚准备好的数组(absvalues)。在循环的开始,我们看到:如果((temp=absvalues[k])==0){r++;继续;}所以它只是跳过数组中的所有0值,直到找到一个非零值。值得优化吗?通过快速测试,我发现有很长的零值序列是很常见的。单独迭代它们可能很昂贵。让我们试试前面的方法,在数组中查找顶部非零元素:__m128i t=_mm_loadu_si128(((\u m128i*)和absvalues[k]);t=_mm_cmpeq_epi16(t,_mm_setzero_si128());//与0比较int idx=_mm_movemask_epi8(t);//提取字节掩码if(idx==0xffff){/如果所有值都为零,则全部跳过r+=8;k+=8;继续;}否则{/如果有些非零,则跳到第一个非零int skip=内置ctz(~idx)/2;r+=跳过;k+=跳过;if(k>Se)break;//走得太远则停止}temp=absvalues[k];//加载第一个非零值我们现在得到什么?我们的测试图像现在只需要3.7秒。我们已经达到了1.62倍的加速。perf展示了什么?45.94%lt JPEGRANlibjpeg.so文件.9.1.0[.]先对单片机进行编码28.73%lt-jpetran28.73%libjpeg.so文件.9.1.0[.]编码我们可以看到形势已经好转。现在先编码mcu-AC-u是这两种功能中速度较慢的一种,而且占用了几乎50%的时间。Moar优化!首先看一下encode_mcu_AC_,我们看到一个循环,它执行的操作与我们已经优化的功能类似。为什么不重用我们已经知道是好的优化呢?我们将把一个大循环分成两个小循环,一个执行所需的转换,并将它们存储到辅助数组中,另一个则跳过零项读取这些值。请注意,现在转换有些不同:如果(温度>=Al;温度2=~温度;}其他{温度>>=Al;temp2=温度;}SIMD的等效值为:__m128i neg=_mm_cmpgt_epi16(_mm_setzero_si128(),x1);//负掩码x1=_mm_abs_epi16(x1);//绝对值x1=_mm_srli_epi16(x1,Al);//>>__m128i x2=_mm_and not_si128(x1,neg);//反转x1,并应用负掩码x2=_mm_xor_si128(x2,_mm_and not_si128(neg,x1));//应用非负掩码跳过零值的方式与之前相同。现在测试图像的性能为2.65秒!这比原来的jpegtran快2.25倍。结论让我们最后看一下perf输出:40.78%lt JPEGRANlibjpeg.so文件.9.1.0[.]编码26.01%lt-jpetran26.01%libjpeg.so文件.9.1.0[.]先对单片机进行编码我们可以看到,我们优化的两个函数仍然占据了执行的很大一部分。但现在它只占整个项目的67%。如果你问你自己,当这些函数从83.5%提高到67%时,我们是如何得到2.25倍的加速的,请阅读土豆悖论。这里绝对值得进一步优化。但是,在函数之外研究数据是如何排列和传递的可能会更好。底线是:优化代码很有趣,使用SIMD指令可以给您带来显著的提升。比如在这种情况下。我们CloudFlare确保我们的服务器以一流的性能运行,因此我们客户的网站也一样!。您可以在我们的GitHub存储库中找到最终代码:https://github.com/cloudflare/jpegtran