欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【NEON 和 VFP 编程】NEON通用算术指令

程序员文章站 2022-04-19 16:12:18
...

本节包括以下小节:

• VABA{L} 和 VABD{L}

向量差值绝对值累加和差值绝对值。

• V{Q}ABS 和 V{Q}NEG

向量绝对值和求反。

• V{Q}ADD、VADDL、VADDW、V{Q}SUB、VSUBL 和 VSUBW

向量加法和减法。

• V{R}ADDHN 和 V{R}SUBHN

选择高半部分的向量加法和选择高半部分的向量减法。

• V{R}HADD 和 VHSUB

向量半加和半减。

• VPADD{L}、VPADAL

向量按对加,向量按对加并累加。

• VMAX、VMIN、VPMAX 和 VPMIN

向量最大值,向量最小值,向量按对最大值和向量按对最小值。

• VCLS、VCLZ 和 VCNT

向量前导符号位计数,前导零计数和设置位计数。

• VRECPE 和 VRSQRTE

向量近似倒数和近似平方根倒数。

• VRECPS 和 VRSQRTS

向量倒数步进和平方根倒数步进。

一、VABA{L} 和 VABD{L}

VABA(向量差值绝对值累加)用一个向量的元素减去另一个向量的相应元素,并将结果的绝对值累加到目标向量的元素中。

VABD(向量差值绝对值)用一个向量的元素减去另一个向量的相应元素,并将结果的绝对值存放到目标向量的元素中。

这两个指令的长型格式都可用。

语法

Vop{cond}.datatype {Qd}, Qn, Qm

Vop{cond}.datatype {Dd}, Dn, Dm

VopL{cond}.datatype Qd, Dn, Dm

其中:

op 必须为 ABA 或 ABD。

cond 是一个可选的条件代码。

datatype 必须是下列值之一:

• 对于 VABA、VABAL 或 VABDL,为 S8、S16、S32、U8、U16 或 U32

• 对于 VABD,为 S8、S16、S32、U8、U16、U32 或 F32。

Qd、Qn、Qm 是四字运算的目标向量、第一个操作数向量和第二个操作数向量。

Dd、Dn、Dm 是双字运算的目标向量、第一个操作数向量和第二个操作数向量。

Qd、Dn、Dm 是长型运算的目标向量、第一个操作数向量和第二个操作数向量。

下面是使用VABA指令的示例代码。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x5;
        data1[i] = 0x7;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VLDM %0,{q1}\t\n"
            "VBIC q2,q2\t\n"
            "VABA.S32 q2,q0,q1\t\n"
            "VABA.S32 q2,q1,q0\t\n"
            "VSTM %1,{q2}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1", "q2"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

11-06 08:01:40.370 3232-3232/ndk.example.com.ndkexample I/Native: 1 data[0]=0X5 data1[0]=0X7
    1 data[1]=0X5 data1[1]=0X7
    1 data[2]=0X5 data1[2]=0X7
    1 data[3]=0X5 data1[3]=0X7
    2 data[0]=0X5 data1[0]=0X4
    2 data[1]=0X5 data1[1]=0X4
    2 data[2]=0X5 data1[2]=0X4
    2 data[3]=0X5 data1[3]=0X4

二、V{Q}ABS 和 V{Q}NEG

VABS(向量绝对值)获取一个向量中每个元素的绝对值,并将结果存放到另一个向量中。 (对于浮点格式,仅清除符号位。)

VNEG(向量求反)对一个向量中的每个元素执行求反运算,并将结果存放到另一个向量中。 (对于浮点格式,仅反转符号位。)

这两个指令的饱和格式都可用。 如果进行饱和,则会设置粘性 QC 标记(FPSCR位 [27])。

语法

V{Q}op{cond}.datatype Qd, Qm

V{Q}op{cond}.datatype Dd, Dm

其中:

Q 如果存在,则指示在任何结果溢出时对其进行饱和。

op 必须为 ABS 或 NEG。

cond 是一个可选的条件代码。

datatype 必须是下列值之一:

S8、S16、S32 对于 VABS、VNEG、VQABS 或 VQNEG

F32 仅限 VABS 和 VNEG。

Qd、Qm 是四字运算的目标向量和操作数向量。

Dd、Dm 是双字运算的目标向量和操作数向量。

下面用VABS和VNEG指令演示它们的用法。

    int *data = new int[4];
    int *data1 = new int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = i - 2;
        data1[i] = i - 2;
        LOGI("1 data[%d]=%d data1[%d]=%d", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VABS.S32 q0,q0\t\n"
            "VNEG.S32 q1,q1\t\n"
            "VSTM %0,{q0}\t\n"
            "VSTM %1,{q1}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%d data1[%d]=%d", i, data[i], i, data1[i]);
    }

运行结果:

11-06 08:22:40.710 3367-3367/ndk.example.com.ndkexample I/Native: 1 data[0]=-2 data1[0]=-2
    1 data[1]=-1 data1[1]=-1
    1 data[2]=0 data1[2]=0
    1 data[3]=1 data1[3]=1
    2 data[0]=2 data1[0]=2
    2 data[1]=1 data1[1]=1
    2 data[2]=0 data1[2]=0
    2 data[3]=1 data1[3]=-1

三、V{Q}ADD、VADDL、VADDW、V{Q}SUB、VSUBL 和 VSUBW

VADD(向量加法)将两个向量中的相应元素相加,并将结果存放到目标向量中。

VSUB(向量减法)用一个向量的元素减去另一个向量的相应元素,并将结果存放到目标向量的结果中。

饱和、长型和宽型格式都可用。 如果进行饱和,则会设置粘性 QC 标记(FPSCR位 [27])。

语法

V{Q}op{cond}.datatype {Qd}, Qn, Qm

V{Q}op{cond}.datatype {Dd}, Dn, Dm

VopL{cond}.datatype Qd, Dn, Dm

VopW{cond}.datatype {Qd}, Qn, Dm

其中:

Q 如果存在,则指示在任何结果溢出时对其进行饱和。

op 必须为 ADD 或 SUB。

cond 是一个可选的条件代码。

datatype 必须是下列值之一:

I8、I16、I32、I64、F32 对于 VADD 或 VSUB

S8、S16、S32 对于 VQADD、VQSUB、VADDL、VADDW、VSUBL 或 VSUBW

U8、U16、U32 对于 VQADD、VQSUB、VADDL、VADDW、VSUBL 或 VSUBW

S64、U64 对于 VQADD 或 VQSUB。

Qd、Qn、Qm 是四字运算的目标向量、第一个操作数向量和第二个操作数向量。

Dd、Dn、Dm 是双字运算的目标向量、第一个操作数向量和第二个操作数向量。

Qd、Dn、Dm 是长型运算的目标向量、第一个操作数向量和第二个操作数向量。

Qd、Qn、Dm 是宽型运算的目标向量、第一个操作数向量和第二个操作数向量。

我们来看看向量加减法指令具体如何使用?

    int *data = new int[4];
    int *data1 = new int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = i - 2;
        data1[i] = i - 2;
        LOGI("1 data[%d]=%d data1[%d]=%d", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VADD.I32 q0,q0,q1\t\n"
            "VSUB.I32 q1,q0,q1\t\n"
            "VSTM %0,{q0}\t\n"
            "VSTM %1,{q1}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%d data1[%d]=%d", i, data[i], i, data1[i]);
    }

运行结果:

11-06 08:29:08.130 3424-3424/ndk.example.com.ndkexample I/Native: 1 data[0]=-2 data1[0]=-2
    1 data[1]=-1 data1[1]=-1
    1 data[2]=0 data1[2]=0
    1 data[3]=1 data1[3]=1
    2 data[0]=-4 data1[0]=-2
    2 data[1]=-2 data1[1]=-1
    2 data[2]=0 data1[2]=0
    2 data[3]=2 data1[3]=1

至于带有L和W标记的ADD和SUB指令无非是长型和宽型指令,注意看上面语法中带有这两个标记指令的操作数的区别。

四、V{R}ADDHN 和 V{R}SUBHN

V{R}ADDH(向量窄型加法,选择高半部分)将两个向量中的相应元素相加,选择相加结果的最高有效半部,并将最终结果存放到目标向量中。 可将结果舍入或截断。

V{R}SUBH(向量窄型减法,选择高半部分)用一个向量的元素减去另一个向量的相应元素,选择相减结果的最高有效半部,并将最终结果存放到目标向量中。 可将结果舍入或截断。

语法

V{R}opHN{cond}.datatype Dd, Qn, Qm

其中:

R 如果存在,则指示对每个结果进行舍入。 否则将每个结果截断。op 必须为 ADD 或 SUB。

cond 是一个可选的条件代码。

datatype 必须为 I16、I32 或 I64 之一。

Dd、Qn、Qm 是目标向量、第二个操作数向量和第二个操作数向量。

下面是使用这两个指令的示例片段。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x12345678;
        data1[i] = 0x1234567;
        LOGI("1 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VADDHN.I32 d4,q0,q1\t\n"
            "VSUBHN.I32 d5,q0,q1\t\n"
            "VSTM %0,{q2}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1", "q2"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

运行结果:

11-06 18:50:01.750 3688-3688/ndk.example.com.ndkexample I/Native: 1 data[0]=0X12345678 data1[0]=0X1234567
    1 data[1]=0X12345678 data1[1]=0X1234567
    1 data[2]=0X12345678 data1[2]=0X1234567
    1 data[3]=0X12345678 data1[3]=0X1234567
    2 data[0]=0X13571357 data1[0]=0X1234567
    2 data[1]=0X13571357 data1[1]=0X1234567
    2 data[2]=0X11111111 data1[2]=0X1234567
    2 data[3]=0X11111111 data1[3]=0X1234567

VADDHN.I32指令把q0和q1中的向量相加,然后截取向量中的高半部分存入d4。也就是0x12345678 + 0x1234567 = 0x13579BDF。VSUBHN.I32则是q0减去q1中的向量值,然后取高半部分,即0x1111。

五、V{R}HADD 和 VHSUB

VHADD(向量半加)将两个向量中的相应元素相加,将每个结果右移一位,并将这些结果存放到目标向量中。 可将结果舍入或截断。

VHSUB(向量半减)用一个向量的元素减去另一个向量的相应元素,将每个结果右移一位,并将这些结果存放到目标向量中。 结果将始终被截断。

语法

V{R}HADD{cond}.datatype {Qd}, Qn, Qm

V{R}HADD{cond}.datatype {Dd}, Dn, Dm

VHSUB{cond}.datatype {Qd}, Qn, Qm

VHSUB{cond}.datatype {Dd}, Dn, Dm

其中:

R 如果存在,则指示对每个结果进行舍入。 否则将每个结果截断。

cond 是一个可选的条件代码。

datatype 必须为 S8、S16、S32、U8、U16 或 U32 之一。

Qd、Qn、Qm 是四字运算的目标向量、第一个操作数向量和第二个操作数向量。

Dd、Dn、Dm 是双字运算的目标向量、第一个操作数向量和第二个操作数向量。

向量半加和向量半减,看了上边的解释就清楚了,因为把结果右移了一位,就相当于结果减半。下面是使用这两条指令的示例。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x12345678;
        data1[i] = 0x1234567;
        LOGI("1 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VHADD.U32 q2,q0,q1\t\n"
            "VHSUB.U32 q3,q0,q1\t\n"
            "VSTM %0,{q2}\t\n"
            "VSTM %1,{q3}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1", "q2", "q3"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

运行结果:

11-06 19:01:44.330 3841-3841/ndk.example.com.ndkexample I/Native: 1 data[0]=0X12345678 data1[0]=0X1234567
    1 data[1]=0X12345678 data1[1]=0X1234567
    1 data[2]=0X12345678 data1[2]=0X1234567
    1 data[3]=0X12345678 data1[3]=0X1234567
    2 data[0]=0X9ABCDEF data1[0]=0X8888888
    2 data[1]=0X9ABCDEF data1[1]=0X8888888
    2 data[2]=0X9ABCDEF data1[2]=0X8888888
    2 data[3]=0X9ABCDEF data1[3]=0X8888888

0x12345678 + 0x1234567 = 0x13579BDF,它的一半即0X9ABCDEF。

0x12345678 - 0x1234567 = 0x11111111,它的一半即0X8888888。

六、VPADD{L}、VPADAL

VPADD(向量按对加)将两个向量的相邻元素对相加,并将结果存放到目标向量中。

【NEON 和 VFP 编程】NEON通用算术指令

VPADDL(向量长型按对加)将向量中相邻的元素对相加,用符号或零将结果扩展为原宽度的两倍,并将最终结果存放到目标向量中。

【NEON 和 VFP 编程】NEON通用算术指令

VPADAL(向量长型按对加累加)将向量中相邻的元素对相加,并将结果的绝对值累加到目标向量的元素中。

【NEON 和 VFP 编程】NEON通用算术指令

语法

VPADD{cond}.datatype {Dd}, Dn, Dm

VPopL{cond}.datatype Qd, Qm

VPopL{cond}.datatype Dd, Dm

其中:

op 必须为 ADD 或 ADA。

cond 是一个可选的条件代码。

datatype 必须是下列值之一:

I8、I16、I32和F32 对于 VPADD

S8、S16和S32 对于 VPADDL 或 VPADAL

U8、U16和U32 对于 VPADDL 或 VPADAL。

Dd、Dn、Dm 是 VPADD 运算的目标向量、第一个操作数向量和第二个操作数向量。

Qd、Qm 是四字 VPADDL 或 VPADAL 的目标向量和操作数向量。

Dd、Dm 是双字 VPADDL 或 VPADAL 的目标向量和操作数向量。

下面是使用这些指令的示例片段。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x12341234;
        data1[i] = 0x11112222;
        LOGI("1 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VMOV q2,q0\t\n"
            "VPADD.I16 d4,d0,d1\t\n"
            "VPADAL.U16 d5,d2\t\n"
            "VSTM %0,{q2}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1", "q2"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

运行结果:

11-06 19:28:50.410 4016-4016/? I/Native: 1 data[0]=0X12341234 data1[0]=0X11112222
    1 data[1]=0X12341234 data1[1]=0X11112222
    1 data[2]=0X12341234 data1[2]=0X11112222
    1 data[3]=0X12341234 data1[3]=0X11112222
    2 data[0]=0X24682468 data1[0]=0X11112222
    2 data[1]=0X24682468 data1[1]=0X11112222
    2 data[2]=0X12344567 data1[2]=0X11112222
    2 data[3]=0X12344567 data1[3]=0X11112222

0x1234 + 0x1234 = 0x2468;

0x12341234 + (0x1111 + 0x2222) = 0x12344567;

结合配图,相信看了以上两个算式就一目了然了。

七、VMAX、VMIN、VPMAX 和 VPMIN

VMAX(向量最大值)对两个向量中的相应元素进行比较,并将每一对中的较大值复制到目标向量的相应元素中。

VMIN(向量最小值)对两个向量中的相应元素进行比较,并将每一对中的较小值复制到目标向量的相应元素中。

VPMAX(向量按对最大值)对两个向量中的相邻元素对进行比较,并将每一对中的较大值复制到目标向量的相应元素中。 操作数和结果必须为双字向量。

VPMIN(向量按对最小值)对两个向量中的相邻元素对进行比较,并将每一对中的较小值复制到目标向量的相应元素中。 操作数和结果必须为双字向量。

语法

Vop{cond}.datatype Qd, Qn, Qm

Vop{cond}.datatype Dd, Dn, Dm

VPop{cond}.datatype Dd, Dn, Dm

其中:

op 必须为 MAX 或 MIN。

cond 是一个可选的条件代码。

datatype 必须为 S8、S16、S32、U8、U16、U32 或 F32 之一。

Qd、Qn、Qm 是四字运算的目标向量、第一个操作数向量和第二个操作数向量。

Dd、Dn、Dm 是双字运算的目标向量、第一个操作数向量和第二个操作数向量。

浮点最大值和最小值

max(+0.0, -0.0) = +0.0.

min(+0.0, -0.0) = -0.0

如果任意输入为非数字,则对应的结果元素为缺省非数字。

下面示例中使用了VMAX和VPMIN指令,看看运行结果如何。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x12341234;
        data1[i] = 0x11112222;
        LOGI("1 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VMAX.U16 q2,q0,q1\t\n"
            "VBIC q3,q3\t\n"
            "VPMIN.U16 d6,d0,d2\t\n"
            "VSTM %0,{q2}\t\n"
            "VSTM %1,{q3}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1", "q2", "q3"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

运行结果:

11-07 07:50:25.590 3085-3085/ndk.example.com.ndkexample I/Native: 1 data[0]=0X12341234 data1[0]=0X11112222
    1 data[1]=0X12341234 data1[1]=0X11112222
    1 data[2]=0X12341234 data1[2]=0X11112222
    1 data[3]=0X12341234 data1[3]=0X11112222
    2 data[0]=0X12342222 data1[0]=0X12341234
    2 data[1]=0X12342222 data1[1]=0X11111111
    2 data[2]=0X12342222 data1[2]=0
    2 data[3]=0X12342222 data1[3]=0

八、VCLS、VCLZ 和 VCNT

VCLS(向量前导符号位计数)计算一个向量的每个元素中最高位后面与最高位相同的连续位数目,并将结果存放到另一个向量中。

VCLZ(向量前导零计数)计算一个向量的每个元素中从最高位开始算起的连续零数目,并将结果存放到另一个向量中。

VCNT(向量设置位计数)计算一个向量的每个元素中位为 1 的数目,并将结果存放到另一个向量中。

语法

Vop{cond}.datatype Qd, Qm

Vop{cond}.datatype Dd, Dm

其中:

op 必须为 CLS、CLZ 或 CNT 之一。

cond 是一个可选的条件代码。

datatype 必须是下列值之一:

• 对于 CLS,为 S8、S16 或 S32。

• 对于 CLZ,为 I8、I16 或 I32。

• 对于 CNT,为 I8。

Qd、Qm 是四字运算的目标向量和操作数向量。

Dd、Dm 是双字运算的目标向量和操作数向量。

下面来看这些统计向量的的位数指令用法。

   unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x12341234;
        data1[i] = 0x11112222;
        LOGI("1 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VCLS.S32 q2,q0\t\n"
            "VCLZ.I32 q3,q1\t\n"
            "VSTM %0,{q2}\t\n"
            "VSTM %1,{q3}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1", "q2", "q3"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

运行结果:

11-08 07:39:03.970 3133-3133/ndk.example.com.ndkexample I/Native: 1 data[0]=0X12341234 data1[0]=0X11112222
    1 data[1]=0X12341234 data1[1]=0X11112222
    1 data[2]=0X12341234 data1[2]=0X11112222
    1 data[3]=0X12341234 data1[3]=0X11112222
    2 data[0]=0X2 data1[0]=0X3
    2 data[1]=0X2 data1[1]=0X3
    2 data[2]=0X2 data1[2]=0X3
    2 data[3]=0X2 data1[3]=0X3

说实话初看运行结果有些疑惑,但如果注意到这两条指令具体含义就清晰了,它们都是要统计连续的相同位数。对于VCLS来说是计算一个向量的每个元素中最高位后面与最高位相同的连续位数目,对于VCLZ是计算一个向量的每个元素中从最高位开始算起的连续零数目。因为0x12341234中将最高位展开就变为了0b0001xxxx…这种二进制形式,最高位是0后面跟了两个0,所以结果就为2。另外对于0x11112222转化为二进制以后就变为了0b0001 0001 0001 0001 0010 0010 0010 0010,当然连续0为3。

接着再来看看VCNT指令,它是计算一个向量的每个元素中位为 1 的数目,注意这里没有连续,我们来验证一下。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x12341234;
        data1[i] = 0x11112222;
        LOGI("1 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %0,{q0}\t\n"
            "VLDM %1,{q1}\t\n"
            "VCLS.S32 q2,q0\t\n"
            "VCNT.I8 q3,q1\t\n"
            "VSTM %0,{q2}\t\n"
            "VSTM %1,{q3}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0", "q1", "q2", "q3"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%#0X data1[%d]=%#0X", i, data[i], i, data1[i]);
    }

运行结果:

11-08 07:50:53.690 3094-3094/ndk.example.com.ndkexample I/Native: 1 data[0]=0X12341234 data1[0]=0X11112222
    1 data[1]=0X12341234 data1[1]=0X11112222
    1 data[2]=0X12341234 data1[2]=0X11112222
    1 data[3]=0X12341234 data1[3]=0X11112222
    2 data[0]=0X2 data1[0]=0X2020202
    2 data[1]=0X2 data1[1]=0X2020202
    2 data[2]=0X2 data1[2]=0X2020202
    2 data[3]=0X2 data1[3]=0X2020202

结果为0X2020202即0X02 02 02 02,代表每个I8有2两个位为1。

九、VRECPE 和 VRSQRTE

VRECPE(向量近似倒数)求出一个向量中每个元素的近似倒数,并将结果存放到另一个向量中。

VRSQRTE(向量近似平方根倒数)求出一个向量中每个元素的近似平方根倒数,并将结果存放到另一个向量中。

语法

Vop{cond}.datatype Qd, Qm

Vop{cond}.datatype Dd, Dm

其中:

op 必须为 RECPE 或 RSQRTE。

cond 是一个可选的条件代码。

datatype 必须为 U32 或 F32。

Qd、Qm 是四字运算的目标向量和操作数向量。

Dd、Dm 是双字运算的目标向量和操作数向量。

超出范围的输入的结果

下表显示了超出范围的输入值的结果。
【NEON 和 VFP 编程】NEON通用算术指令

十、VRECPS 和 VRSQRTS

VRECPS(向量倒数步进)将一个向量的元素与另一个向量的相应元素相乘,用 2 减去每个相乘结果,并将最终结果存放到目标向量的元素中。

VRSQRTS(向量平方根倒数步进)将一个向量的元素与另一个向量的相应元素相乘,用 3 减去每个相乘结果,再将这些结果除以 2,并将最终结果存放到目标向量的元素中。

语法

Vop{cond}.F32 {Qd}, Qn, Qm

Vop{cond}.F32 {Dd}, Dn, Dm

其中:

op 必须为 RECPS 或 RSQRTS。

cond 是一个可选的条件代码。

Qd、Qn、Qm 是四字运算的目标向量、第一个操作数向量和第二个操作数向量。

Dd、Dn、Dm 是双字运算的目标向量、第一个操作数向量和第二个操作数向量。

超出范围的输入的结果

下表显示了超出范围的输入值的结果。
【NEON 和 VFP 编程】NEON通用算术指令
用法

牛顿- 拉弗森迭代法:

xn+1=xn(2dxn)x_{n+1}=x_n(2-dx_n)

如果 x0 是将 VRECPE 应用于 d 的结果,则收敛到 (1/d)。

牛顿- 拉弗森迭代法:

xn+1=xn(3dxn2)/2x_{n+1}=x_n(3-dx_{n^2}) / 2

如果 x0 是将 VRSQRTE 应用于 d 的结果,则收敛到 1/d1/\sqrt{d}

以下是使用以上指令的示例片段用法。

vrecpe.f32          d1, d5
vrecps.f32          d2, d1, d5
vmul.f32            d1, d1, d2
vrecps.f32          d2, d1, d5
vmul.f32            d5, d1, d2

实际效果不一定很理想。具体除法运算也可以采用VFP指令VDIV。