使用向量操作

  • AVX指令集和YMM寄存器
  • AVX512指令集和ZMM寄存器
  • 自动向量化
  • 使用内建函数
    • 对齐数据
    • 向量化表查找
  • 使用向量类
    • 向量类的CPU分发
  • 转换串行代码到向量化代码
  • 数学函数的向量化
  • 对齐动态分配的内存
  • 对齐RGB视频或三维向量
  • 结论

今天的微处理器有向量指令,这让在一个向量的所有元素上进行操作成为可能。这样叫单指令多数据(SIMD)操作。每个向量的大小可以是64位(MMX),128位(XMM),256位(YMM)和512位(ZMM)。
当需要在大数据集上,对多个数据执行相同的操作,并且程序逻辑也允许时,向量操作是很有用的。例如:图像处理、音频处理、向量和矩阵的数学操作。天然串行的算法,例如排序算法,不大适合向量操作。严重依赖于表查找或要求很多数据交换的算法,例如很多加密算法,是不大适合向量操作。
向量操作依赖于一系列特殊的向量寄存器。不同指令集支持的向量寄存器大小和数目各有不同。可以参考各指令集的数据。
为了高效的访问,不同向量寄存器建议的内存对齐要求也各有不同。
一般,越新的处理器,向量处理也越快。
通常,越小的元素,向量操作越有益。

AVX指令集和YMM寄存器

128位的XMM寄存器被扩展到256位,在AVX指令集中称为YMM。AVX指令集的主要好处是,它允许更大的浮点向量。AVX2指令集也允许256位整数向量。
代码针对AVX指令集编译的话,只能在CPU和操作系统都支持AVX时才能运行。

AVX512指令集和ZMM寄存器

256位的YMM寄存器扩展为512位的ZMM寄存器,在AVX512指令集。AVX512指令集中,64位模式有32个向量寄存器,而32位模式只有8个。因此,AVX512代码最好编译为64位模式。

自动向量化

好的编译器,会在明显的并行场景中,自动使用向量寄存器。详细的指令可以参考编译器文档。例如:

// Example 12.1a. Automatic verctorization
const int size = 1024;
int a[size], b[size];
for (int i = 0; i < size; i++) {a[i] = b[i] + 2;
}

当使用SSE2或更新的指令集时,好的编译器会使用向量操作来优化这个循环。根据可用的指令集,每次操作4、8或16个元素。当循环次数可以被一个向量的元素数整除时,向量操作优化效果是最好的。可以在数组后边添加一些多于元素,使得数组长度可以被向量长度整除。

当数据是通过指针访问时,是不利的。例如:

// Example 12.1b. Vectorization with alignment problem
void AddTwo(int * __restrict aa, int * __restrict bb) {for (int i = 0; i < size; i++) {aa[i] = bb[i] + 2;}
}

如果数组按照向量大小对齐,性能是最好的。有几个方式可以使通过指针和引用访问数据的代码更高效:

  • 如果用的是Intel编译器,那么用#pragma vector aligned__assume_aligned指令告知编译器数据是对齐的,并且要确保数组确实对齐。
  • 把函数声明为inline。这可能让编译器把12.1b的代码简化为12.1a。
  • 如果可能,使用最大向量大小的指令集。AVX或更新的指令集对于对齐有非常少的限制。

如果满足下列条件,那么自动向量化可以工作的最好:

  1. 使用对自动向量化支持的较好的编译器,例如Gnu、Clang或Intel。
  2. 使用自信版本的编译器。
  3. 使用编译器选项来启用期望的指令集。(windows下的/arch:SSE2,/arch:AVX等;Linux下的-msse2,-mavx512f等)。
  4. 使用限制较少的浮点选项。对于Gnu和Clang编译器,使用-O3 -fno-trapping-math -fno-math-errno -fno-signed-zeros(-ffast-math工作良好,但是像isnan(x)-ffast-math下没有作用)。
  5. 对于SSE2,把数据和大的结构体以16对齐,AVX以32,AVX512以64。
  6. 循环计数器应该是一个常量,它可以被向量中的元素数目整数。
  7. 如果数组通过指针访问,使得函数声明周期内对齐不可见,那么参考上边调到的建议。
  8. 如果数组或结构体通过指针或引用访问,如果合适那么显示的告知编译器指针没有别名。如果做可以参考编译器文档。
  9. 在向量元素级别尽量少的使用分支。
  10. 避免向量元素级别的查表操作。

你可以查看汇编输出来确定向量化是否如预期。

由于一些原因,编译器可能向量化代码失败,或让代码不必要的复杂化。最重要的组织自动向量化的障碍如下:

  • 编译器不能消除指向重叠或别名地址的数据指针。
  • 编译器不能消除会产生一般异常或其他副作用的无效分支。
  • 编译器不知道是否一个数组大小是向量大小的倍数。
  • 编译器不知道数据结构是否正确对齐。
  • 数据需要重排来适应向量。
  • 代码太复杂。
  • 代码调用外部函数,而外部函数没有向量版本。
  • 代码使用查找表。

在对一系列连续变量进行相同的操作时,即使没有循环,编译器也可以使用向量操作。例如:

// Example 12.2
struct alignas(16) S1 {           // Structure of 4 floats, alignedfloat a, b, c, d;
};void Func() {S1 x, y;x.a = y.a + 1.;x.b = y.b + 2.;x.c = y.c + 3.;x.d = y.d + 4.;
}

四个浮点数的结构体适合128位 XMM寄存器。对于例子12.2,优化的代码会加载结构体y到一个向量寄存器,加上常量寄存器(1, 2, 3, 4),并存储结果到x

编译器并不总能正确地预测向量化是否有益。编译器可能有#pragma或其他指令用来告知编译器哪个循环可以向量化。

使用内建函数

预测编译器是否互向量化一个循环是困难的。例如:

// Example 12.4a. Loop with branch
// Loop with branch
void SelectAddMul(short int aa[], short int bb[], short int cc[]) {for (int i = 0; i < 256; i++) {aa[i] = (bb[i] > 0) ? (cc[i] +2) : (bb[i] * cc[i]);}
}

通过使用所谓的内建函数显示地向量化代码是可能的。这对于例12.4a这种编译器并不总是向量化的代码是有用的。也对向量化后的代码不够优化的情形有用。

内建函数和汇编有点像,因为大多数内建函数会被转换为特定的机器指令。与汇编相比,内建函数是易用且安全的,因为编译器负责寄存器分配,函数调用惯例等。更大的好处是,编译器能够通过重排指令、通用子表达式消除等方式优化代码。Gnu、Clang、Intel和Microsoft编译器支持内建函数。Gnu和Clang的性能最佳。

要优化例子12.4a的代码,以便可以在8个16位整数寄存器中同时处理8个元素。循环里的代码根据不同的指令集可以有不同的实现方式。下边的例子显示了对于SSE2指令集,使用内建函数的实现方式:

// Example 12.4b. Vectorized with SSE2
#include <emmintrin.h>                      // Define SSE2 intrinsic functions// Function to load unaligned integer vector from array
static inline __m128i LoadVector(void const * p) {return _mm_loadu_si128((__m128i const*)p);
}// Function to store unaligned integer vector into array
static inline void StoreVector(void * d, __m128i const & x) {_mm_storeu_si128((__m128i *)d, x);
}// Branch/loop function vectorized:
void SelectAddMul(short int aa[], short int bb[], short int cc[]) {// Make a vector of (0, 0, 0, 0, 0, 0, 0, 0)__m128i zero = _mm_setzero_si128();// Make a vector of (2, 2, 2, 2, 2, 2, 2, 2)__m128i two = _mm_set1_epi16(2);// Roll out loop by eight to fit the eight-element vectors:for (int i = 0; i< 256; i += 8) {// Load eight consecutive elements from bb into vector b:__m128i b = LoadVector(bb + i);// Load eight consecutive elements from cc into vector c:__m128i c = LoadVector(cc + i);// Add 2 to each element in vector c__m128i c2 = _mm_add_epi16(c, two);// Multiply b and c__m128i bc = _mm_mullo_epi16(b, c);// Compare each element in b to 0 and generate a bit-mask:__m128i mask = _mm_cmpgt_epi16(b, zero);// AND each element in vector c2 with the bit-mask:c2 = _mm_and_si128(c2, mask);// AND each element in vector bc with the inverted bit-mask:bc = _mm_andnot_si128(mask, bc);// OR the results of the two AND operations:__m128i a = _mm_or_si128(c2, bc);// Store the result vector in eight consecutive elements in aa:StoreVector(aa + i, a);}
}

上述代码更高效,因为它每次处理8个元素,并且避免了循环内的分支。例12.4b比例12.4a运行快3-7倍,具体取决于循环里的分支预测。
如果SSE4.1指令集可用,那么上述代码中的AND-OR操作可以用一个混合指令替换:

// Example 12.4c. Vectorized with SSE4.1
#include <immintrin.h>// Function to load unaligned integer vector from array
static inline __m128i LoadVector(void const * p) {return _mm_loadu_si128((__m128i const*)p);
}// Function to store unaligned integer vector into array
static inline void StoreVector(void * d, __m128i const & x) {_mm_storeu_si128((__m128i *)d, x);
}// Branch/loop function vectorized:
void SelectAddMul(short int aa[], short int bb[], short int cc[]) {// Make a vector of (0, 0, 0, 0, 0, 0, 0, 0)__m128i zero = _mm_setzero_si128();// Make a vector of (2, 2, 2, 2, 2, 2, 2, 2)__m128i two = _mm_set1_epi16(2);// Roll out loop by eight to fit the eight-element vectors:for (int i = 0; i< 256; i += 8) {// Load eight consecutive elements from bb into vector b:__m128i b = LoadVector(bb + i);// Load eight consecutive elements from cc into vector c:__m128i c = LoadVector(cc + i);// Add 2 to each element in vector c__m128i c2 = _mm_add_epi16(c, two);// Multiply b and c__m128i bc = _mm_mullo_epi16(b, c);// Compare each element in b to 0 and generate a bit-mask:__m128i mask = _mm_cmpgt_epi16(b, zero);// Use mask to choose between c2 and bc for each element__m128i a = _mm_blendv_epi8(bc, c2, mask);// Store the result vector in eight consecutive elements in aa:StoreVector(aa + i, a);}
}

AVX512指令集提供了更加高效的分支、使用掩码寄存器和条件指令的方法:

// Example 12.4d. Vectorized with AVX512F
#include <immintrin.h>
#include <x86intrin.h>// Branch/loop function vectorized:
void SelectAddMul(short int aa[], short int bb[], short int cc[]) {// Make a vector of all 0__m512i zero = _mm512_setzero_si512();// Make a vector of all 2__m512i two = _mm512_set1_epi16(2);// Roll out loop by 32 to fit the 32-element vectors:for (int i = 0; i< 256; i += 32) {// Load 32 consecutive elements from bb into vector b:__m512i b = _mm512_loadu_si512(bb + i);// Load eight consecutive elements from cc into vector c:__m512i c = _mm512_loadu_si512(cc + i);// Multiply b and c__m512i bc = _mm512_mullo_epi16(b, c);// Compare each element in b to 0 and generate a 32bit-mask// Each bit in mask is 1 for b[i] > 0__mmask32 mask = _mm512_cmp_epi16_mask(b, zero, 6);// Conditionally add 2 to each element in vector c.// Select c+2 when mask bit = 1, bc when mask bit = 0__m512i r = _mm512_mask_add_epi16(bc, mask, c, two);// Store the result vector in eight consecutive elements in aa:_mm512_storeu_epi16(aa + i, r);}
}

你必须确保CPU支持对应的指令集,否则程序会崩溃。除了Microsoft,所有的编译器都允许用命令行指定使用哪个指令集编译。

对齐数据

如果数据在内存里的地址以向量大小(16,32,或64字节)对齐,那么加载数据会更快。在旧型号的处理器上更加明显。新的处理器上不那么重要。下例显示了如何对其数据:

// Example 12.5. Aligned arrays
const int size = 256;
alignas (16) int16_t aa[size];  // Make aligned array

向量化表查找

查找表对于优化代码很有用。不幸的是,表查找通常是向量化的障碍。AVX2和更新的指令集已经有对表查找有用的聚合指令。一个32位或64位整数向量提供一个索引表在内存里。结果是一个32位或64位的整数、浮点或双精度值的向量。聚合指令花费几个时钟周期,因为元素是一个一个读的。
如果查找表小得可以放进一个或几个向量寄存器,那么还是给表查找用排列指令是更高效的。

使用内建函数通常是冗繁的,代码会庞大且易读性较差。通常使用向量类更容易。

使用向量类

通过封装向量到C++类中,使用操作符重载,以更清晰和智能的方式实现与内建函数相同效果是可能的。操作符内联以便最终的机器码与使用内建函数相同。
向量类的好处:

  • 可以指定代码的哪部分向量化和如何向量化。
  • 可以克服自动向量化的障碍。
  • 代码通常更简单,与编译器自动生成的相比。因为编译器不得不处理与应用程序无关的特殊情况。
  • 代码更简洁和易读,与汇编或使用内建函数相比,却效率相同。

现在,预定义向量类的各种库可用。例如,Intel的向量库和开源库https://github.com/vectorclass。

256位和512位向量只有在CPU和操作系统支持时才可用。例12.4b的向量版本:

// Example 12.4d. Same example, using Intel vector classes
#include <dvec.h>           // Define vector classes// Function to load unaligned integer vector from array
static inline __m128i LoadVector(void const * p) {return _mm_loadu_si128((__m128i const*)p);
}// Function to store unaligned integer vector into array
static inline void StoreVector(void * d, __m128i const & x) {_mm_storeu_si128((__m128i *)d, x);
}void SelectAddMul(short int aa[], short int bb[], short int cc[]) {// Make a vector of (0, 0, 0, 0, 0, 0, 0, 0)Is16vec8 zero(0, 0, 0, 0, 0, 0, 0, 0);// Make a vector of (2, 2, 2, 2, 2, 2, 2, 2)Is16vec8 two(2, 2, 2, 2, 2, 2, 2, 2);// Roll out loop by eight to fit the eight-element vectors:for (int i = 0; i< 256; i += 8) {// Load 8 consecutive elements from bb into vector b:Is16vec8 b = LoadVector(bb + i);// Load 8 consecutive elements from bb into vector c:Is16vec8 c = LoadVector(cc + i);// result = b > 0 ? c + 2 : b * c;Is16vec8 a = select_gt(b, zero, c + two, b * c);// Store the result vector in eight consecutive elements in aa:StoreVector(aa + i, a);}
}

较旧版本的Microsoft编译器由于对齐问题不允许向量对象作为函数参数。这时,建议使用常量引用替代:

// Example 12.6. Function with vector parameters
Vec4f polynomial (Vec4f const & x) {// polynomial(x) = 2.5*x^2 - 8*x + 2return (2.5f * x - 8.0f) * x + 2.0f;
}

向量类的CPU分发

下边的例子显示了如何根据支持的指令集进行自动CPU分发。

// Example 12.7. Vector class code with automatic CPU dispatching
#include "vectorclass.h" // vector class library
#include <stdio.h> // define fprintf
// define function type
typedef void FuncType(short int aa[], short int bb[], short int cc[]);
// function prototypes for each version
FuncType SelectAddMul, SelectAddMul_SSE2, SelectAddMul_SSE41,
SelectAddMul_AVX2, SelectAddMul_AVX512BW, SelectAddMul_dispatch;
// Define function name depending on instruction set
#if INSTRSET == 2 // SSE2
#define FUNCNAME SelectAddMul_SSE2
#elif INSTRSET == 5 // SSE4.1
#define FUNCNAME SelectAddMul_SSE41
#elif INSTRSET == 8 // AVX2
#define FUNCNAME SelectAddMul_AVX2
#elif INSTRSET == 10 // AVX512BW
#define FUNCNAME SelectAddMul_AVX512BW
#endif
// specific version of the function. Compile once for each version
void FUNCNAME(short int aa[], short int bb[], short int cc[]) {Vec32s a, b, c; // Define biggest possible vector objects// Roll out loop by 32 to fit the biggest vectors:for (int i = 0; i < 256; i += 32) {b.load(bb+i);c.load(cc+i);a = select(b > 0, c + 2, b * c);a.store(aa+i);}
}
#if INSTRSET == 2
// make dispatcher in only the lowest of the compiled versions
#include "instrset_detect.cpp" // instrset_detect function
// Function pointer initially points to the dispatcher.
126
// After first call it points to the selected version
FuncType * SelectAddMul_pointer = &SelectAddMul_dispatch;
// Dispatcher
void SelectAddMul_dispatch(short int aa[], short int bb[],short int cc[]) {// Detect supported instruction setint iset = instrset_detect();// Set function pointerif (iset >=10) SelectAddMul_pointer = &SelectAddMul_AVX512BW;else if (iset >= 8) SelectAddMul_pointer = &SelectAddMul_AVX2;else if (iset >= 5) SelectAddMul_pointer = &SelectAddMul_SSE41;else if (iset >= 2) SelectAddMul_pointer = &SelectAddMul_SSE2;else {// Error: lowest instruction set not supportedfprintf(stderr, "\nError: Instruction set SSE2 not supported");return;}// continue in dispatched versionreturn (*SelectAddMul_pointer)(aa, bb, cc);
}
// Entry to dispatched function call
inline void SelectAddMul(short int aa[], short int bb[], short int cc[]) {// go to dispatched versionreturn (*SelectAddMul_pointer)(aa, bb, cc);
}
#endif // INSTRSET == 2

转换串行代码到向量化代码

不是所有的代码都有一个并行结构让它可以比较容易的向量化。许多代码是串行场景的,每个计算依赖于前一个计算结果。如果代码是重复性的,那么把代码组织为可以向量化的形式是可能的。最简单的例子是一个长列表元素的求和:

// Example 12.8a. Sum of a list
float a[100];
float sum = 0;
for (int i = 0; i < 100; i++) sum += a[i];

一个技巧是以n展开循环,n是向量中元素个数。例如n=4时:

// Example 12.8b. Sum of a list, rolled out by 4
float a[100];
float sum = 0,s1 = 0, s2 = 0, s3 = 0, s4 = 0;
for (int i = 0; i < 100; i += 4) {s1 += a[i];s2 += a[i+1];s3 += a[i+2];s4 += a[i+3];
}
sum = (s1 + s2) + (s3 + s4);

现在,s1,s2,s3,s4可以被组合为一个128位的向量,以便我们可以一次操作完成四次加法。

一些编译器可以自动完成上述优化和向量化。但更复杂的情形,编译器则无能无力。例如,泰勒级数。指数函数可以用泰勒级数计算。
e x = ∑ n = 0 ∞ x n n ! e^{x}=\sum_{n=0}^{\infty }\frac{x^{n}}{n!} ex=n=0∑∞​n!xn​

一个C++实现可能如下:

// Example 12.9a. Taylor series
float Exp(float x) {                    // Approximate exp(x) for small xfloat xn = x;                       // x^nfloat sum = 1.f;                    // sum, initialize to x^0/0!float nfac = 1.f;                   // n factorialfor (int n = 1; n <= 16; n++) {sum += xn / nfac;xn *= x;nfac *= n+1;}return sum;
}

可以以4展开循环进行优化。

// Example 12.9b. Taylor series, vectorized
#include "vectorclass.h" // Vector class library
// Approximate exp(x) for small x
float Exp(float x) {// table of 1/n!alignas(16) const float coef[16] = {1., 1./2., 1./6., 1./24., 1./120., 1./720., 1./5040.,1./40320., 1./362880., 1./3628800., 1./39916800.,1./4.790016E8, 1./6.22702E9, 1./8.71782E10,1./1.30767E12, 1./2.09227E13};float x2 = x * x; // x^2float x4 = x2 * x2; // x^4// Define vectors of four floatsVec4f xxn(x4, x2*x, x2, x); // x^1, x^2, x^3, x^4Vec4f xx4(x4); // x^4Vec4f s(0.f, 0.f, 0.f, 1.f); // initialize sumfor (int i = 0; i < 16; i += 4) { // Loop by 4s += xxn * Vec4f().load(coef+i); // s += x^n/n!xxn *= xx4; // next four x^n}return horizontal_add(s); // add the four sums
}

这个循环在一个向量里计算四个连续的项。进一步展开循环可能是有益的,如果循环更长的话,因为这里的性能更可能受限于乘法,而不是输出。这里的系数表在编译时计算。可能在运行时计算系数是更方便的,如果你可以保证只计算一次,而不是每次函数调用都要计算系数表。

数学函数的向量化

有各种函数库以向量方式来计算数学函数,例如:对数、幂函数、三角函数等。这些函数库对向量化数学代码是有用的。
有两种不同种类的向量数学库:长向量库和短向量库。来看看它们的不同。假设要计算1000个数字的某个函数。用长向量库,把1000个数字的数组作为参数传给库函数,这个库函数存储这1000个结果到另一个数组。使用长向量版库函数的缺点是,如果要做一系列计算,在下一次调用函数前就需要存储中间结果到一个临时数组中。用短向量版本的向量库,可以把数据集拆分为子向量来适配向量寄存器。如果向量寄存器可以处理4个数字,那么需要调用250此库函数。这个库函数会返回结果到向量寄存器中,可以直接被下一次计算利用,而不需要存储中间结果到RAM中。这可能更快。然而,短向量的库函数可能是不利的,如果计算序列形成了长依赖链。

这是一些长向量函数库:

  • Intel 向量数学库(VML, MKL)。工作在x86平台。这些库函数在非Intel的CPU上会低效,除非重写了Intel cpu分发器。
  • Intel的IPP。工作在x86平台。也适用于非Intel的CPU。包含很多统计、信号处理和图像处理函数。
  • Yeppp。开源库。支持x86和ARM平台,多种编程语言。参考Yeppp。

这是一些短向量库:

  • Sleef库。支持多种平台。开源。参考www.sleef.org。
  • Intel短向量库(SVML)。Intel编译器提供,被自动向量化调用。Gnu编译器可以通过选项-mveclibabi=svml使用这个库。如果用的是非Intel的CPU,也可以使用。
  • AMD LIBM库。只支持64位Linux平台。没有FMA4指令集时,性能会降低。Gnu通过-mveclibabi=acml选项使用。
  • VCL库。个人开发。参考https://github.com/vectorclass。

所有这些库都有好的性能和精度。比非向量库快很多倍。

对齐动态分配的内存

使用newmalloc分配的内存根据平台不同,以8或16 对齐。这对于需要16或更多字节对齐的向量操作是一个问题。C++17标准给出了使用new时自动按需对齐的方式:

// Example 12.10
// Aligned memory allocation in C++17
int arraysize = 100;
__m512 * pp = new __m512[arraysize];
// pp will be aligned by alignof(__m512) if using c++17

对齐RGB视频或三维向量

RGB图像数组每个点有三个值。这不适合四个浮点数的向量。这种情况同样出现在三维地理信息和其他奇数尺寸的向量数据。出于效率考虑,这些数据需要以向量大小赌气。使用非对齐的读写可能会减慢向量化代码的执行速度。根据问题的算法不同,可以选择以下的解决方案:

  • 添加无用的第四个值,使数据符合向量。副作用是增加内存消耗。如果内存是瓶颈,要避免这个方法。
  • 以4或8为一组来分组数据。例如4个R值在一个向量,4个G值在下一个向量,4个B值在最后一个向量。
  • 把所有的R值组织在仪器,然后是所有的G值,最后是所有的B值。

选择哪个方法,取决于算法是否合适。如果点的数量不能被向量大小整除,那么添加几个额外的无用的点到结尾来获取整数数目的向量。

结论

如果算法允许并行计算,那么使用向量代码能获得运行速度的提升。提升多少取决于每个向量的元素数量。最简洁的方式是依靠编译器的自动向量化。对于简单情形、标准操作,编译器会自动向量化代码,只要你打开合适的指令集和相应的编译选项。

然而,有许多情形编译器不能自动向量化代码或者处理的不够优化。这时,你就需要显示的向量化代码。几种常见的方式:

  • 使用汇编语言
  • 使用内建函数
  • 使用预定义向量类

最简单的显示向量化代码方法是使用向量类库。类库和内建函数可以结合使用。内建函数和向量类库通常只是方便与否,性能几乎没有区别。

一个好的编译器,通常在你显示向量化代码后可以更好的优化代码。编译器可以使用诸如内联函数、通用子表达式消除、常量传播、循环优化等技术来优化代码。这些技术很少在手工汇编代码使用,因为它会是代码庞大,易出错,并且几乎不可维护。当前的编译器在常量传播和向量代码优化上表现的不够好。你可以查看汇编输出或调试器中的反汇编来查看编译器做了什么。

向量化的代码通常包含额外的指令来转换数据到适合的格式以及把数据放到向量的正确位置。这些数据转换和重排有时比真正的计算更耗时。在决定向量化是否合适时,要考虑这个因素。

利于进行向量化的因素:

  • 小数据类型:char, int16_t, float
  • 大数组中所有元素进行相似的处理。
  • 数组大小可以被向量大小整除。
  • 在两个简单表达式间选择的不可预测的分支。
  • 只有向量操作数可用的操作:最小量、最大量、饱和加法、快速近似倒数、快速近似倒数平方根、RGB颜色差值。
  • 向量指令集可用。
  • 数学向量函数库
  • 使用Gnu或Clang编译器。

利于不进行向量化的因素:

  • 大数据类型:int64_t,double
  • 非对齐的数据
  • 需要额外的数据转换,重排,打包,解包。
  • 预测分支时,有的分支可以跳过庞大的表达式。
  • 编译器没有指针对齐和别名的足够信息。
  • 向量指令集中没有对应的操作。
  • 旧型号的CPU中向量寄存器大小太小。

向量化代码对于开发者来说更难开发,因此也更容易出错。因此,向量化代码应该放在重用和经过良好测试的模块和头文件中。

C++性能优化笔记-11-使用向量操作相关推荐

  1. U3D开发性能优化笔记(待增加版本.x)

    Amir Fasshihi 优化方案: 一.遇到麻烦时要调用"垃圾回收器"(Garbage Collector,无用单元收集程序,以下简称GC) 由于具有C/C++游戏编程背景,我 ...

  2. php 频繁dom和 文件,性能优化之为什么不要频繁操作DOM

    性能优化:为什么不要频繁操作DOM @[toc] 性能优化的时候,我们常说"不要频繁操作DOM",但是"DOM 为什么这么慢"以及"如何使 DOM 变 ...

  3. ActiveMQ性能优化笔记

    ActiveMQ性能优化笔记 队列的类型 环境搭建 踩到的坑 消息入队的性能 消息持久化 同步/异步发送消息 acitveMQ服务器通信方式 消费者消费 optimizeACK 以前都是看看消息队列的 ...

  4. 详细的聊聊接口性能优化的11个小技巧 不收藏对不起我

    前言 接口性能优化对于从事后端开发的同学来说,肯定再熟悉不过了,因为它是一个跟开发语言无关的公共问题. 该问题说简单也简单,说复杂也复杂. 有时候,只需加个索引就能解决问题. 有时候,需要做代码重构. ...

  5. Android性能优化 笔记

    说明 这篇文章是将很久以来看过的文章,包括自己写的一些测试代码的总结.属于笔记的性质,没有面面俱到,一些自己相对熟悉的点可能会略过. 最开始看到的性能优化的文章,就是胡凯的优化典范系列,后来又陆续看过 ...

  6. 聊聊接口性能优化的11个小技巧

    点击下方"IT牧场",选择"设为星标" 前言 接口性能优化对于从事后端开发的同学来说,肯定再熟悉不过了,因为它是一个跟开发语言无关的公共问题. 该问题说简单也简 ...

  7. 细说接口性能优化的11个小技巧

    前言 接口性能优化对于从事后端开发的同学来说,肯定再熟悉不过了,因为它是一个跟开发语言无关的公共问题. 该问题说简单也简单,说复杂也复杂. 有时候,只需加个索引就能解决问题. 有时候,需要做代码重构. ...

  8. Android性能优化笔记(一)——启动优化

    本文主要是学习了极客时间张绍文老师的 Android开发高手课 以及 谷歌官网文章 的启动优化笔记~ 参考文章:  https://time.geekbang.org/column/article/7 ...

  9. 看看别人后端API接口性能优化的11个方法,那叫一个优雅!

    前言 接口性能优化对于从事后端开发的同学来说,肯定再熟悉不过了,因为它是一个跟开发语言无关的公共问题. 该问题说简单也简单,说复杂也复杂. 有时候,只需加个索引就能解决问题. 有时候,需要做代码重构. ...

最新文章

  1. 转 前端工程师凭什么这么值钱?
  2. python实现字符串切割
  3. android 程序安装路径选择
  4. android view 点击变暗,Android应用开发Android ImageView点击变暗效果
  5. 电脑计算器_哪几种计算器可以携带入考场!注会考试忘带计算器了怎么办?
  6. 基于JAVA+SpringBoot+Mybatis+MYSQL的在线论坛管理系统
  7. 计算机考试属于相对性评价还是绝对性评价,【易错检测】查漏补缺第46天 — 相对性评价与绝对性评价...
  8. 软件工程Java毕设 SSM企业公寓宿舍后勤管理系统(含源码+论文)
  9. activiti6超详细教程
  10. MyBatis出现参数索引越界
  11. android app 后台运行,安卓APP锁定后台运行的方法
  12. python gtk_python 创建gtk应用程序
  13. php cms下载地址,phpcms将下载地址替换为图片显示
  14. 明尼苏达量表结果分析_MMPI明尼苏达多项人格测验量表分析
  15. loglog()函数
  16. java编写爬虫_Java怎么写网络爬虫?分分钟带你爬取,源码
  17. 班章管家:理财产品哪个好一些?从以下几个方面比较
  18. java 验证手机号_Java使用正则表达式验证手机号和电话号码的方法
  19. 第十三章 外星人来了
  20. Xilinx FPGA的专用时钟引脚及时钟资源相关

热门文章

  1. 游戏玩家的程序猿之路
  2. 貝塞爾 Layer 入門指南
  3. C++中的exit()函数
  4. 吐槽下Arcgis的二次开发
  5. java 随机16位随机数字_随机生成防猜不重复的16位纯数字序列号【快速且高性能】...
  6. 异常报错原因及解决方案
  7. IDEA: 自动优化导包 Auto Import
  8. Android Studio 笔记3.3 相对布局
  9. 全球与中国聚 (3,4-亚乙基二氧噻吩) (PEDOT)市场深度研究分析报告
  10. 面向服务与微服务架构