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

京东云_鑫云主机_怎么样

小七 141 0

霓虹灯是新的黑色:ARM服务器上的快速JPEG优化

随着Cloudflare的工程师迅速调整我们的软件堆栈以在ARM上运行,我们的软件堆栈的一些部分在ARM处理器上的性能不如目前在Xeon®Silver 4116 CPU上的表现好。在很大程度上,这是一个英特尔特定的优化问题,其中一些优化利用了SIMD或其他特殊指令。一个这样的例子就是久负盛名的jpetran,它是我们波兰图像优化服务的幕后推手之一。不久前,我为英特尔处理器优化了jpegtran版本。因此,当我对测试图像进行比较时,我预计Xeon的性能会优于ARM:vlad@xeon公司用法:~$time./jpetran-outfile/dev/null-progressive-optime-copy none测试.jpg实数0m2.305s用户0m2.059s系统0m0.252s弗拉德@手臂用法:~$time./jpetran-outfile/dev/null-progressive-optime-copy none测试.jpg实数0m8.654s用户0m8.433s系统0.225s理想情况下,我们希望ARM的性能达到或超过每个核心Xeon性能的50%。这将确保我们没有性能倒退,并净性能提高,因为ARM CPU的核心数量是我们当前的2插槽设置的两倍。然而,在这种情况下,我很失望地发现了几乎4倍的减速。没有人失望,我发现应用我为英特尔做的同样的优化将是微不足道的。霓虹灯的指令肯定和我以前用过的SSE指令完全对应吗?CC BY-SA 2.0图像viZZZual.com网站霓虹灯是什么NEON是SIMD的ARMv8版本,单指令多数据指令集,其中单个操作对多个操作数执行(通常)相同的操作。NEON在32个专用128位寄存器上运行,类似于Intel SSE。它可以对32位和64位浮点数字,或8位、16位、32位和64位有符号或无符号整数执行操作。与SSE一样,您可以用汇编语言编程,也可以用C语言使用内部函数编程。内部函数通常更易于使用,并且取决于应用程序和编译器,可以提供更好的性能,但是基于内部函数的代码往往非常冗长。如果选择使用NEON内部函数,则必须包含。虽然SSE内在函数对所有的SIMD整数运算都使用u m128i,但NEON的内部函数对于每个整数和浮点宽度都有不同的类型。例如,对有符号16位整数的操作使用int16x8_t类型,我们将使用它。类似地,对于无符号整数,也有uint16x8_t类型,以及int8x16斨t、int32x4_t和int64x2_t及其uint导数都是不言而喻的。入门运行perf会告诉我,这两个罪魁祸首是导致大部分CPU时间花费的原因:perf record./jpetran-outfile/dev/null-progressive-optimize-copy none测试.jpeg性能报告71.24%lt jpegtranlibjpeg.so文件.9.1.0[.]编码15.24%lt jpegtranlibjpeg.so文件.9.1.0[.]先对单片机进行编码啊哈,我的老冤家们,先把编码弄细再编码!直截了当的方法编码让我们回顾一下我们之前应用于编码的优化。该功能有两个环路,较重的环路执行以下操作:对于(k=cinfo->Ss;k>16位整数乘以Al位_mm_storeu_si128((\u m128i*)&absvalues[k],x1);//存储x1=_mm_cmpeq_epi16(x1,_mm_set1_epi16(1));//与1比较unsigned int idx=_mm_movemask_epi8(x1);//提取字节掩码EOB=idx?k+16-\u builtin_clz(idx)/2:EOB;//计算索引在大多数情况下,向霓虹灯的过渡确实很简单。要将寄存器初始化为全零,我们可以使用vdupq_n_s16内部函数,它在寄存器的所有通道上复制给定值。插入是用vsetq_lane_s16内部函数执行的。使用vabsq_s16获得绝对值。右移的指示使我停顿了一会儿。我只是找不到可以右移非常数整数值的指令。它不存在。然而解决办法很简单,你左移一个负数!它的本质是vshlq_s16。没有右转指令并非巧合。与x86指令集不同,x86指令集理论上可以支持任意长的指令,因此在添加新指令之前不必三思而后行,无论它是多么专业或冗余,ARMv8指令集只能支持32位长的指令,并且操作码空间非常有限。因此,指令集要简洁得多,许多指令实际上是其他指令的别名。甚至最基本的MOV指令也是ORR(二进制或)的别名。这意味着ARM和NEON的编程有时需要更大的创造力。循环的最后一步是将每个元素与1进行比较,然后得到掩码。用vceqq_s16进行相等性比较。但同样没有手术来提取面具。这是个问题。但是,不需要获取位掩码,而是可以先将vuzp1q_u8应用于比较结果,从每个通道提取一个完整的字节到64位值。vuzp1q_u8交织两个向量的偶数索引字节(而vuzp2q_u8交错奇数索引)。所以解决方案应该是这样的:int16x8_t zero=vdupq_n_s16(0);int16x8_t al_neon=vdupq_n_s16(-al);0英寸×8英寸×0英寸;int16x8_t x1=零;//按顺序加载8个16位值x1=vsetq_车道_s16((*块)[自然顺序[k+0]],x1,0);//交错加载以补偿延迟x0=vsetq_车道_s16((*block)[自然顺序[k+1]],x0,1);x1=vsetq_车道_s16((*块)[自然顺序[k+2]],x1,2);x0=vsetq_车道_s16((*block)[自然顺序[k+3]],x0,3);x1=vsetq_车道_s16((*块)[自然顺序[k+4]],x1,4);x0=vsetq_车道_s16((*block)[自然顺序[k+5]],x0,5);x1=vsetq_车道_s16((*街区)[自然顺序[k+6]],x1,6);x0=vsetq_车道_s16((*block)[自然顺序[k+7]],x0,7);int16x8_t x=vorrq_s16(x1,x0);x=vabsq_s16(x);//取16位整数的绝对值x=vshlq_s16(x,al_neon);//>>16位整数乘以al位vst1q_s16(&absvalues[k],x);//存储uint8x16_t is_one=vreinterpretq_u8_u16(vceqq_s16(x,one));//与1比较is_one=vuzp1q_u8(is_one,is_one);//将比较结果压缩成64位uint64_t idx=vgetq_u64(vreinterpretq_u64_u8(是_one),0);//提取EOB=idx?k+8-\u builtin_clzl(idx)/8:EOB;//获取索引注意显式类型转换的内部函数。它们实际上不发出任何指令,因为不管类型如何,操作数总是占据相同的寄存器。转到第二个循环:如果((temp=absvalues[k])==0){r++;继续;}SSE解决方案是:__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];//加载下一个非零值但是我们已经知道没有办法提取字节掩码。我没有使用NEON,而是选择一次跳过四个零值,使用64位整数,如下所示:uint64_t tt,*t=(uint64_t*)和absvalues[k];if((tt=*t)==0)while((tt=*++t)==0);//全零时跳过int skip=u builtin_ctzl(tt)/16+((int64Βt)t-(int64Βt)&absvalues[k])/2;//获取下一个非零的索引k+=跳过;r+=跳过;温度=绝对值[k];我们现在有多快?弗拉德@手臂用法:~$time./jpetran-outfile/dev/null-progressive-optime-copy none测试.jpg实数0m4.008s用户0m3.770s系统0.241s哇,太不可思议了。超过2倍加速!先对mcu进行编码另一个函数非常相似,但第一个过程的逻辑略有不同:温度=(*block)[自然顺序[k]];如果(温度=Al;//应用点变换温度2=~温度;}其他{temp>=Al;//应用点变换temp2=温度;}t1[k]=温度;t2[k]=温度2;这里需要将温度的绝对值赋给t1[k],如果temp为负,则将其逆赋值给t2[k],否则t2[k]与t1[k]分配相同的值。为了得到值的倒数,我们使用vmvnq_s16内部函数,为了检查值是否为负,我们需要使用vcgezq_s16与零进行比较,最后使用vbslq_s16根据掩码进行选择。int16x8_t zero=vdupq_n_s16(0);int16x8_t al_neon=vdupq_n_s16(-al);int16x8_t x0=零;int16x8_t x1=零;//按顺序加载8个16位值x1=vsetq_车道_s16((*块)[自然顺序[k+0]],x1,0);//交错加载以补偿延迟x0=vsetq_车道_s16((*block)[自然顺序[k+1]],x0,1);x1=vsetq_车道_s16((*块)[自然顺序[k+2]],x1,2);x0=vsetq_车道_s16((*block)[自然顺序[k+3]],x0,3);x1=vsetq_车道_s16((*块)[自然顺序[k+4]],x1,4);x0=vsetq_车道_s16((*街区)[自然