RISC-V向量指令集的Compute Library函数库移植

8
M i c r o c o n t r o l l e r s &E m b e d d e d S y
s t e m s  2021年第1期w w w .m e s n e t .c o m .c n
R I S C V 向量指令集的C o m p u t e L i b r a r y 函数库移植
叶锡聪1,庄灿锋1,王宇木2,吴鹏飞2,潘志铭2,廖力灵1,孙轶1
(1.深圳优矽科技有限公司,深圳518000;2.
深圳大学)摘要:A R M C o m p u t e L i b r a r y 是一类针对A R M C o r t e x A 系列C P U 处理器和A R M M a l i 系列G P U 特定优化的软件算法函数库,内部实现了卷积滤波器㊁卷积神经网络等算法,并且使用C o r t e x A C P U N E O N ㊁M a l i G P U 的S I M D 技术
加速算法运行㊂R I S C V 指令集作为一种开源的指令集,目前发布了相对稳定的S I M D 指令集版本,并且C S K Y 开源了支持v 0.7.1i n t r i n s i c s 的G C C 和Q E MU ㊂在这些基础上,本文尝试将A R M C o m p u t e L i b r a r y 函数库移植至支持
R I S C V 向量指令集,其中函数移植的核心思想是在不修改源文件的前提下,通过编写一个头文件,用宏定义把A R M N E O N 向量类型接口逐一替换成R I S C V i n t r i n s i c s 中定义的向量类型和向量函数接口㊂
关键词:C o m p u t e L i b r a r y ;R I S C V ;向量指令集;函数库移植;Q E MU ;A R M 中图分类号:T P 31    文献标识码:A
T r a n s p l a n t a t i o n o f C o m p u t e L i b r a r y o
f R I S C V V e c t o r I n s t r u c t i o n S e t Y e X i c o n
g 1
,Z h u a n g C a n f e n g 1
,W a n g Y u m u 2
,W u P e n g f e i 2
,P a n Z h i m i n g 2
,
L i a o L i l i n g 1
,S u n Y i q
u n 1
(1.U C T E C H I P ,S h e n z h e n 518000,C h i n a ;2.S h e n z h e n U n i v e r s i t y
)A b s t r a c t :A RM C o m p u t e L i b r a r y i s a k i n d o f s o f t w a r e a l g o r i t h m f u n c t i o n l i b r a r y s p e c i f i c a l l y o p
t i m i z e d f o r A RM C o r t e x A s e r i e s C P U p r o c e s s o r s a n d A RM M a l i s e r i e s G P U s .I t i n t e r n a l l y i m p l e m e n t s a l g
o r i t h m s s u c h a s c o n v o l u t i o n f i l t e r s a n d c o n v o l u t i o n a l n e u r a l n e t -w o r k s ,a n d u s e s C o r t e x A C P U N E O N o r M a l i G P U t o a c c e l e r a t e a l g o r i t h m o p e r a t i o n .A s a n o p
e n s o u r c e i n s t r u c t i o n s e t ,t h e R I S C V i n s t r u c t i o n s e t h a s r e l e a s e d a r e l a t i v e l y s t a b l e v e r s i o n o
f t h e S I M D i n s t r u c t i o n s e t ,a n d C S K Y h a s o p
e n s o u r c e d G C C a n d Q E MU t h a t s u p p o r t v 0.7.1i n t r i n s i c s .O n t h e s e
f o u n d a t i o n s ,t h i s a r t i c l e a t t e m p t s t o p o r t t h e A RM C o m p u t e L i b r a r y f u n c t i o n l i b r a r y t o s u p p o r t t h e R I S C V v e c t o r i n s t r u c t i o n s e t .T h e c o r e i d e a o f f u n c t i o n m i
g r a t i o n i s t o w r i t e a
h e a d e r f
i l e a n d u s e m a c r o d e f i n i t i o n s w i t h o u t m o d i f y i n g
t h e s o u r c e f i l e s t o r e p l a c e t h e A RM N E O N v e c t o r t y p e i n t e r f a c e o n e b y o n e w i t h t h e v e c t o r t y p e a n d v e c t o r f u n c t i o n i n t e r f a c e d e f i n e d i n R I S C V i n t r i n s i c s .
K e y
w o r d s :C o m p u t e L i b r a r y ;R I S C V ;v e c t o r i n s t r u c t i o n s e t ;f u n c t i o n l i b r a r y i m g r a t i o n ;Q E MU ;A RM 0 引 言
S I M D (S i n g l e I n s t r u c t i o n M u l t i p
l e D a t a )指令可以一次性对多个数据进行操作,减少指令执行次数,代替单一
的原来需要多个周期的循环工作[1]
单人飞行器,对于多媒体应用和向量函数库来说,S I M D 技术能够显著提升性能[2]
,
所以目前主流的指令集架构都会提供S I M D 扩展指令集,
例如A R M 架构的N E O N 指令集㊁I n t e l 架构的MM X /S S E 指令集㊁R I S C V 架构的向量指令集㊂
R I S C V 指令集是一种开源的指令集,
它定义了一组标准扩展指令,提供基本整数I ㊁乘法/除法M ㊁原子操作A ㊁单精度浮点运算F ㊁双精
度浮点运算D ㊁向量指令V ㊁比特操作指令B ㊁D S P 指令P ㊁
压缩指令C ,其中基本整数指令集I 是强制要求实现的,其他的指令集M /A /F /D
/V /B /P /C 是可选的㊂目前I /M /A /F /D /C 指令集已经形
成标准,编码和汇编格式不再发生改变,而V /B /P 这些指
令集则还在不断地定义和完善中[
3]
㊂从程序员的角度来说,如果想要在程序中使用S I M D
技术,有以下4种方法[4]
:①在程序中使用编译器实现的
i n t r i n s i c s ,这种方法较为简单而且易于维护,i n t r i n s i c s 在编译时会直接转化为S I M D 的汇编指令;②直接使用支持S I M D 指令技术的函数库,例如支持A R M N E O N 指令集
的C o m p u t e L i b r a r y
㊁N e 10等开源函数库,这些函数库提供了C /C ++接口,所以开发者不需要具体了解A R M
N E O N 指令,而且这些算法函数库往往经过特定优化,性能较高;③编译程序时使用自动向量化选项(a u t o v e c -
t o r i z a t i o n ),让编译器自动生成S I M D 指令,
不需要修改源
敬请登录网站在线投稿
(t o u g a o.m e s n e t.c o m.c n)2021年第1期
9
代码,且跨平台移植方便,所以这是最简单直接的一种方法,但是对于复杂算法,编译器的优化效果很差;④手写
S I M D汇编,这种方法移植性差,而且难度高㊂对一般的应用开发者来说,既能够取得较高性能㊁又能降低开发难度的方法就是使用算法函数库㊂
A R M C o m p u t e L i b r a r y是一个基于A R M C P U和A R M G P U架构㊁针对图像处理㊁计算机视觉和机器学习的低层次算法函数库,能够在L i n u x㊁安卓甚至裸机上运行,目前支持的处理器架构有a r m v7a(32b i t)和a r m64 v8a(64b i t)架构[5]㊂R I S C V作为一种新兴的指令集架构,目前的开源软件生态还不够成熟,特别是对于R I S C V向量扩展指令集,虽然大部分的R I S C V向量扩展指令已经确定,并且也有了编译工具链和模拟器的支持,但是目前并没有基于R I S C V向量指令集的算法函数库可供使用,所以R I S C V的S I M D编程仍然给应用开发者带来了较大的困难㊂
本文尝试将A R M C o m p u t e L i b r a r y函数库移植至支持R I S C V向量指令集,其中函数移植的核心思想是尽可能地不修改或者少修改源文件,然后通过编写一个头文件,用宏定义的方法把A R M定义的N E O N向量类型和函数接口替换成R I S C V i n t r i n s i c s中定义的向量类型和函数接口㊂在软件工具链方面,使用了C S K Y开源的支持v0.7.1i n t r i n s i c s的G C C,同时为了测试移植后的C o m-p u t e L i b r a r y,还使用了C S K Y开源的软件仿真器Q E MU㊂1R I S C V向量扩展指令
与A R M N E O N指令集类似,R I S C V向量指令也属于S I M D指令,拥有独立的寄存器文件,而且支持8位㊁16位㊁32位㊁64位等数据类型的向量运算[6],但是与A R M架构的N E O N指令集相比,它具有如下三个特点:硬件向量长度不仅在编译时未知,而且还可能会在程序运行过程中发生变化;向量寄存器文件可以通过软件配置成不同的数据类型和大小;几乎所有的向量指令都支持掩码操作[7]㊂1.1可变的硬件向量长度
在S I M D编程中,往往需要考虑边角处理,以A R M A53N E O N(具有32个128位长度的向量寄存器)为例,ʌ代码段1ɔ展示了如何实现一个浮点向量加法㊂第一个w h i l e循环主体负责主要的浮点加法运算,每次循环能且只能处理4个32位的单精度浮点运算,如果需要处理的向量长度不为4的倍数,例如向量处理长度为7,那么则需要依靠第二个w h i l e循环主体处理㊂
ʌ代码段1ɔ
s n i p b l n C n t=b l o c k S i z e>>2U;
w h i l e(b l k C n t>0U){
v e c1=v l d1q_f32(p S r c A);
v e c2=v l d1q_f32(p S r c B);
r e s=v a d d q_f32(v e c1,v e c2);
v s t1q_f32(p D s t,r e s);
p S r c A+=4;
p S r c B+=4;
p D s t+=4;
b l k C n t;
}
b l k C n t=b l o
c k S i z e&0x3;
w h i l e(b l k C n t>0U){
*p D s t++=(*p S r c A++)+(*p S r c B++);
b l k C n t;
}
对于ʌ代码段1ɔ,如果使用R I S C V向量指令实现,可以得到如ʌ代码段2ɔ所示的汇编代码,可以看到ʌ代码段2ɔ中只使用了一个循环主体,这是因为R I S C V向量指令集引入了向量长度寄存器v l,通过设置v l寄存器
图1使用v l寄存器控制元素个数
以控制向量指令需要
运算的元素个数㊂如
图1所示,在V L E N=
128b i t,S E W=32b i t,
L MU L=1的设置下,
如果v l寄存器被设置
为4,那么一条向量浮点加法指令v f a d d.v v一次性可以处理4个32位的单精度浮点元素,类似地,如果v l寄存器被设置为3,那么v f a d d.v v一次性只能处理3个单精度浮点元素㊂
ʌ代码段2ɔ
s n i p
1:
v s e t v l i t0,a0,e32,m8
v l w.v v0,(a1)
v l w.v v8,(a2)
v f a d d.v v v16,v8,v0
v s w.v v16,(a3)
s u b a0,a0,t0
s l l i t0,t0,2
a d d a1,a1,t0
a d d a2,a2,t0
a d d a3,a3,t0
b n e z a0,1b
r e t
1.2可变的寄存器文件配置
R I S C V向量指令集具有32个V L E N b i t的向量寄
10
M i c r o c o n t r o l l e r s &E m b e d d e d S y
精炼剂
s t e m s  2021年第1期w w w .m e s n e t .c o m .c n
存器,这些向量寄存器可以寄存器组的形式进行索引,例如可以将v 0㊁v 1㊁v 2㊁v 3㊁v 4㊁v 5㊁v 6㊁v 7八个寄存器绑定为一个寄存器组,称为v 0寄存器组,
如图2和图3所示
㊂图2 V L E N =128b ,L M U L =8
时的寄存器分布
图3 V L E N=128b ,L M U L =4时的寄存器分布
寄存器组中的寄存器个数可以是1个㊁2个㊁4个或者
8个,通过L MU L (L e n g t h MU L t i p
l i e r )参数设置㊂寄存器组相当于扩展了寄存器的位宽,但是相应地也减少了寄存器的个数,例如在L MU L =8的情况下,只有v 0㊁v 8㊁v 16㊁v 24四个寄存器可以使用,如果指令使用了不存在的向量寄存器(例如v 1
),那么将会触发非法指令异常㊂1.3 支持掩码操作指令
ʌ代码段3
ɔf o r (i =0;i <8;i ++)
{  i f (y
[i ]!=0)    z [i ]=x [i ]-y [i
]}
ʌ代码段3ɔ中的循环体由于需要条件执行,所以无法
对向量元素执行相同的操作,不利于对循环体的向量化展开㊂为了解决这个问题,R I S C-V 向量指令集把v 0寄存器作为掩码寄存器,v 0寄存器中的每一个比特分别控制着向量指令中每个元素运算的条件执行,如果向量元素对应的v 0比特的值为1,并且该指令开启了掩码使能,则执行该元素的运算,否则执行空操作㊂
ʌ代码段4
ɔa s m (①
"l i a 0,8\n "②
电暧器"v s e t v l i t 0,a 0,e 8,m 1\n "③
"v l e .v v 1,(%0)\n " ④
"v m s n e .v i v 0,v 1,0\n "⑤ "v l e .v v 2,(%1)\n "⑥
"v s u b .v v v 3,v 1,v 2,v 0.t \n "⑦
"v s e .v v 3,(%2)\n "⑧
:⑨ :"r "(y
),"r "(x ),"r "(z )⑩);
对于ʌ代码段3ɔ,如果使用R I S C V 向量汇编指令实
现,可以得到ʌ代码段4ɔ,代码段4使用了G C C 内联汇编,具体的内联语法参见参考文献[8]㊂下面简单分析ʌ代码段4ɔ,④表示从数组y 中获取数据,⑥则使用整型比较指
令,如果v 0中的元素不为0,则将v 0寄存器中的对应比特置1,在获得有效的掩码寄存器v 0后,⑦就可以使用向量减法指令,实现代码段3中的循环体展开㊂
2 C o m p u t e L i b r a r y 系统架构
整体系统架构如图4所示,架构中包含了三个主要目
录:a r m _c o m p u t e 包含了整个函数库的头文件;e x a m p
l e s 包含了一些例程;s r c 包含了整个函数库的源文件[5]㊂从
隧道施工风机
构建模块的角度来看,整个函数库的主体是s r c 目录,它内部包含了c o r e ㊁g r a p h ㊁r u n t i m e 三个目录,前两者负责设计算法,但是它们既不负责内存分配,也不会执行多线程操作,这些工作交由r u n t i m e 处理,所以这种构建方法实际上把算法实现分成了顶层设计和底层实现两部分
图4 C o m p u t e L i b r a r y 系统架构
3 移植过程介绍
C o m p u t e L i b r a r y 函数库主要由高级语言C++构成,所以大部分算法不需要额外的移植工作即可以在
敬请登录网站在线投稿
(t o u g a o.m e s n e t.c o m.c n)2021年第1期
11
R I S C V上运行,但是部分算法实现时使用了A R M N E-O N i n t r i n s i c s或者A R M N E O N内联汇编指令,因此将这些N E O N i n t r i n s i c s和N E O N内联汇编替换成R I S C V 向量指令就是主要的移植工作㊂我们提出了两种移植方案:方案一,出所有包含A R M N E O N i n t r i n s i c s和A R M N E O N内联汇编指令的源文件,逐一对源文件进行移植;方案二,尽可能地不修改或者少修改源文件,用宏定义的方法把A R M定义的N E O N向量类型替换成R I S C V i n t r i n s i c s中定义的向量类型㊂考虑到C o m p u t e L i b r a r y 内部包含了大量使用到的A R M N E O N i n t r i n s i c s和A R M N E O N内联汇编指令的源文件,方案一的工作量较大,所以最终选定方案二㊂
编写一个头文件v e c t o r_t r a n s p l a n t.h,将N E O N i n-t r i n s i c s替换成R I S C V V e c t o r i n t r i n s i c s,不过对于那些使用了N E O N内联汇编的源文件,则只能手写R I S C V V e c t o r汇编指令进行修改㊂在整个移植过程中,使用的R I S C V向量规范版本为v0.7.1,见参考文献[7],根据R I S C V向量规范和C S K Y开源的G C C功能规范,约定如下参数:V L E N=128b,E L E N=64b㊂
3.1向量数据类型的重新定义
A R M i n t r i n s i c s针对64位/128位N E O N寄存器定义了一些数据类型,命名方式如下[9]:
<t y p e><s i z e>x<n u m b e r_o f_l a n e s>_t
例如i n t16x4_t代表一个包含4个16位i n t16_t元素的向量类型,类似地,f l o a t32x4_t代表一个包含4个32位f l o a t数据类型的向量类型㊂
根据参考文献[10],C S K Y开源的G C C也定义了相应的向量数据类型,命令方式如下:
<e l e m e n t_b i t s>x<l m u l>_t
例如i n t16x m1_t表示在L MU L=1情况下,向量寄存器中的元素数据类型为i n t16_t,由于在移植过程约定V L E N=128b,那么该数据类型代表一个包含8个16位i n t16_t元素的向量类型,由此可得表1所列的映射关系㊂
表1A R M N E O N与R I S C V V e c t o r映射关系1
除了上述这些描述单个向量寄存器的数据类型外, A R M N E O N i n t r i n s i c s还定义了一些数组形式的向量类型,这是因为部分N E O N指令(例如N E O N l o a d/s t o r e指令)支持同时操作多个向量
寄存器㊂这些向量类型的命名格式如下[9]:
<t y p e><s i z e>x<l a n e s>x<l e n g t h_o f_a r r a y>_t
ʌ代码段5ɔ
t y p e d e f s t r u c t i n t16x4x2_t{
i n t16x4_t v a l[2];
}i n t16x4x2_t;
ʌ代码段5ɔ为实际的代码举例,以结构体的形式,利用向量类型i n t16x4_t重新构建了一个新的向量类型i n t16x4x2_ t,相当于把相连的两个向量寄存器绑定成一个寄存器组㊂对于A R M N E O N指令而言,大部分的算术指令并不支持同时运算多个寄存器,如果要对实际数据执行操作,则需要从各个寄存器中选择元素,例如<v a r_n a m e>.v a l[0][9]㊂
C S K Y开源的G C C也定义了类似的向量类型数组[10],例如i n t32x2x m1_t表示在L MU L=1的情况下,将相连的两个向量寄存器绑定成一个寄存器组,并且向量寄存器中的元素数据类型为i n t16_t,由此可以得到表2所列的映射关系㊂
表2A R M N E O N与R I S C V V e c t o r映射关系2
3.2内在函数的重新定义(有对应指令支持)
A R M提供的i n t r i n s i c s函数是紧密与N E O N指令相对应的,所以每个函数的命名方式如下[9]:
<o p n a m e><f l a g s>_<t y p e>
例如v a d d q_s16表示两个16位有符号整型的向量相加,运算公式为V c[i]=V a[i]+V b[i]㊂需要注意的是,v a d d q_s16中的q用来标识使用128位向量寄存器进行运算㊂
C S K Y开源的G C C也提供了R I S C V向量的i n-t r i n s i c s函数,命名方式如下[10]:
<o p n a m e><_m a s k>_<t y p e>x m<l m u l>
以v a d d v v_i n t16x m1为例,它表示在L MU L=1的情况下,两个16位有符号整型的向量相加,与A R M N E O N 不同,在使用v a d d v v_i n t16x m1函数时,除了需要提供i n t16x8_t类型的操作数a和b外,还需要提供向量长度g v l(g r a n t e d v e c t o r l e n g t h)参数㊂根据g v l参数确定需要执行运算的元素个数㊂ʌ代码段6ɔ展示的是重新实现后
12
M i c r o c o n t r o l l e r s &E m b e d d e d S y s t e m s  2021年第1期w w w .m e s n e t .c o m .c n
的v a d d q
_s 16函数㊂ʌ代码段6
ɔ__e x t e n s i o n __s t a t i c __i n l i n e i n t 16x 8_t
v a d d q
_s 16(i n t 16x 8_t __a ,i n t 16x 8_t __b )①{
i n t n u m b e r =8;②
u n s i g n e d i n t g v l =v s e t v l i (n u m b e r ,R V V _E 16,R V V _M 1);③ r e t u r n v a d d v v _i n t 16x m 1(__a ,__b ,g
v l );④}
下面逐一分析代码段6,①是v a d d q _s 16的函数原型,在移植过程中,需要保证重新实现后的i n t r i n s i c s 与A R M
N E O N 定义的i n t r i n s i c s 接口完全保持一致;②定义了一个n u m b e r 变量,并且初始化为8;③使用v s e t v l i 函数(也属于i n t r i n s i c s )初始化C S R 寄存器,设置L MU L =1,
元素位宽为16b ,元素个数为8个(满足16b X 8=128b=
V L E N );④调用v a d d v v _i n t 16x m 1函数,
同时返回结果㊂对于L o a d /S t o r e ㊁最大或最小值㊁逻辑运算㊁加减乘除以及衍生的乘加㊁饱和加等大部分A R M N E O N 指令操
作,都可以到对应的R I S C V 向量指令,
所以移植过程与v a d d q
_s 16类似㊂3.3 内在函数的重新定义(
无对应指令支持)虽然大部分的A R M N E O N 指令操作都可以到对
应的R I S C V 向量指令,
但仍有一些指令需要结合多条已有的R I S C V 向量扩展指令实现功能㊂例如N E O N i n t r i n s i c s 函数v a b s q
_s 8,它表示取一个8位有符号整型向量的绝对值,R I S C V 向量扩展指令中并没有定义求整型向量绝对值的指令,所以需要通过异或,然后相加,最后取最大值一系列操作得出绝对值㊂
ʌ代码段7
ɔ__e x t e n s i o n __s t a t i c __i n l i n e i n t 8x 16_t
v a b s q _s 8(i n t 8x 16_t __a ){ i n t n u m b e r =16;
u n s i g n e d i n t g v l =v s e t v l i (n u m b e r ,R V V _E 8,R V V _M 1); i n t 8x m 1_t b =v x o r v i _i n t 8x m 1(__a ,-1,g v l ); i n t 8x m 1_t c =v a d d v i _i n t 8x m 1(b ,1,g
v l ); r e t u r n v m a x v v _i n t 8x m 1(__a ,c ,g
v l );}
ʌ代码段7ɔ与ʌ代码段6ɔ类似,下面主要分析利用异
或㊁加法㊁求最大值三个指令实现求绝对值的原理㊂在对一个数进行运算时,都是在这个数的补码上进行的,由于正数的补码是原码,负数的补码为原码除符号位取反后加
1,
这时可以发现如果将符号位也取反后加1,那么可以得到相反数㊂例如-2的补码是8 b 1111_1110,在与-1(8 b 1111_1111)异或后加1,可以得到8 b 0000_0010,
也就是+2,类似地,如果是+2,那么可以得到-2,所以在得
到相反数后,还需要使用求最大值指令选出正数㊂
4 初步测试移植后的C o m p u t e L i b r a r y
A R M C o m p u t e L i b r a r y 自带了一些测试程序,
运行这些程序可以初步验证移植后的C o m p u t e L i b r a r y 是否正
常工作㊂由于目前还没有硬件平台能够支持R I S C V 向量指令集,所以选择在C S K Y 开源的Q E MU 上测试,
为了验证程序运行的结果是否正确,我们还选择使用一块A R M C o r t e x A 53开发板作为参考对象,对比两者的运算结果是否完全一致㊂
4.1 需要测试的程序
由于在移植C o m p u t e L i b r a r y 过程中,
主要修改了与N E O N 指令相关的算法代码,
所以在移植完成后,需要重点验证N E O N 指令相关的代码㊂C o m p u t e L i b r a r y 的e
x -a m p l e s 子目录下包含了一些N E O N 指令相关的例程,目前选择的测试程序如表3所列㊂
sero-0151
表3 需要测试的例程列表
例程名称功能描述
n e o n _p e r m u t e 改变数组矩阵的排列,实现图片的格式转换n e o n _c o p y _o b j
e c t s 实现对输入的复制,设置限制条件,在输入中按
行复制后输出
n e o n _c a r t o o n _e f f e c t 对图片卡通化处理
n e o n _s c a l e
对图片缩放处理
n e o n _c o n v o l u t i o n
对图片作局部卷积处理
4.2 验证运算结果
叶轮加工
n e o n _p e r
m u t e 和n e o n _c o p y _o b j
e c t s 这两个例程可以直接在终端打印结果㊂图5和图6对照A R M C o r t e x
A 53开发板和R I S C V Q E MU 两者的输出结果(
左边图为A R M C o r t e x A 53,右边图为R I S C V Q E MU )
,可以看出两者的运算结果完全一致㊂
图5 n e o n _c o p y _o b j
e c t s 对比

本文发布于:2024-09-22 17:37:42,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/4/167148.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:向量   指令   寄存器   需要   实现   使用   算法
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议