服务器之家:专注于服务器技术及软件下载分享
分类导航

Linux|Centos|Ubuntu|系统进程|Fedora|注册表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服务器之家 - 服务器系统 - Linux - 学习在kernel态下使用NEON对算法进行加速的方法

学习在kernel态下使用NEON对算法进行加速的方法

2022-02-21 17:54commando_lwp Linux

这篇文章主要介绍了学习在kernel态下使用NEON对算法进行加速的方法,一起来学习下,大大提高数据运算的效率。

本文跟着小编一起来学习在linux kernel态下如何使用neon对算法进行加速的技巧,内容通过图文实例给大家做了详细分析,一起来看下。

arm处理器从cortex系列开始集成neon处理单元,该单元可以简单理解为协处理器,专门为矩阵运算等算法设计,特别适用于图像、视频、音频处理等场景,应用也很广泛。

本文先对neon处理单元进行简要介绍,然后介绍如何在内核态下使用neon,最后列举实例说明。

一.neon简介

其实最好的资料就是官方文档,cortex™-a series programmer's guide ,以下描述摘自该文档

1.1 simd

neon采用simd架构,single instruction multy data,一条指令处理多个数据,neon中这多个数据可以很多,而且配置灵活(8bit、16bit、32bit为单位,可多个单位数据),这是优势所在。

如下图,apu需要至少四条指令完成加操作,而neon只需要1条,考虑到ld和st,节省的指令更多。

 学习在kernel态下使用NEON对算法进行加速的方法

上述特性,使neon特别适合处理块数据、图像、视频、音频等。

 1.2 neon architecture overview

neon也是load/store架构,寄存器为64bit/128bit,可形成向量化数据,配合若干便于向量操作的指令。

1.2.1 commonality with vfp         1.2.2 data type

学习在kernel态下使用NEON对算法进行加速的方法

 指令中的数据类型表示,例如vmlal.s8:

学习在kernel态下使用NEON对算法进行加速的方法

1.2.3 registers 

32个64bit寄存器,d0~d31;同时可组成16个128 bit寄存器,q0~q15。与vfp公用。

学习在kernel态下使用NEON对算法进行加速的方法

寄存器内部的数据单位为8bit、16bit、32bit,可以根据需要灵活配置。

学习在kernel态下使用NEON对算法进行加速的方法

neon的指令有normal,long,wide,narrow和saturating variants等几种后缀,是根据操作的源src和dst寄存器的类型确定的。

学习在kernel态下使用NEON对算法进行加速的方法

   学习在kernel态下使用NEON对算法进行加速的方法

1.2.4 instruction set

学习在kernel态下使用NEON对算法进行加速的方法

                     学习在kernel态下使用NEON对算法进行加速的方法

1.3 neon 指令分类概述

指令比较多, 详细可参考cortex™-a series programmer's guide。可大体分为:

neon general data processing instructions   neon shift instructions  neon logical and compare operations  neon arithmetic instructions neon multiply instructions  neon load and store element and structure instructions b.8 neon and vfp pseudo-instructions

简单罗列一下各指令

学习在kernel态下使用NEON对算法进行加速的方法

                学习在kernel态下使用NEON对算法进行加速的方法

                  学习在kernel态下使用NEON对算法进行加速的方法

                学习在kernel态下使用NEON对算法进行加速的方法

  学习在kernel态下使用NEON对算法进行加速的方法

无循环左移,负数左移按右移处理。

load和store指令不太好理解,说明一下。

  学习在kernel态下使用NEON对算法进行加速的方法

1.4 neon 使用方式      

1.4.1 neon使用方式

neon有若干种使用方式:

c语言被编译器自动向量化,需要增加编译选项,且c语言编码时有若干注意事项。这种方式不确定性太大,没啥实用价值   neon汇编,可行,汇编稍微复杂一点,但是核心算法还是值得的   intrinsics,gcc和armcc等编译器提供了若干与neon对应的inline函数,可直接在c语言里调用,这些函数反汇编时会直接编程响应的neon指令。这种方式比较实用与c语言环境,且相对简单。本文后续使用这种方式进行详细说明。          1.4.2  c语言neon数据类型

需包含arm_neon.h头文件,该头文件在gcc目录里。都是向量数据。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
typedef __builtin_neon_qi int8x8_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_hi int16x4_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_si int32x2_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_di int64x1_t;
typedef __builtin_neon_sf float32x2_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_poly8 poly8x8_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_poly16 poly16x4_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_uqi uint8x8_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_uhi uint16x4_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_usi uint32x2_t  __attribute__ ((__vector_size__ (8)));
typedef __builtin_neon_udi uint64x1_t;
typedef __builtin_neon_qi int8x16_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_hi int16x8_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_si int32x4_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_di int64x2_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_sf float32x4_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_poly8 poly8x16_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_poly16 poly16x8_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_uqi uint8x16_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_uhi uint16x8_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_usi uint32x4_t  __attribute__ ((__vector_size__ (16)));
typedef __builtin_neon_udi uint64x2_t  __attribute__ ((__vector_size__ (16)));
 
typedef float float32_t;
typedef __builtin_neon_poly8 poly8_t;
typedef __builtin_neon_poly16 poly16_t;
 
typedef struct int8x8x2_t
{
 int8x8_t val[2];
} int8x8x2_t;
 
typedef struct int8x16x2_t
{
 int8x16_t val[2];
} int8x16x2_t;
 
typedef struct int16x4x2_t
{
 int16x4_t val[2];
} int16x4x2_t;
 
typedef struct int16x8x2_t
{
 int16x8_t val[2];
} int16x8x2_t;
 
typedef struct int32x2x2_t
{
 int32x2_t val[2];
} int32x2x2_t;
 
typedef struct int32x4x2_t
{
 int32x4_t val[2];
} int32x4x2_t;
 
typedef struct int64x1x2_t
{
 int64x1_t val[2];
} int64x1x2_t;
 
typedef struct int64x2x2_t
{
 int64x2_t val[2];
} int64x2x2_t;
 
typedef struct uint8x8x2_t
{
 uint8x8_t val[2];
} uint8x8x2_t;
 
typedef struct uint8x16x2_t
{
 uint8x16_t val[2];
} uint8x16x2_t;
 
typedef struct uint16x4x2_t
{
 uint16x4_t val[2];
} uint16x4x2_t;
 
typedef struct uint16x8x2_t
{
 uint16x8_t val[2];
} uint16x8x2_t;
 
typedef struct uint32x2x2_t
{
 uint32x2_t val[2];
} uint32x2x2_t;
 
typedef struct uint32x4x2_t
{
 uint32x4_t val[2];
} uint32x4x2_t;
 
typedef struct uint64x1x2_t
{
 uint64x1_t val[2];
} uint64x1x2_t;
 
typedef struct uint64x2x2_t
{
 uint64x2_t val[2];
} uint64x2x2_t;
 
typedef struct float32x2x2_t
{
 float32x2_t val[2];
} float32x2x2_t;
 
typedef struct float32x4x2_t
{
 float32x4_t val[2];
} float32x4x2_t;
 
typedef struct poly8x8x2_t
{
 poly8x8_t val[2];
} poly8x8x2_t;
 
typedef struct poly8x16x2_t
{
 poly8x16_t val[2];
} poly8x16x2_t;
 
typedef struct poly16x4x2_t
{
 poly16x4_t val[2];
} poly16x4x2_t;
 
typedef struct poly16x8x2_t
{
 poly16x8_t val[2];
} poly16x8x2_t;
 
typedef struct int8x8x3_t
{
 int8x8_t val[3];
} int8x8x3_t;
 
typedef struct int8x16x3_t
{
 int8x16_t val[3];
} int8x16x3_t;
 
typedef struct int16x4x3_t
{
 int16x4_t val[3];
} int16x4x3_t;
 
typedef struct int16x8x3_t
{
 int16x8_t val[3];
} int16x8x3_t;
 
typedef struct int32x2x3_t
{
 int32x2_t val[3];
} int32x2x3_t;
 
typedef struct int32x4x3_t
{
 int32x4_t val[3];
} int32x4x3_t;
 
typedef struct int64x1x3_t
{
 int64x1_t val[3];
} int64x1x3_t;
 
typedef struct int64x2x3_t
{
 int64x2_t val[3];
} int64x2x3_t;
 
typedef struct uint8x8x3_t
{
 uint8x8_t val[3];
} uint8x8x3_t;
 
typedef struct uint8x16x3_t
{
 uint8x16_t val[3];
} uint8x16x3_t;
 
typedef struct uint16x4x3_t
{
 uint16x4_t val[3];
} uint16x4x3_t;
 
typedef struct uint16x8x3_t
{
 uint16x8_t val[3];
} uint16x8x3_t;
 
typedef struct uint32x2x3_t
{
 uint32x2_t val[3];
} uint32x2x3_t;
 
typedef struct uint32x4x3_t
{
 uint32x4_t val[3];
} uint32x4x3_t;
 
typedef struct uint64x1x3_t
{
 uint64x1_t val[3];
} uint64x1x3_t;
 
typedef struct uint64x2x3_t
{
 uint64x2_t val[3];
} uint64x2x3_t;
 
typedef struct float32x2x3_t
{
 float32x2_t val[3];
} float32x2x3_t;
 
typedef struct float32x4x3_t
{
 float32x4_t val[3];
} float32x4x3_t;
 
typedef struct poly8x8x3_t
{
 poly8x8_t val[3];
} poly8x8x3_t;
 
typedef struct poly8x16x3_t
{
 poly8x16_t val[3];
} poly8x16x3_t;
 
typedef struct poly16x4x3_t
{
 poly16x4_t val[3];
} poly16x4x3_t;
 
typedef struct poly16x8x3_t
{
 poly16x8_t val[3];
} poly16x8x3_t;
 
typedef struct int8x8x4_t
{
 int8x8_t val[4];
} int8x8x4_t;
 
typedef struct int8x16x4_t
{
 int8x16_t val[4];
} int8x16x4_t;
 
typedef struct int16x4x4_t
{
 int16x4_t val[4];
} int16x4x4_t;
 
typedef struct int16x8x4_t
{
 int16x8_t val[4];
} int16x8x4_t;
 
typedef struct int32x2x4_t
{
 int32x2_t val[4];
} int32x2x4_t;
 
typedef struct int32x4x4_t
{
 int32x4_t val[4];
} int32x4x4_t;
 
typedef struct int64x1x4_t
{
 int64x1_t val[4];
} int64x1x4_t;
 
typedef struct int64x2x4_t
{
 int64x2_t val[4];
} int64x2x4_t;
 
typedef struct uint8x8x4_t
{
 uint8x8_t val[4];
} uint8x8x4_t;
 
typedef struct uint8x16x4_t
{
 uint8x16_t val[4];
} uint8x16x4_t;
 
typedef struct uint16x4x4_t
{
 uint16x4_t val[4];
} uint16x4x4_t;
 
typedef struct uint16x8x4_t
{
 uint16x8_t val[4];
} uint16x8x4_t;
 
typedef struct uint32x2x4_t
{
 uint32x2_t val[4];
} uint32x2x4_t;
 
typedef struct uint32x4x4_t
{
 uint32x4_t val[4];
} uint32x4x4_t;
 
typedef struct uint64x1x4_t
{
 uint64x1_t val[4];
} uint64x1x4_t;
 
typedef struct uint64x2x4_t
{
 uint64x2_t val[4];
} uint64x2x4_t;
 
typedef struct float32x2x4_t
{
 float32x2_t val[4];
} float32x2x4_t;
 
typedef struct float32x4x4_t
{
 float32x4_t val[4];
} float32x4x4_t;
 
typedef struct poly8x8x4_t
{
 poly8x8_t val[4];
} poly8x8x4_t;
 
typedef struct poly8x16x4_t
{
 poly8x16_t val[4];
} poly8x16x4_t;
 
typedef struct poly16x4x4_t
{
 poly16x4_t val[4];
} poly16x4x4_t;
 
typedef struct poly16x8x4_t
{
 poly16x8_t val[4];
} poly16x8x4_t;

 1.4.3  gcc的neon函数

跟neon指令对应,详见gcc手册。

学习在kernel态下使用NEON对算法进行加速的方法

 二.内核状态下使用neon的规则

在linux里,应用态可以比较方便使用neon instrinsic,增加头arm_neon.h头文件后直接使用。但是内核态下使用neon有较多限制,在linux内核文档  /documentation/arm/kernel_mode_neon.txt对此有详细说明。要点为:

学习在kernel态下使用NEON对算法进行加速的方法

 还有一点特别关键:

学习在kernel态下使用NEON对算法进行加速的方法

?
1
2
3
4
5
6
cc [m] /work/platform-zynq/drivers/zynq_fpga_driver/mmi_neon/lcd_hw_fs8812_neon.o
in file included from /home/liuwanpeng/lin/lib/gcc/arm-xilinx-linux-gnueabi/4.8.3/include/arm_neon.h:39:0,
         from /work/platform-zynq/drivers/zynq_fpga_driver/mmi_neon/lcd_hw_fs8812_neon.c:8:
/home/liuwanpeng/lin/lib/gcc/arm-xilinx-linux-gnueabi/4.8.3/include/stdint.h:9:26: error: no include path in which to search for stdint.h
 # include_next <stdint.h>
 没有使用-ffreestanding编译选项时,在内核态下使用出现此编译错误。

 三.实例

neon一般在图像等领域,最小处理单位就是8bit,而不是1bit,这方便的例子非常多,本文就不说明了。在实际项目中,我需要对液晶的一组数据按位操作,变换,形成新的数据,如果用传统arm指令,掩码、移位、循环,想想效率就非常低。于是决定使用neon的位相关指令完成上述任务。

3.1 任务说明

如下图,需要对各个bit进行转换,组成新的数据。

 学习在kernel态下使用NEON对算法进行加速的方法

3.2 算法说明

使用vmsk、vshl、vadd等位操作完成。

3.3 kernel配置

必须配置内核支持neon,否则kernel_neon_begin()和kernel_neon_end()等函数不会编辑进去。

make menuconfig:floating point emulation,如下图。

学习在kernel态下使用NEON对算法进行加速的方法

?
1
2
3
未使能“support for neon in kernel mode”时会报错:
mmi_module_amp: unknown symbol kernel_neon_begin (err 0)
mmi_module_amp: unknown symbol kernel_neon_end (err 0)

3.4 模块代码

由于neon代码需要单独设置编译选项,所以单独建立了一个内核模块,makefile如下:

?
1
cflags_module += -o3 -mfpu=neon -mfloat-abi=softfp -ffreestanding 

核心代码:

?
1
2
3
#include <linux/module.h>
#include <linux/printk.h>
#include <arm_neon.h>  // 来自gcc的头文件,必须用-ffreestanding编译选徐昂

#define lcd_8812_row_bytes 16
#define lcd_8812_page_rows 8
#define lcd_page_bytes (lcd_8812_row_bytes*lcd_8812_page_rows)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int fs8812_cvt_buf( uint8 * dst, uint8 * src )
{
 
  uint8x16_t v_src[8];
  uint8x16_t v_tmp[8];
  uint8x16_t v_dst[8];
  uint8x16_t v_msk;
  int8x16_t v_shift;
  int8 rshl_bits[8] = {0,1,2,3,4,5,6,7};
  int8 row,bit;
  uint8 page;
  uint8 * fb_page_x = null; 
 
  // convert the frame_buf for fs8812
  for( page=0;page<4;page++ ){
    fb_page_x = src + page*lcd_page_bytes;
    for( row=0;row<lcd_8812_page_rows;row++ )
      v_src[row] = vld1q_u8( fb_page_x + row*lcd_8812_row_bytes );
       for( bit=0;bit<8;bit++){
        v_msk = vdupq_n_u8(1<<bit);
        for( row=0;row<lcd_8812_page_rows;row++){
          v_tmp[row] = vandq_u8(v_src[row],v_msk);  // only process the desire bit
          v_shift = vdupq_n_s8( rshl_bits[row]-bit );
          v_tmp[row] = vshlq_u8( v_tmp[row],v_shift );
        } 
        v_dst[bit] = vorrq_u8(v_tmp[0],v_tmp[1]);   // all bit_x convert to one row
        v_dst[bit] |= vorrq_u8(v_tmp[2],v_tmp[3]);
        v_dst[bit] |= vorrq_u8(v_tmp[4],v_tmp[5]);
        v_dst[bit] |= vorrq_u8(v_tmp[6],v_tmp[7]);       
      }
      // store to ram
      fb_page_x = dst + page*lcd_page_bytes;
      for( row=0;row<lcd_8812_page_rows;row++ ){
         vst1q_u8(fb_page_x,v_dst[row]);
        fb_page_x += lcd_8812_row_bytes;
      }
  }
  return 0;
}
 
export_symbol_gpl(fs8812_cvt_buf);

调用模块,务必没有“-mfpu=neon -mfloat-abi=softfp ”选项

?
1
2
3
4
// convert the frame_buf for fs8812
kernel_neon_begin();
fs8812_cvt_buf( g_tmp_buf, frame_buf );
kernel_neon_end();

 以上就是本篇文章的全部内容,大家有不懂的可以在下面留言区讨论。

原文链接:https://www.cnblogs.com/liuwanpeng/p/7833000.html

延伸 · 阅读

精彩推荐
  • Linux手把手教您在 Linux 上使用 GPG 加解密文件

    手把手教您在 Linux 上使用 GPG 加解密文件

    在本教程中,我将告诉你如何用 GPG 加密和解密文件。这是一个简单的教程,你可以在你的 Linux 系统上尝试所有的练习。这将帮助你练习 GPG 命令,并在你...

    Linux中国6962021-12-15
  • Linux确保Linux系统安全的前提条件 漏洞防护

    确保Linux系统安全的前提条件 漏洞防护

    Linux 作为开放式的操作系统受到很多程序员的喜爱,很多高级程序员都喜欢编写Linux操作系统的相关软件。这使得Linux操作系统有着丰富的软件支持,还有无...

    Linux之家2642020-04-11
  • LinuxLinux中环境变量配置的步骤详解

    Linux中环境变量配置的步骤详解

    Linux中环境变量包括系统级和用户级,系统级的环境变量是每个登录到系统的用户都要读取的系统变量,而用户级的环境变量则是该用户使用系统时加载的...

    Myths7882022-02-10
  • Linux详解Linux系统下PXE服务器的部署过程

    详解Linux系统下PXE服务器的部署过程

    这篇文章主要介绍了Linux系统下PXE服务器的部署过程,包括对PXE的API架构作了一个基本的简介,需要的朋友可以参考下...

    运维之道9812019-07-04
  • LinuxLinux lnmp下无法使用mail发邮件的两种解决方法

    Linux lnmp下无法使用mail发邮件的两种解决方法

    在配置了lnmp环境后,出现了mail函数不能发送邮件的问题,其实有两种方法,一是使用sendmail组件,而是使用postfix。下面为大家一一介绍下 ...

    Linux之家4042019-09-17
  • LinuxLinux上设置用户通过SFTP访问目录的权限的方法

    Linux上设置用户通过SFTP访问目录的权限的方法

    这篇文章主要介绍了Linux上设置用户通过SFTP访问目录的权限的方法,SFTP可以理解为使用SSH协议进行FTP传输的协议,因而同时要对OpenSSH进行相关设置,需要的朋...

    OSChina10022019-06-19
  • LinuxLinux常用的日志文件和常用命令

    Linux常用的日志文件和常用命令

    成功地管理任何系统的关键之一,是要知道系统中正在发生什么事。 Linux 中提供了异常日志,并且日志的细节是可配置的。Linux 日志都以明文形式存储,所...

    Linux教程网2632020-04-18
  • Linuxlinux中rmdir命令使用详解(删除空目录)

    linux中rmdir命令使用详解(删除空目录)

    今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的 ...

    linux命令大全5372019-11-19