有些人做网站不用钱的 对吗wordpress主题路径
有些人做网站不用钱的 对吗,wordpress主题路径,鄂尔多斯网站建设公司,三亚网站开发公司难丫低拐这个解释很简单#xff0c;最明显的要求是内存地址必须是16字节对齐的。似乎看不出什么核心的信息。 以前我也尝试过使用这个函数去处理#xff0c;很多情况下发现对算法的执行结果没有什么优化。但是通过刚刚那三篇文章#xff0c;确实发现在特定的场合下恰当的使用…难丫低拐这个解释很简单最明显的要求是内存地址必须是16字节对齐的。似乎看不出什么核心的信息。以前我也尝试过使用这个函数去处理很多情况下发现对算法的执行结果没有什么优化。但是通过刚刚那三篇文章确实发现在特定的场合下恰当的使用他确实能带来不晓得新能提升。一个核心的概念就是隐藏在这个函数背后的一些写内存机制(Write Combine Buffer)鉴于自己对硬件的一知半解我也不能说出个所以然来百度也看了半天把一些只言片语整理下分享下吧Write Combine Buffer 简介Write Combine Buffer写合并缓冲区是处理器内存系统中的一个组件主要用于优化写操作性能。当启用写合并机制时CPU会将写入的数据先暂存到这个缓冲区然后以突发burst传输方式高效地推送到系统总线。这种方式能显著减少内存访问延迟因为从缓冲区推送数据的延迟约10多纳秒远低于直接从主内存读取的延迟约100多纳秒从而提升写吞吐量。??缓冲区特性?WC缓冲区大小和条目数量有限具体取决于CPU架构如Intel Skylake处理器有4个可用WC缓冲区。? 缓冲区仅对同一缓存行的写操作有效若后续写操作地址分散则无法合并。??适用场景?最适合连续、非有序的写入如图形内存、日志记录等。对于需要强一致性的内存区域如普通程序数据应避免使用WC模式以免引发数据竞争。?当CPU执行一个Store操作时它将会把数据写到离CPU最近的L1的数据缓存如果这个时候发生Write miss, 则CPU将会去L2缓存为了减少Write Miss带来的性能开销Intel和其它很多型号的CPU都引入了Write Combining 技术。Write Combining Buffer不是编程时内存里的Buffer而是CPU里面真实的存储单元是硬件。当发生L1 Write Miss时WC 可以把多个对同一缓存行Store操作的数据放在WC中在程序对相应缓存行或者理解为这些数据读之前先合并等到需要读取时再一次性写入来减少写的次数和总线的压力。此时CPU可以在把数据放入WC后继续执行指令减少了很多时钟周期的浪费。不同的CPU, WC的数量可能是不一样的。Intel的CPU中其实只有4个WC可以真正被我们同时使用。_mm_stream_si128 指令说明_mm_stream_si128 是SSE指令集中的一个内置函数intrinsics用于执行非临时写操作。它属于MOVNTMove Non-Temporal类指令旨在将数据直接写入内存而不污染缓存即不将数据加载到L1/L2缓存中适用于大数据量传输场景如流式数据处理。??功能?该指令将128位数据如__m128i类型写入指定内存地址通常用于避免缓存争用提升内存带宽利用率。?与写合并的关系?虽然公开资料未直接说明 _mm_stream_si128 是否利用写合并缓冲区但非临时写操作通常与写合并机制兼容。这是因为写合并缓冲区专门处理延迟写合并以优化性能而 _mm_stream_si128 的非临时特性可能使其更高效地利用缓冲区减少缓存污染。不过具体行为取决于CPU架构和内存类型如写合并内存类型WC。?以上的一些资料都说明这个写操作可能绕过L1缓存直接在合适的时间将数据以块为单位写入内存这个块查阅有关资料说在P6之前的电脑是32字节现在基本每个块的大小都是64字节。在高性能计算相关的文章中作者使用了转置这个算法为例说明了使用这个方法加速算法速度的过程这里稍微简单的重复下。何为转置--- 转置是行变为列列变为行的一个过程这个里面不存在什么计算就是取数据然后写数据。普通的C语言也就几行代码以灰度为例:复制代码for (int Y 0; Y DstH; Y) // 正常的循环{unsigned char* LinePS Src Y;unsigned char* LinePD Dest Y * StrideD;for (int X 0; X DstW; X){LinePD[X] LinePS[0];LinePS StrideS;}}复制代码以4000*3000的灰度图转置为3000*4000的为例这个C代码执行100次转置需要4616ms。使用SIMD指令集优化时通常一个简单的做法就是分块比如当使用SSE时一个合适的块大小是8*8这个时候可以一次性的完成64个像素的转置相关的代码如下复制代码// 灰度数据的8*8转置// A0 A1 A2 A3 A4 A5 A6 A7 A0 B0 C0 D0 E0 F0 G0 H0// B0 B1 B2 B3 B4 B5 B6 B7 A1 B1 C1 D1 E1 F1 G1 H1// C0 C1 C2 C3 C4 C5 C6 C7 A2 B2 C2 D2 E2 F2 G2 H2// D0 D1 D2 D3 D4 D5 D6 D7 A3 B3 C3 D3 E3 F3 G3 H3// E0 E1 E2 E3 E4 E5 E6 E7 A4 B4 C4 D4 E4 F4 G4 H4// F0 F1 F2 F3 F4 F5 F6 F7 A5 B5 C5 D5 E5 F5 G5 H5// G0 G1 G2 G3 G4 G5 G6 G7 A6 B6 C6 D6 E6 F6 G6 H6// H0 H1 H2 H3 H4 H5 H6 H7 A7 B7 C7 D7 E7 F7 G7 H7void Transpose8x8_8U_Stirde(unsigned char* Src, unsigned char* Dest, int StrideS, int StrideD){__m128i S0 _mm_loadl_epi64((__m128i*)(Src 0 * StrideS)); // A0 A1 A2 A3 A4 A5 A6 A7 0 0 0 0 0 0 0 0__m128i S1 _mm_loadl_epi64((__m128i*)(Src 1 * StrideS)); // B0 B1 B2 B3 B4 B5 B6 B7 0 0 0 0 0 0 0 0__m128i S2 _mm_loadl_epi64((__m128i*)(Src 2 * StrideS)); // C0 C1 C2 C3 C4 C5 C6 C7 0 0 0 0 0 0 0 0__m128i S3 _mm_loadl_epi64((__m128i*)(Src 3 * StrideS)); // D0 D1 D2 D3 D4 D5 D6 D7 0 0 0 0 0 0 0 0__m128i S4 _mm_loadl_epi64((__m128i*)(Src 4 * StrideS)); // E0 E1 E2 E3 E4 E5 E6 E7 0 0 0 0 0 0 0 0__m128i S5 _mm_loadl_epi64((__m128i*)(Src 5 * StrideS)); // F0 F1 F2 F3 F4 F5 F6 F7 0 0 0 0 0 0 0 0__m128i S6 _mm_loadl_epi64((__m128i*)(Src 6 * StrideS)); // G0 G1 G2 G3 G4 G5 G6 G7 0 0 0 0 0 0 0 0__m128i S7 _mm_loadl_epi64((__m128i*)(Src 7 * StrideS)); // H0 H1 H2 H3 H4 H5 H6 H7 0 0 0 0 0 0 0 0__m128i S01 _mm_unpacklo_epi8(S0, S1); // A0 B0 A1 B1 A2 B2 A3 B3 A4 B4 A5 B5 A6 B6 A7 B7__m128i S23 _mm_unpacklo_epi8(S2, S3); // C0 D0 C1 D1 C2 D2 C3 D3 C4 D4 C5 D5 C6 D6 C7 D7__m128i S45 _mm_unpacklo_epi8(S4, S5); // E0 F0 E1 F1 E2 F2 E3 F3 E4 F4 E5 F5 E6 F6 E7 F7__m128i S67 _mm_unpacklo_epi8(S6, S7); // G0 H0 G1 H1 G2 H2 G3 H3 G4 H4 G5 H5 G6 H6 G7 H7__m128i S0123L _mm_unpacklo_epi16(S01, S23); // A0 B0 C0 D0 A1 B1 C1 D1 A2 B2 C2 D2 A3 B3 C3 D3__m128i S0123H _mm_unpackhi_epi16(S01, S23); // A4 B4 C4 D4 A5 B5 C5 D5 A6 B6 C6 D6 A7 B7 C7 D7__m128i S4567L _mm_unpacklo_epi16(S45, S67); // E0 F0 G0 H0 E1 F1 G1 H1 E2 F2 G2 H2 E3 F3 G3 H3__m128i S4567H _mm_unpackhi_epi16(S45, S67); // E4 F4 G4 H4 E5 F5 G5 H5 E6 F6 G6 H6 E7 F7 G7 H7__m128i T0 _mm_unpacklo_epi32(S0123L, S4567L); // A0 B0 C0 D0 E0 F0 G0 H0 A1 B1 C1 D1 E1 F1 G1 H1__m128i T1 _mm_unpackhi_epi32(S0123L, S4567L); // A2 B2 C2 D2 E2 F2 G2 H2 A3 B3 C3 D3 E3 F3 G3 H3__m128i T2 _mm_unpacklo_epi32(S0123H, S4567H); // A4 B4 C4 D4 E4 F4 G4 H4 A5 B5 C5 D5 E5 F5 G5 H5__m128i T3 _mm_unpackhi_epi32(S0123H, S4567H); // A6 B6 C6 D6 E6 F6 G6 H6 A7 B7 C7 D7 E7 F7 G7 H7_mm_storel_epi64((__m128i*)(Dest 0 * StrideD), T0); // A0 B0 C0 D0 E0 F0 G0 H0_mm_storeh_epi64((__m128i*)(Dest 1 * StrideD), T0); // A1 B1 C1 D1 E1 F1 G1 H1_mm_storel_epi64((__m128i*)(Dest 2 * StrideD), T1); // A2 B2 C2 D2 E2 F2 G2 H2_mm_storeh_epi64((__m128i*)(Dest 3 * StrideD), T1); // A3 B3 C3 D3 E3 F3 G3 H3_mm_storel_epi64((__m128i*)(Dest 4 * StrideD), T2); // A4 B4 C4 D4 E4 F4 G4 H4_mm_storeh_epi64((__m128i*)(Dest 5 * StrideD), T2); // A5 B5 C5 D5 E5 F5 G5 H5_mm_storel_epi64((__m128i*)(Dest 6 * StrideD), T3); // A6 B6 C6 D6 E6 F6 G6 H6_mm_storeh_epi64((__m128i*)(Dest 7 * StrideD), T3); // A7 B7 C7 D7 E7 F7 G7 H7}复制代码我们看到这里在写入内存时用的是_mm_storel_epi64而且写入时每次写入8个字节每次调一行写入。此时如果不考虑边缘的一些像素则转置可以用下面的代码实现复制代码int BlockH DstW / 8, BlockV DstH / 8;for (int Y 0; Y BlockV * 8; Y 8){unsigned char* LinePS Src Y;unsigned char* LinePD Dest Y * StrideD;for (int X 0; X BlockH * 8; X 8){Transpose8x8_8U_Stirde(LinePS X * StrideS, LinePD X, StrideS, StrideD);}}复制代码同样大小的图这个SSE版本的速度能提高到484ms。在高性能转置的文章中作者分配了一个64*64字节的缓冲区每次转置8*8的数据时并没有把数据直接用_mm_storel_epi64写入到目的图像对应的位置而是呢先写到这个64*64字节的缓冲区的合适位置当处理完64*64个大小的块8*8个8*8的小块后再从这个64*64的缓冲区的适当位置取数据然后组合下一次性的写入目标地址同一行的64字节中当目标地址符合16字节对齐时使用_mm_stream_si128不符合是使用常规的_mm_storeu_si128保存数据。复制代码// 借用Transpose8x8_8U_Stirde_Contiguous是实64*64范围的转置void Transpose64x64_8U(unsigned char* Src, unsigned char* Dest, int StrideS, int StrideD, bool UseStream){// 64x64 大小的临时Bufferalignas(64) unsigned char Buffer[64 * 64];for (int Y 0; Y 8; Y){for (int X 0; X 8; X){Transpose8x8_8U_Stirde_Contiguous(Src Y * 8 * StrideS X * 8, Buffer Y * 64 * 8 X * 64, StrideS);}}for (int Y 0; Y 8; Y){const unsigned char* Base Buffer Y * 64;for (int X 0; X 8; X){int LaneOffset X * 8;__m128i V0 _mm_loadl_epi64((__m128i*)(Base 0 * 512 LaneOffset));__m128i V1 _mm_loadl_epi64((__m128i*)(Base 1 * 512 LaneOffset));__m128i V2 _mm_loadl_epi64((__m128i*)(Base 2 * 512 LaneOffset));__m128i V3 _mm_loadl_epi64((__m128i*)(Base 3 * 512 LaneOffset));__m128i V4 _mm_loadl_epi64((__m128i*)(Base 4 * 512 LaneOffset));__m128i V5 _mm_loadl_epi64((__m128i*)(Base 5 * 512 LaneOffset));__m128i V6 _mm_loadl_epi64((__m128i*)(Base 6 * 512 LaneOffset));__m128i V7 _mm_loadl_epi64((__m128i*)(Base 7 * 512 LaneOffset));__m128i V01 _mm_unpacklo_epi64(V0, V1);__m128i V23 _mm_unpacklo_epi64(V2, V3);__m128i V45 _mm_unpacklo_epi64(V4, V5);__m128i V67 _mm_unpacklo_epi64(V6, V7);unsigned char* DstRowPtr Dest (Y * 8 X) * StrideD;if (UseStream true){// Stream 路径要求 dstRowPtr 必须 16 字节对齐_mm_stream_si128((__m128i*)(DstRowPtr 0), V01);_mm_stream_si128((__m128i*)(DstRowPtr 16), V23);_mm_stream_si128((__m128i*)(DstRowPtr 32), V45);_mm_stream_si128((__m128i*)(DstRowPtr 48), V67);}else{_mm_storeu_si128((__m128i*)(DstRowPtr 0), V01);_mm_storeu_si128((__m128i*)(DstRowPtr 16), V23);_mm_storeu_si128((__m128i*)(DstRowPtr 32), V45);_mm_storeu_si128((__m128i*)(DstRowPtr 48), V67);}}}}复制代码这种简单的组合方式表面上看是增加了计算量的保存到临时缓冲区然后又要读取数据再组合但是实际上测试性能却能比直接使用8*8的要快一些。在刚刚相同的测试图上用时158毫秒而且看实际的数据因为相关地址不符合16字节的对齐的约定因此实际上是使用_mm_storeu_si128予以保存的。为什么即使是用_mm_storeu_si128保存的数据这个速度也能有这么大的区别我个人认为这个是收到cass miss的影响的一次性保存64个字节到同一行的位置 比一次保存8个然后又跳一行的cass miss要少很多。我们又找了一副3000*2000的灰度测试图这个时候的内存是符合16字节的对齐的测试使用_mm_stream_si128进行处理结果呢比直接使用_mm_storeu_si128还慢了很多。同样的其他一些测试图也表明即使能使用_mm_stream_si128保存都是使用_mm_stream_si128更慢这是怎么回事呢。后面通过进一步的测试表面只有当转置后的目标图的宽度原图的高度正好是64的整数倍时使用_mm_stream_si128能起到比_mm_storeu_si128更快的速度比如刚刚的4000*3000的图如果是4000*3008的图使用_mm_storeu_si128时100次耗时140ms而使用_mm_stream_si128则变为了70ms。当我们处理32位图形时考虑到数据的大小一次性可以按照16*16的尺寸进行块处理这个时候只要原图的高度方向尺寸是16的整数倍则可以使用_mm_stream_si128进行加速。而处理24位则比较特殊因为考虑数据的对齐一次性只能处理48个字节。这个时候即使能使用_mm_stream_si128及原图高度是16的倍数也无法起到加速作用。以上的几个现象根据我的猜测感觉和Write Combining Buffer的块大小现在基本是64字节有关系。但是没有找到实际的理论依据。这个现象也可以找到实际的应用场景我们有很多算法是行列分离的并且在行方向或者列方向更具有实现优势一般是反方向易必行列方向易SIMD指令化比如我们常用的最大值最小值就是在反方向实现比较容易这个时候可能就要借用中间数据实现转置下然后调用同样的行方向算法或列方向算法处理完成后再转置回去因此如果转置算法的耗时占比较大则就不划算了。因此这类基础算法的优化就很有意义。这个时候根据上面的特性我们可以分配一个中间图像而且中间图像的宽度对应原图的高度设计为恰好是大于或等于原图高度的最小的64的倍数这样可以在第一次转置时获得最佳的性能。当然第二次转置就没有办法了。我们把以上的测试耗时整理比较下image几个现象1、对于灰度图当原图高度不为64的整数倍用stream会减速。2、对于24位图像使用stream都会减速。3、对于32位图原图高度为16的倍数即可用stream加速。4、4000*3000的像素数只比3000*2000的多一倍但无论是C版本还是SSE版本耗时都增加了10来倍谁来解释下为什么类似于转置这样的计算量小、但是读写内存耗时的算法还有很多比如旋转90度、180度、270度、左右镜像、多图处理加、减、平均、求大、求小等、反色等他们都可以用类似的方法来处理。我们以反色这个算法再来说明下如何使用他正常情况下一个SSE反色优化的代码如下所示复制代码int IM_Invert_SSE_Raw(unsigned char* Src, unsigned char* Dest, int Width, int Height, int Stride){int Channel Stride / Width;if (Src NULL) return IM_STATUS_NULLREFRENCE;if ((Width 0) || (Height 0)) return IM_STATUS_INVALIDPARAMETER;if ((Channel ! 1) (Channel ! 3) (Channel ! 4)) return IM_STATUS_NOTSUPPORTED;int BlockSize 64, Block (Height * Stride) / BlockSize;const __m128i Mask _mm_set1_epi32(0xFFFFFFFF);for (int Index 0; Index Block * BlockSize; Index BlockSize){__m128i V0 _mm_loadu_si128((__m128i*)(Src Index 0));__m128i V1 _mm_loadu_si128((__m128i*)(Src Index 16));__m128i V2 _mm_loadu_si128((__m128i*)(Src Index 32));__m128i V3 _mm_loadu_si128((__m128i*)(Src Index 48));__m128i T0 _mm_xor_si128(V0, Mask);__m128i T1 _mm_xor_si128(V1, Mask);__m128i T2 _mm_xor_si128(V2, Mask);__m128i T3 _mm_xor_si128(V3, Mask);_mm_storeu_si128((__m128i*)(Dest Index 0), T0);_mm_storeu_si128((__m128i*)(Dest Index 16), T1);_mm_storeu_si128((__m128i*)(Dest Index 32), T2);_mm_storeu_si128((__m128i*)(Dest Index 48), T3);}for (int Index Block * BlockSize; Index Height * Stride; Index){Dest[Index] Src[Index] ^ 0xFF;}return IM_STATUS_OK;}复制代码这个是很高效的一个算法处理一副7360*4912的24位彩色图平均耗时大概是9.99ms。但是经过我们改写用_mm_stream_si128的版本如下复制代码int IM_Invert_SSE(unsigned char* Src, unsigned char* Dest, int Width, int Height, int Stride){int Channel Stride / Width;if (Src NULL) return IM_STATUS_NULLREFRENCE;if ((Width 0) || (Height 0)) return IM_STATUS_INVALIDPARAMETER;if ((Channel ! 1) (Channel ! 3) (Channel ! 4)) return IM_STATUS_NOTSUPPORTED;unsigned long long Address (unsigned long long)Dest;int StartIndex 0;// 找到目标中首个16字节对齐的位置从这个位置开始进行处理while ((Address 0xF) ! 0) StartIndex;int BlockSize 64, Block (Height * Stride - StartIndex) / BlockSize;// 前面未对齐的部分用普通的语法吹for (int Index 0; Index StartIndex; Index){Dest[Index] Src[Index] ^ 0xFF;}// 中间对齐的部分 可以直接用stream注意如果src和dest相同这段代码没有加速作用// 可能是因为不满足写入的部分不会马上读的要求// 但是如果Src和Dest相同那么本身速度就是会比不相同快的const __m128i Mask _mm_set1_epi32(0xFFFFFFFF);for (int Index StartIndex; Index StartIndex Block * BlockSize; Index BlockSize){__m128i V0 _mm_loadu_si128((__m128i*)(Src Index 0));__m128i V1 _mm_loadu_si128((__m128i*)(Src Index 16));__m128i V2 _mm_loadu_si128((__m128i*)(Src Index 32));__m128i V3 _mm_loadu_si128((__m128i*)(Src Index 48));__m128i T0 _mm_xor_si128(V0, Mask);__m128i T1 _mm_xor_si128(V1, Mask);__m128i T2 _mm_xor_si128(V2, Mask);__m128i T3 _mm_xor_si128(V3, Mask);_mm_stream_si128((__m128i*)(Dest Index 0), T0);_mm_stream_si128((__m128i*)(Dest Index 16), T1);_mm_stream_si128((__m128i*)(Dest Index 32), T2);_mm_stream_si128((__m128i*)(Dest Index 48), T3);}// 尾部不能处理的部分for (int Index StartIndex Block * BlockSize; Index Height * Stride; Index){Dest[Index] Src[Index] ^ 0xFF;}_mm_sfence();return IM_STATUS_OK;}复制代码平均耗时降低至大概7.12ms效率又提高30%。其核心的要点就是要先找到目标地址中第一个是16整数倍的内存地址然后把原始代码中的_mm_storeu_si128改为_mm_stream_si128就OK了。这个比如在最大值和最小值算法时就有用了比如我写好了最大值那么我只要反色后用最大值然后再用最小值则就不用重新写最小值算法了而且因为反色的耗时占比很小基本不用担心耗时的差异了。