NEON是指适用于Arm Cortex-A系列处理器的一种高级SIMD(单指令多数据)扩展指令集,可执行并行数据处理。
| arm v6 SIMD扩展 | arm v7-a NEON |
|---|---|
| (1)利用arm通用寄存器 (2)支持8/16bit整数 (3)同时计算2x16/4x8操作数 | (1)32个64bit NEON寄存器 (2)支持8/16/32/64bit整数 (3)支持单精度浮点 (4)最多同时支持16个8bit操 作数 |
处理大型数据集时,一个主要的性能限制因素是执行数据处理指令所花费的 CPU 时间量。此 CPU 时间取决于处理整个数据集所需的指令数。指令的数量取决于每条指令可以处理的数据项数。
大多数 Arm 指令是单指令单数据 (SISD)。每条指令对单个数据源执行其指定的操作。因此,处理多个数据项需要多个指令。例如,要执行四个加法运算,需要四条指令来添加四对寄存器中的值:
ADD w0, w0, w5
ADD w1, w1, w6
ADD w2, w2, w7
ADD w3, w3, w8
这种方法相对较慢,并且很难看出不同寄存器之间的关系。为了提高性能和效率,媒体处理通常被卸载到专用处理器,如图形处理单元(GPU)或媒体处理单元,它们可以通过单个指令处理多个数据值。
如果要处理的值小于最大位大小,则 SISD 指令会浪费额外的潜在带宽。例如,将 8 位值相加时,需要将每个 8 位值加载到单独的 64 位寄存器中。对较小的数据大小执行大量单独的操作并不能有效地使用计算机资源,因为处理器、寄存器和数据路径都是为 64 位计算而设计的。
单指令多数据 (SIMD) 指令同时对多个数据项执行相同的操作。这些数据项作为单独的通道打包在更大的寄存器中。
例如,以下指令将四对单精度(32 位)值相加。但是,在这种情况下,这些值被打包到两对128位寄存器中的单独通道中。然后,第一个源寄存器中的每个通道被添加到第二个源寄存器中的相应通道中,然后存储在目标寄存器中的同一通道中:
ADD V10.4S, V8.4S, V9.4S
// This operation adds two 128-bit (quadword) registers, V8 and V9,
// and stores the result in V10.
// Each of the four 32-bit lanes in each register is added separately.
// There are no carries between the lanes.
该单条指令同时对大型寄存器中的所有数据值进行操作:

使用单个 SIMD 指令执行这四个操作比使用四个单独的 SISD 指令更快。
该图显示了 128 位寄存器,每个寄存器包含四个 32 位值,但对于 Neon 寄存器,也可以采用其他组合:
可以使用 Neon 寄存器的所有 128 位同时操作两个 64 位、四个 32 位、8 个 16 位或 16 个 8 位整数数据元素。
可以使用 Neon 寄存器的下部 64 位同时操作两个 32 位、4 个 16 位或 8 个 8 位整数数据元素(在这种情况下,Neon 寄存器的上部 64 位未使用)。
请注意,图中所示的加法操作对于每个车道都是真正独立的。车道 0 的任何溢出或携带都不会影响车道 1,这是一个完全独立的计算。
Arm v8-A是一个非常重要的架构变化,它支持64位执行模式 “AArch64” ,并且带来了全新的64位指令集 “A64” 。同时,为了兼容Arm v7-A (32位架构)指令集,也引入了 “AArch32” 的概念。大部分Arm v7-A代码可以运行在Arm v8-A AArch32执行模式下。
Arm v8-A AArch64有31个64位通用寄存器,每一个通用寄存器具有64位(X0-X30)或是32位模式(W0-W30)。其寄存器视图如下:

Arm v8-A AArch64有32个128位寄存器,也能当作32位Sn寄存器或是64位Dn寄存器使用。其寄存器视图如下:

Arm v8-A AArch32指令集是由A32(Arm指令,32 位固定长度指令集)和T32(Thumb指令集,16 位固定长度指令集;Thumb2指令集, 16/32位长度指令集)指令集组成。它是Arm v7 Cortex-A指令集的超集,因此Arm v8-A AArch32能后向兼容Arm v7-A以便运行早期软件。同时,为了维持与A64指令集的一致性,AArch32指令集又新增了NEON除法,加密指令扩展。
与AArch32指令集相比,AArch64指令集A64(32位固定长度)发生了很大变化,比如,它们具有完全不同的指令格式。但是在功能上来说,AArch64指令集基本上实现了AArch32指令集的全部功能,另外添加了NEON双精度浮点的支持。
AArch64 NEON指令格式通用描述如下:
{ < p r e f i x > } < o p > { < s u f f i x > } V d . < T > , V n . < T > , V m . < T > \{<prefix>\}<op>\{<suffix>\} Vd.<T>, Vn.<T>, Vm.<T> {<prefix>}<op>{<suffix>}Vd.<T>,Vn.<T>,Vm.<T>
<prefix>——前缀,如S/U/F/P 分别表示有符号整数/无符号整数/浮点数/布尔数据类型
<op>——操作符。例如ADD,AND等。
<suffix>——后缀,通常是有以下几种:
P:将向量按对操作,例如ADDP
V:跨所有的数据通道操作,例如FMAXV
2:在宽指令/窄指令中操作数据的高位部分。例如ADDHN2,SADDL2。
ADDHN2:两个128位矢量相加,得到64位矢量结果,并将结果存到NEON寄存器的高64位部分。
SADDL2: 两个NEON寄存器的高64位部分相加,得到128-位结果。
<T> ——数据类型,通常是8B/16B/4H/8H/2S/4S/2D等。B代表8bit数据类型;H代表16位数据宽度;S代表32位数据宽度,可以是32位整数或单精度浮点;D代表64位数据宽度,可以是64位整数或双精度浮点。
下面列出具体的NEON指令例子:
UADDLP V0.8H, V0.16B
FADD V0.4S, V0.4S, V0.4S
NEON 技术通常有下列四种方式:
调用NEON优化过的库函数
使用编译器自动矢量化选项
使用NEON intrinsics指令
手写NEON汇编
用户只需要在程序中直接调用NEON优化过的库函数就可以了,简单易用。目前你有下列库可以选择:
Arm Compute library —— 一系列经过Arm CPU和GPU优化过的底层函数库。用于图像处理、机器学习和计算机视觉。可以参考如下链接:https://developer.Arm.com/technologies/compute-library
Ne10开源库——由Arm主导开发的,目前提供了比较通用的数学函数,部分图像处理函数,以及FFT函数。Project Ne10:Arm Architecture @ GitHub 的开放式优化软件库项目
在GCC编译器选项中有自动矢量化编译选项可以帮助现有的代码编译生成NEON代码。GNU GCC提供一系列的选项,有的能提升性能,有的能降低生成可执行文件的代码大小。对于每一行代码,有很多种汇编指令可以选择。编译器在寄存器、堆栈空间、代码大小、编译时间、便于调试、指令执行时间等许多选项中必须有所取舍,这样才能生成最优的映像文件。
NEON intrinsics可以视作在NEON指令上面封装了一层接口。当用户在C程序中调用NEON intrinsics接口时,编译器会自动生成相关的NEON指令。NEON intrinsics可以跨Arm v7-A/v8-A运行。只要编程一次,就可以借助编译器生成相应的NEON代码。如果用户在代码中使用了Arm v8-A AArch64特有的NEON指令,只要如下例所示,用宏定义(__aarch64__)将这部分代码分隔即可。
下面是NEON intrinsics的两个示例。
考虑一个 24 位 RGB 图像,其中图像是一个像素数组,每个像素都有一个红色、蓝色和绿色元素。在内存中,这可能显示为:

RGB数据是交错的,访问和操作三个单独的颜色通道会给程序员带来问题。在简单的情况下,可以通过将模数3来操作交错的RGB值单色通道数值。但是,对于更复杂的操作(如傅里叶变换),提取和拆分通道更有意义。
我们在内存中有一个 RGB 值数组,我们希望对它们进行去插错,并将这些值放在单独的颜色数组中。执行此操作的 C 过程可能如下所示:
void rgb_deinterleave_c(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {
/*
* Take the elements of "rgb" and store the individual colors "r", "g", and "b".
*/
for (int i=0; i < len_color; i++) {
r[i] = rgb[3*i];
g[i] = rgb[3*i+1];
b[i] = rgb[3*i+2];
}
}
但有一个问题。使用优化级别 -O3(非常高的优化)使用 Arm Compiler 6 进行编译并检查反汇编,发现没有使用 Neon 指令或寄存器。每个单独的 8 位值都存储在单独的 64 位通用寄存器中。考虑到全宽 Neon 寄存器的宽度为 128 位,在示例中,每个寄存器可以容纳 16 个 8 位值,重写解决方案以使用 Neon 内部函数应该会给我们带来良好的结果。
void rgb_deinterleave_neon(uint8_t *r, uint8_t *g,
uint8_t *b, uint8_t *rgb, int len_color)
{
/*
* Take the elements of "rgb" and store the individual colors "r", "g", and "b"
*/
int num8x16 = len_color / 16;
uint8x16x3_t intlv_rgb;
for (int i=0; i < num8x16; i++) {
intlv_rgb = vld3q_u8(rgb+3*16*i);
vst1q_u8(r+16*i, intlv_rgb.val[0]);
vst1q_u8(g+16*i, intlv_rgb.val[1]);
vst1q_u8(b+16*i, intlv_rgb.val[2]);
}
}
在此示例中,我们使用了以下类型和内部函数:
| 代码元素 | 这是什么? | 我们为什么要使用它? |
|---|---|---|
uint8x16_t | 包含16 个8位无符号整数的数组。 | 一个uint8x16_t适合128位寄存器。我们可以确保即使在C代码中也没有浪费的寄存器位。 |
uint8x16x3_t | 具有三个uint8x16_t元素的结构。 | 循环中当前颜色值的临时保留区域。 |
vld3q_u8(…) | 通过加载 3*16 字节内存的连续区域来返回uint8x16x3_t的函数。加载的每个字节都以交替模式放置在三个uint8x16_t数组之一。 | 在最低级别,这种内在保证了LD3指令的生成,LD3指令以交替模式将给定地址的值加载到三个Neon寄存器中。 |
vst1q_u8(…) | 将uint8x16_t存储在给定地址的函数。 | 它存储一个完整的128位寄存器,充满字节值。 |
可以使用以下命令在 Arm 计算机上编译和反汇编上述完整源代码:
gcc -g -o3 rgb.c -o exe_rgb_o3
objdump -d exe_rgb_o3 > disasm_rgb_o3
矩阵乘法是在许多数据密集型应用程序中执行的操作。它由一组以简单方式重复的算术运算组成:

矩阵乘法过程如下:
A- 在第一个矩阵中取一行
B- 使用第二个矩阵中的列执行此行的点积
C- 将结果存储在新矩阵的相应行和列中
对于 32 位浮点数的矩阵,乘法可以写为:
void matrix_multiply_c(float32_t *A, float32_t *B, float32_t *C,
uint32_t n, uint32_t m, uint32_t k)
{
for (int i_idx=0; i_idx < n; i_idx++) {
for (int j_idx=0; j_idx < m; j_idx++) {
C[n*j_idx + i_idx] = 0;
for (int k_idx=0; k_idx < k; k_idx++) {
C[n*j_idx + i_idx] += A[n*k_idx + i_idx]*B[k*j_idx + k_idx];
}
}
}
}
我们假设了内存中矩阵的列主布局。也就是说,n x m 矩阵 M 表示为数组M_array其中 Mij = M_array[n*j + i]。
此代码不是最佳的,因为它没有充分利用 Neon。我们可以开始通过使用内部函数来改进它,但是让我们先解决一个更简单的问题,先看看小的、固定大小的矩阵,然后再转到更大的矩阵。
下面的代码使用内部函数将两个 4x4 矩阵相乘。由于我们要处理的数值数量很少且固定,所有这些值都可以同时放入处理器的 Neon 寄存器中,因此我们可以完全展开循环。
void matrix_multiply_4x4_neon(float32_t *A, float32_t *B, float32_t *C)
{
// these are the columns A
float32x4_t A0;
float32x4_t A1;
float32x4_t A2;
float32x4_t A3;
// these are the columns B
float32x4_t B0;
float32x4_t B1;
float32x4_t B2;
float32x4_t B3;
// these are the columns C
float32x4_t C0;
float32x4_t C1;
float32x4_t C2;
float32x4_t C3;
A0 = vld1q_f32(A);
A1 = vld1q_f32(A+4);
A2 = vld1q_f32(A+8);
A3 = vld1q_f32(A+12);
// Zero accumulators for C values
C0 = vmovq_n_f32(0);
C1 = vmovq_n_f32(0);
C2 = vmovq_n_f32(0);
C3 = vmovq_n_f32(0);
// Multiply accumulate in 4x1 blocks, i.e. each column in C
B0 = vld1q_f32(B);
C0 = vfmaq_laneq_f32(C0, A0, B0, 0);
C0 = vfmaq_laneq_f32(C0, A1, B0, 1);
C0 = vfmaq_laneq_f32(C0, A2, B0, 2);
C0 = vfmaq_laneq_f32(C0, A3, B0, 3);
vst1q_f32(C, C0);
B1 = vld1q_f32(B+4);
C1 = vfmaq_laneq_f32(C1, A0, B1, 0);
C1 = vfmaq_laneq_f32(C1, A1, B1, 1);
C1 = vfmaq_laneq_f32(C1, A2, B1, 2);
C1 = vfmaq_laneq_f32(C1, A3, B1, 3);
vst1q_f32(C+4, C1);
B2 = vld1q_f32(B+8);
C2 = vfmaq_laneq_f32(C2, A0, B2, 0);
C2 = vfmaq_laneq_f32(C2, A1, B2, 1);
C2 = vfmaq_laneq_f32(C2, A2, B2, 2);
C2 = vfmaq_laneq_f32(C2, A3, B2, 3);
vst1q_f32(C+8, C2);
B3 = vld1q_f32(B+12);
C3 = vfmaq_laneq_f32(C3, A0, B3, 0);
C3 = vfmaq_laneq_f32(C3, A1, B3, 1);
C3 = vfmaq_laneq_f32(C3, A2, B3, 2);
C3 = vfmaq_laneq_f32(C3, A3, B3, 3);
vst1q_f32(C+12, C3);
}
我们选择将固定大小的 4x4 矩阵相乘,原因如下:
一些应用特别需要4x4矩阵,例如图形或相对论物理学。
Neon 矢量寄存器保存四个 32 位值,因此将程序与架构相匹配将使其更容易进行优化。
我们可以采用这个4x4内核,并将其用于更通用的内核。
让我们总结一下这里使用的内部函数:
| 代码元素 | 这是什么? | 我们为什么要使用它? |
|---|---|---|
float32x4_t | 由4个 32 位浮点数组成的数组。 | 一个uint32x4_t适合128位寄存器。我们可以确保即使在C代码中也没有浪费的寄存器位。 |
vld1q_f32(…) | 将4个 32 位浮点数加载到float32x4_t中的函数。 | 要获得矩阵值,我们需要从A和B获得矩阵值。 |
vfmaq_lane_f32(…) | 使用融合乘法累加指令的函数。将float32x4_t值乘以另一个float32x4_t元素,然后将结果加第三个float32x4_t,然后再返回结果。 | 由于矩阵行对列点积是一组乘法和加法,因此此操作非常自然地适合。 |
vst1q_f32(…) | 将float32x4_t存储在给定地址的函数。 | 在计算结果后存储结果。 |
现在我们可以乘以4x4矩阵,我们可以通过将较大的矩阵视为4x4矩阵块来乘以它们。这种方法的一个缺陷是,它仅适用于在两个维度中都是四倍的矩阵大小,但是通过用零填充任何矩阵,您可以使用此方法而无需更改它。
下面列出了更一般的矩阵乘法的代码。内核的结构变化很小,添加循环和地址计算是主要变化。与在 4x4 内核中一样,我们对 B 列使用了唯一的变量名称,即使我们可以使用一个变量并重新加载。这充当编译器的提示,为这些变量分配不同的寄存器,这将使处理器能够在等待加载另一列的同时完成一列的算术指令。
void matrix_multiply_neon(float32_t *A, float32_t *B, float32_t *C,
uint32_t n, uint32_t m, uint32_t k)
{
/*
* Multiply matrices A and B, store the result in C.
* It is the user's responsibility to make sure the matrices are compatible.
*/
int A_idx;
int B_idx;
int C_idx;
// these are the columns of a 4x4 sub matrix of A
float32x4_t A0;
float32x4_t A1;
float32x4_t A2;
float32x4_t A3;
// these are the columns of a 4x4 sub matrix of B
float32x4_t B0;
float32x4_t B1;
float32x4_t B2;
float32x4_t B3;
// these are the columns of a 4x4 sub matrix of C
float32x4_t C0;
float32x4_t C1;
float32x4_t C2;
float32x4_t C3;
for (int i_idx=0; i_idx<n; i_idx+=4 {
for (int j_idx=0; j_idx<m; j_idx+=4){
// zero accumulators before matrix op
c0=vmovq_n_f32(0);
c1=vmovq_n_f32(0);
c2=vmovq_n_f32(0);
c3=vmovq_n_f32(0);
for (int k_idx=0; k_idx<k; k_idx+=4){
// compute base index to 4x4 block
a_idx = i_idx + n*k_idx;
b_idx = k*j_idx k_idx;
// load most current a values in row
A0=vld1q_f32(A+A_idx);
A1=vld1q_f32(A+A_idx+n);
A2=vld1q_f32(A+A_idx+2*n);
A3=vld1q_f32(A+A_idx+3*n);
// multiply accumulate 4x1 blocks, i.e. each column C
B0=vld1q_f32(B+B_idx);
C0=vfmaq_laneq_f32(C0,A0,B0,0);
C0=vfmaq_laneq_f32(C0,A1,B0,1);
C0=vfmaq_laneq_f32(C0,A2,B0,2);
C0=vfmaq_laneq_f32(C0,A3,B0,3);
B1=v1d1q_f32(B+B_idx+k);
C1=vfmaq_laneq_f32(C1,A0,B1,0);
C1=vfmaq_laneq_f32(C1,A1,B1,1);
C1=vfmaq_laneq_f32(C1,A2,B1,2);
C1=vfmaq_laneq_f32(C1,A3,B1,3);
B2=vld1q_f32(B+B_idx+2*k);
C2=vfmaq_laneq_f32(C2,A0,B2,0);
C2=vfmaq_laneq_f32(C2,A1,B2,1);
C2=vfmaq_laneq_f32(C2,A2,B2,2);
C2=vfmaq_laneq_f32(C2,A3,B3,3);
B3=vld1q_f32(B+B_idx+3*k);
C3=vfmaq_laneq_f32(C3,A0,B3,0);
C3=vfmaq_laneq_f32(C3,A1,B3,1);
C3=vfmaq_laneq_f32(C3,A2,B3,2);
C3=vfmaq_laneq_f32(C3,A3,B3,3);
}
//Compute base index for stores
C_idx = n*j_idx + i_idx;
vstlq_f32(C+C_idx, C0);
vstlq_f32(C+C_idx+n,Cl);
vstlq_f32(C+C_idx+2*n,C2);
vstlq_f32(C+C_idx+3*n,C3);
}
}
}
编译和反汇编此函数,并将其与我们的 C 函数进行比较,如下所示:
给定矩阵乘法的算术指令更少,因为我们利用的是具有完整寄存器打包的高级 SIMD 技术。纯 C 代码通常不会这样做。
FMLA 而不是 FMUL 指令。由内部函数指定。
减少循环迭代。如果使用得当,内部函数允许循环轻松展开。
但是,由于内存分配和数据类型(例如,float32x4_t)的初始化,存在不必要的加载和存储,这些类型在纯 C 代码中未使用。
可以使用以下命令在 Arm 计算机上编译和反汇编上述完整源代码:
gcc -g -o3 matrix.c -o exe_matrix_o3
objdump -d exe_ matrix _o3 > disasm_matrix_o3
程序约定是针对特定编程语言的一组准则。
为了使用内部函数,必须支持高级 SIMD 体系结构,并且在任何情况下都可能启用某些特定指令,也可能不启用这些指令。当定义了以下宏并等于 1 时,相应的功能可用:
__ARM_NEON:编译器支持高级 SIMD,始终为 1 表示 AArch64
__ARM_NEON_FP:支持NEON浮点运算,始终为 1 表示 AArch64
__ARM_FEATURE_CRYPTO:提供加密说明。因此,可以使用加密 Neon 内部函数。
__ARM_FEATURE_FMA:融合的乘法累加指令可用。因此,可以使用使用这些的NEON内部函数。
此列表并非详尽无遗,有关更多宏的详细信息,请参阅Arm C 语言扩展公文。
arm_neon.h 中有三大类数据类型,它们遵循以下模式:_base_ 是指基本数据类型,_W_ 是基本类型的宽度。_L_ 是向量数据类型(例如标量数组)中标量数据类型实例的数量,_N_ 是向量数组类型(例如标量数组的结构)中矢量数据类型实例的数量。
baseW_t:标量数据类型
baseWxL_t:矢量数据类型
baseWxLxN_t:向量数组数据类型
通常,_W_ 和 _L_ 是这样的,矢量数据类型的长度为 64 位或 128 位,因此完全适合 Neon 寄存器。_N_对应于那些同时在多个寄存器上运行的指令。
根据 Arm C 语言扩展,arm_neon.h 中的函数原型遵循通用模式。在最一般的层面上,这是:
ret v[p][q][r]name[u][n][q][x][_high][_lane | laneq][_n][_result]_type(args)
请注意,某些字母和名称会过载,但按上述顺序排列:
ret:函数的返回类型;
v:vector 的缩写,存在于所有内部函数上;
* p*:指示成对操作([值] 表示可能存在值);
* q*:表示饱和操作(AArch64 操作中的 vqtb[l][x] 除外,其中 q 表示 128 位索引和结果操作数);
r:表示舍入操作;
name:基本操作的描述性名称,通常,这是高级 SIMD 指令,但不一定是;
* u*:指示有符号到无符号的饱和度;
n:表示窄幅操作;
q:后缀名称表示对 128 位向量的操作。
x:指示 AArch64 中的高级 SIMD 标量操作,它可以是 b、h、s 或 d 之一(即 8、16、32 或 64 位);
_high:在 AArch64 中,用于涉及 128 位操作数的加宽和缩小操作。对于加宽 128 位操作数,“高”是指源操作数的前 64 位。对于缩小范围,它是指目标操作数的前 64 位;
_n:指示作为参数提供的标量操作数;
_lane:指示从矢量的通道获取的标量操作数。_laneq表示从 128 位宽度的输入向量的通道中获取的标量操作数。(左|右表示只出现左或右)。
type:缩写形式的主操作数类型;
args:函数的参数。
NEON手写汇编主要有两种方式:独立汇编文件和内嵌汇编。
独立汇编文件可以用“.S”作为文件后缀,也可以用“.s”作为文件后缀。区别在于.S文件会经过C/C++预处理器处理,这样我们可以利用宏定义等C语言特性。
手写NEON汇编文件时,我们需要注意寄存器的保存。对于Arm v7/v8我们需要保存下列寄存器:

下面是Arm v7-A/v8-A NEON 汇编的一个例程:
//在头文件中定义
void add_float_neon2(float* dst, float* src1, float* src2, int count);
下面是手写汇编代码,保存到.S文件中
// Arm v7-A/Arm v8-A AArch32版本
.text
.syntax unified
.align 4
.global add_float_neon2
.type add_float_neon2, %function
.thumb
.thumb_func
add_float_neon2:
.L_loop:
vld1.32 {q0}, [r1]!
vld1.32 {q1}, [r2]!
vadd.f32 q0, q0, q1
subs r3, r3, #4
vst1.32 {q0}, [r0]!
bgt .L_loop
bx lr
// Arm v8-A AArch64版本
.text
.align 4
.global add_float_neon2
.type add_float_neon2, %function
add_float_neon2:
.L_loop:
ld1 {v0.4s}, [x1], #16
ld1 {v1.4s}, [x2], #16
fadd v0.4s, v0.4s, v1.4s
subs x3, x3, #4
st1 {v0.4s}, [x0], #16
bgt .L_loop
ret
顾名思义,内嵌汇编是和C代码紧密结合在一起的一种方式。我们可以直接把汇编代码内嵌在C/C++代码中,我们可以在需要NEON的地方即时添加。
优点:
过程调用规则简单,不需要自己手动保存寄存器。
可以使用 C/C++ 变量和函数,因此它能非常容易地整合到 C/C++ 代码
缺点:
内嵌汇编有一套复杂的语法规则
NEON代码内嵌在C/C++代码中,不易于移植到其他平台
//Arm v7-A/Arm v8-A AArch32
void add_float_neon3(float* dst, float* src1, float* src2, int count)
{
asm volatile (
"1: \n"
"vld1.32 {q0}, [%[src1]]! \n"
"vld1.32 {q1}, [%[src2]]! \n"
"vadd.f32 q0, q0, q1 \n"
"subs %[count], %[count], #4 \n"
"vst1.32 {q0}, [%[dst]]! \n"
"bgt 1b \n"
: [dst] "+r" (dst)
: [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
: "memory", "q0", "q1"
);
}
//Arm v8-A AArch64
void add_float_neon3(float* dst, float* src1, float* src2, int count)
{
asm volatile (
"1: \n"
"ld1 {v0.4s}, [%[src1]], #16 \n"
"ld1 {v1.4s}, [%[src2]], #16 \n"
"fadd v0.4s, v0.4s, v1.4s \n"
"subs %[count], %[count], #4 \n"
"st1 {v0.4s}, [%[dst]], #16 \n"
"bgt 1b \n"
: [dst] "+r" (dst)
: [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
: "memory", "v0", "v1"
);
}
ArmC语言扩展:Arm C 语言扩展
内部函数查询网站:内在 – 手臂开发人员 (arm.com)
内部函数文档: 适用于 Armv7 和 Armv8 架构的高级 SIMD 架构扩展 (Neon) 内部函数
Introducing Neon for Armv8-A:
https://developer.arm.com/documentation/102474/0100/
Ne10项目开源库: https://github.com/projectNe10/
Arm NEON编程快速上手指南 - 知乎 (zhihu.com)
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
如何学习ruby的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/
我使用irb。下面是我写的代码。“斧头”..“bc”我期待"ax""ay""az""ba"bb""bc"但结果只是“斧头”..“bc”我该如何纠正?谢谢。 最佳答案 >puts("ax".."bc").to_aaxayazbabbbc 关于ruby-从结束值创建一系列字符串,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7617092/