什么是SIMD
SIMD(Single Instruction, Multiple Data,单指令多数据)是一种并行计算的架构和技术,用于在计算机处理器中同时对多个数据点执行相同的操作
- 单指令多数据
- 在SIMD架构中,一条指令可以处理多个数据
- 并行处理
- SIMD通过并行处理多个数据元素来提高计算效率。这种并行性特别适合于需要对大量数据进行相同操作的任务,如图像处理、音频处理和矩阵运算
- 硬件实现
- ESP32S3中,乐鑫为了提升此芯片的神经网络运算能力,加入了SIMD相关指令集,程序员可以使用这些指令集来优化性能
如何查看这些指令集
在esp32-s3_technical_reference_manual中的第一章处理器指令扩展中,乐鑫详细的介绍了S3芯片中SIMD指令以及使用方法
esp32s3都出来几年了,但是乐鑫在今年的文档中才开始描述这部分
- 处理器指令拓展 (PIE, Processor Instruction Extensions) 具有如下特性
- 增加 128-bit 位宽通用寄存器
- 支持 128-bit 位宽的向量数据操作,包括:乘法、加法、减法、累加、移位、比较等
- 合并数据搬运指令与运算指令
- 支持非对齐 128-bit 带宽的向量数据
- 支持取饱和操作
更详细的指令说明在参考手册文档的表1-4中
如何使用
我是一个汇编小白,间断性的学习过几次,但是工作中根本用不到,都忘记了
目前已经有很多开源项目中使用了ESP32S3的SIMD指令,例如:
- JPEGDEC开源库,作者针对ESP32S3进行了SIMD优化,大大降低jpeg的解码了时间 https://github.com/bitbank2/JPEGDEC/issues/56
- 陈亮在此对其进行了测试 https://www.bilibili.com/video/BV1t94y1a7k6/
- 乐鑫官方在esp_lvgl_port中,在对lvgl9的适配中 增加了simd的渲染适配,但是比较局限,只支持数据填充,不支持颜色混合等 https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port/src/lvgl9/simd
- 乐鑫在esp_new_jpg库中,也使用simd对jpeg解码进行了加速
编码测试
我写了一个测试demo,用来测试使用pie编程和c语言编程,simd指令能提速多少
测试内容为将两个8K的int16数据进行相加,使用定时器记录其时间,并进行对比
首先建立一个空白工程,然后新建一个asm_test.s,并且在cmakelists.txt中将其导入编译
在asm_test.s中增加以下内容(我参考了很多代码后,并且边问gpt边猜测边学,写下了以下代码)
# use to esp32s3 simd test.global esp32s3_simd_test_asm
esp32s3_simd_test_asm:entry a1, 48#a2 in_1#a3 in_2#a4 out#a5 nmovi a9, 0 # control number of data
process_channel:ee.vld.128.ip q0, a2, 16ee.vld.128.ip q1, a3,16ee.vadds.s16 q2, q0, q1ee.vst.128.ip q2, a4, 16addi a9, a9, 8bge a9, a5, pie_loop_endj process_channelpie_loop_end:retw
修改main.c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_heap_caps.h"
#include <stdint.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "esp_timer.h"int16_t __attribute__((aligned (16))) input_1[8192];
int16_t __attribute__((aligned (16))) input_2[8192];
int16_t __attribute__((aligned (16))) out[8192];//使用此函数完成input1 和 input2 中的元素逐个相加,并且将数据保存在output中。传入的数组长度为8192=8*1024
void esp32s3_simd_test_asm(int16_t *input1, int16_t *input2, int16_t *output,int size);
void esp32s3_simd_test_c(int16_t *input1, int16_t *input2, int16_t *output ,int size);void output_debug(int16_t *output);
void app_main(void)
{long long start_time, end_time, time_instance_ansi_c, time_instance_pie;//赋予初始值for(int i = 0 ; i < 8192 ; i++){input_1[i] = rand();input_2[i] = rand();}// test asmprintf("start asm\n");start_time = esp_timer_get_time();esp32s3_simd_test_asm(input_1,input_2,out,8192);end_time = esp_timer_get_time();time_instance_pie = end_time - start_time;output_debug(out);// test ansicprintf("\nstart c\n");start_time = esp_timer_get_time();esp32s3_simd_test_c(input_1,input_2,out,8192);end_time = esp_timer_get_time();time_instance_ansi_c = end_time - start_time;output_debug(out);printf("pie: %llu us, ansic %llu us, pie faster: %lld%%\n",time_instance_pie,time_instance_ansi_c,(time_instance_ansi_c - time_instance_pie) * 100 / time_instance_ansi_c);
}void esp32s3_simd_test_c(int16_t *input1, int16_t *input2, int16_t *output ,int size)
{for(int i = 0 ; i < size ; i++){output[i] = input1[i] + input2[i];}
}void output_debug(int16_t *output)
{printf("output[%d]:%d\n",2,output[2]);printf("output[%d]:%d\n",4,output[4]);printf("output[%d]:%d\n",8,output[8]);printf("output[%d]:%d\n",16,output[16]);printf("output[%d]:%d\n",32,output[32]);printf("output[%d]:%d\n",64,output[64]);printf("output[%d]:%d\n",128,output[128]);printf("output[%d]:%d\n",256,output[256]);printf("output[%d]:%d\n",512,output[512]);printf("output[%d]:%d\n",1024,output[1024]);printf("output[%d]:%d\n",2048,output[2048]);
}
额外的,我增加了sdkconfig.defaults, 为了将芯片的性能拉到最高
CONFIG_IDF_TARGET="esp32s3"
CONFIG_IDF_TARGET_ESP32S3=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y
CONFIG_ESP32S3_DATA_CACHE_64KB=y
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
测试
编译烧录后,开发板上打印出了以下信息:
start asm
output[2]:32767
output[4]:32767
output[8]:14096
output[16]:32767
output[32]:-14332
output[64]:-21112
output[128]:12022
output[256]:20041
output[512]:30343
output[1024]:-4054
output[2048]:-3496start c
output[2]:-18248
output[4]:-24817
output[8]:14096
output[16]:-19842
output[32]:-14332
output[64]:-21112
output[128]:12022
output[256]:20041
output[512]:30343
output[1024]:-4054
output[2048]:-3496pie: 55 us, ansic 379 us, pie faster: 85%
可以看到pie指令集所消耗的时间远小于C语言,pie速度几乎是c语言的7倍
但是输出好像有些问题,pie计算时,一些本来应该溢出的计算值,变成了32767,通过查阅手册,发现这种问题是正常的,在1.5.4章节,详细说明了数据溢出相关的问题
将input_1和input_2的值进行限制,输出结果就没有问题了
input_1[i] = rand() % 5000;input_2[i] = rand() % 5000;
输出结果
start asm
output[2]:5208
output[4]:5375
output[8]:5696
output[16]:3814
output[32]:1188
output[64]:1696
output[128]:4990
output[256]:3433
output[512]:4159
output[1024]:3106
output[2048]:2464start c
output[2]:5208
output[4]:5375
output[8]:5696
output[16]:3814
output[32]:1188
output[64]:1696
output[128]:4990
output[256]:3433
output[512]:4159
output[1024]:3106
output[2048]:2464
pie: 57 us, ansic 381 us, pie faster: 85%
其他
ESP32S3的SIMD指令集,应用场合较为局限,对程序员的能力要求较高,并且没有将这些指令广泛的封装成库
自己写的话,受限于汇编能力,只能完成一些较为简单的功能,并且数据在不对齐的情况下,加速效果会稍慢
JPEGDEC的作者在这里也有两篇博客,可以额外了解esp32s3 simd的更多信息:
- https://bitbanksoftware.blogspot.com/2024/01/esp32-s3-simd-minimal-example.html?m=1
- https://bitbanksoftware.blogspot.com/2024/01/surprise-esp32-s3-has-few-simd.html?m=1