NEON快速入门

1.简介

没有长篇大论,只用于NEON快速入门!
SIMD:

  • 单指令处理多个数据的并行技术
  • 例如在C语言中对一个int[8]的数组里每一个数都执行加1操作,SIMD技术可以通过一条add指令并行处理;而通常我们自己写for循环需要执行8次add才能完成,耗时更多

NEON:

  • 一种基于SIMD并适用于ARM的技术,从ARM-V7开始被采用,目前可以在ARM Cortex-A和Cortex-R系列处理器中使用
  • 寄存器,用于存放需要操作的数据;16个128bit的寄存器(128bit代表最大长度,对于int类型数据,能存放4个,对于char类型数据能存放16个,也就是说我能直接把int[4]或者char[16]直接放到128bit的寄存器中)、32个64bit寄存器
  • 支持的数据类型,上官网查询
  • 常用操作:加减乘除,数据之间的读写等

2.简单实例

实例内容:两个长度为5000的int型数组相加,把每个结果存入另一个长度为5000的数组当中,对比纯C语言实现和NEON实现的性能。
纯C实现函数:平平无奇,谁都能写

void add_int_c(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i++)
    dst[i] = src1[i] + src2[i];
}

NEON实现函数:
int32x4_t in1, in2, out:用于存放4个int型数据的变量,int32x4_t是一个数据类型声明(和int效果一致),类似的还有int16x8_t(用于存放8个short类型数据)、int8x16_t(用于存放16个char类型数据),更详细的去官网查询
in1 = vld1q_s32(src1):将指针src1的4个数据加载到in1当中,vld1q_s32用于加载int型的数据,并且只会是4个数据,其余类型的加载函数自行查询,但都是相对应的
src1 += 4:数据加载完成后,指针向后移动4位,用于后续数据加载
out = vaddq_s32(in1, in2):执行in1和in2两个向量相加操作,并存入out中
vst1q_s32(dst, out):将out里的数据存入dst指针指向的内存中
完毕!

void add_int_neon(int* dst, int* src1, int* src2, int count)
{
    int i;
    for (i = 0; i < count; i += 4)
    {
        int32x4_t in1, in2, out;
        in1 = vld1q_s32(src1);
        src1 += 4;
        in2 = vld1q_s32(src2);
        src2 += 4;
        out = vaddq_s32(in1, in2);
        vst1q_s32(dst, out);
        dst += 4;
    }
}

整体代码

#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include<arm_neon.h>

void add_int_c(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i++)
    dst[i] = src1[i] + src2[i];
}

void add_int_neon(int* dst, int* src1, int* src2, int count)
{
    int i;
    for (i = 0; i < count; i += 4)
    {
        int32x4_t in1, in2, out;
        in1 = vld1q_s32(src1);
        src1 += 4;
        in2 = vld1q_s32(src2);
        src2 += 4;
        out = vaddq_s32(in1, in2);
        vst1q_s32(dst, out);
        dst += 4;
    }
}

int main()
{
    /* 数据内存分配,初始化 */
    int size = 5000;
    int *dst = (int*)malloc(size * sizeof(int));
    int *src1 = (int*)malloc(size * sizeof(int));
    int *src2 = (int*)malloc(size * sizeof(int));

    for(int i = 0; i < size; i++)
    {
        src1[i] = i;
        src2[i] = i;
    }

    /* 时间初始化 */
    struct timespec time1_img = {0, 0};
    struct timespec time2_img = {0, 0};
    clock_gettime(CLOCK_REALTIME, &time1_img);

    for(int i = 0; i < size; i++)
    {
        add_int_c(dst, src1, src2, size);
    }
    clock_gettime(CLOCK_REALTIME, &time2_img);
    printf("C time:%d ms\n", (time2_img.tv_sec - time1_img.tv_sec)*1000 + (time2_img.tv_nsec - time1_img.tv_nsec)/1000000);

    printf("dst[0]:%d\n", dst[0]);
    memset(dst, 0, size);

    clock_gettime(CLOCK_REALTIME, &time1_img);
    for(int i = 0; i < size; i++)
    {
        add_int_neon(dst, src1, src2, size);
    }
    clock_gettime(CLOCK_REALTIME, &time2_img);
    printf("Neon time:%d ms\n", (time2_img.tv_sec - time1_img.tv_sec)*1000 + (time2_img.tv_nsec - time1_img.tv_nsec)/1000000);
    printf("dst[0]:%d\n", dst[0]);

    free(dst);
    free(src1);
    free(src2);

    return 0;
}

Makefile

CC	= @echo " GCC	$@"; $(CROSS)gcc

CROSS = arm-xmv2-linux-
CFLAGS += -mcpu=cortex-a9 -mfloat-abi=softfp  -mfpu=neon -mno-unaligned-access -fno-aggressive-loop-optimizations -flax-vector-conversions -fsigned-char -fopenmp
CFLAGS += -std=gnu99 -Wall -O2

TESTSOURCE = $(wildcard ./main.c)
TESTTARGET = main.out

TARGET = $(TESTTARGET) 

all:$(TARGET)

$(TESTTARGET):
	$(CC) $(CFLAGS) -save-temps -o $(TESTTARGET) $(TESTSOURCE) -lstdc++ -lm

clean:
	rm -f $(TESTTARGET) 

效果对比
在这里插入图片描述

3.总结

  • demo中可以明显看到程序执行耗时减少一半,效果非常明显
  • neon只适用于重复运算,我们只需要找到那些具有重复运算的地方,然后使用neon进行加速
  • 第一次看到这些函数非常陌生,多去官网查几次就熟悉了
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
使用NEON指令集来实现堆排序是一个复杂的任务,涉及到对数据的重新排列、比较和交换等操作。下面是一个基本的伪代码示例,展示了如何使用NEON指令集来加速堆排序算法的部分步骤: ```cpp // 假设输入数据是一个整数数组,长度为N,已经按照NEON向量寄存器对齐 int32_t* data = ...; // 输入数据数组 // 使用NEON指令加载输入数据 int32x4_t vec = vld1q_s32(data); // 执行堆排序步骤1:构建最大堆(Build Max Heap) for (int i = N / 2 - 1; i >= 0; i--) { heapify(vec, N, i); } // 执行堆排序步骤2:依次取出最大元素并调整堆(Heapify) for (int i = N - 1; i > 0; i--) { // 将当前根节点(最大值)与未排序部分的最后一个元素交换 swap(vec[0], vec[i]); // 调整堆,重新构建最大堆 heapify(vec, i, 0); } // 将排序后的结果存储回数组 vst1q_s32(data, vec); ``` 在上述代码中,`heapify`函数用于调整堆的结构,确保根节点是最大值。`swap`函数用于交换两个元素的值。这些函数需要根据堆排序算法的要求来实现。 请注意,上述代码仅提供了一个基本的框架,用于展示如何使用NEON指令集来加速堆排序算法。实际实现中,您需要根据具体的堆排序算法和数据类型来进行适当的修改和优化。此外,还应考虑数据排列、循环展开、指令调度等方面的优化技术,以提高代码的效率和性能。 最后,强烈建议在实现堆排序算法之前,详细了解并熟悉堆排序算法的原理和步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值