IT评测·应用市场-qidao123.com技术社区

标题: tesseract引擎RVV代码学习笔记 [打印本页]

作者: 熊熊出没    时间: 2025-3-31 21:33
标题: tesseract引擎RVV代码学习笔记
  Tesseract 是一个开源的 OCR(Optical Character Recognition,光学字符识别)引擎,可将图像中的文本转换为机器可读的文本格式。由于组内曾经有同事为这个项目贡献了RVV(RISC-V Vector)的代码,我打算单独拎出来学习一下。
  PR链接在此:Add RISC-V V support by hleft · Pull Request #4346 · tesseract-ocr/tesseract。由于有一定的篇幅,我只挑了汇编部分来进行阅读。
  1. static int DotProduct(const int8_t *u, const int8_t *v, int num) {
  2.   int total = 0;
  3.   asm __volatile__ (
  4.     "  .option       arch, +v                   \n\t"
  5.     "  vsetvli t0,zero,e32,m8,ta,ma             \n\t"
  6.     "  vmv.v.i v0,0                             \n\t"
  7.     "1:                                         \n\t"
  8.     "  vsetvli t0,%[num],e8,m2,ta,ma            \n\t"
  9.     "  vle8.v v16,0(%[u])                       \n\t"
  10.     "  vle8.v v24,0(%[v])                       \n\t"
  11.     "  sub %[num],%[num],t0                     \n\t"
  12.     "  vwmul.vv v8,v24,v16                      \n\t"
  13.     "  add %[u],%[u],t0                         \n\t"
  14.     "  add %[v],%[v],t0                         \n\t"
  15.     "  vsetvli zero,zero,e16,m4,tu,ma           \n\t"
  16.     "  vwadd.wv v0,v0,v8                        \n\t"
  17.     "  bnez %[num],1b                           \n\t"
  18.     "  vsetvli t0,zero,e32,m8,ta,ma             \n\t"
  19.     "  vmv.s.x v8,zero                          \n\t"
  20.     "  vredsum.vs v0,v0,v8                      \n\t"
  21.     "  vmv.x.s %[total],v0                      \n\t"
  22.     :  [u] "+r" (u),
  23.        [v] "+r" (v),
  24.        [num] "+r" (num),
  25.        [total] "+r" (total)
  26.     :
  27.     :  "cc", "memory"
  28.   );
  29.   return total;
  30. }
复制代码
  这个函数重要用来实现一维向量乘积,采用了内嵌汇编的方式进行优化,除了用RVV汇编,还可以用封装好的riscv_vector.h接口,不过这里使用了最原始的汇编,我们分段阅读。
  1. "  vsetvli t0,zero,e32,m8,ta,ma             \n\t"
  2. "  vmv.v.i v0,0                             \n\t"
复制代码
  vsetvli是跟向量寄存器组有关的指令,这里设置向量长度为最大(zero表示根据配置自动计算),然后再对向量寄存器初始化为0。
  1. "1:                                         \n\t"
  2. "  vsetvli t0,%[num],e8,m2,ta,ma            \n\t"
  3. "  vle8.v v16,0(%[u])                       \n\t"
  4. "  vle8.v v24,0(%[v])                       \n\t"
  5. "  sub %[num],%[num],t0                     \n\t"
复制代码
  1这里表示进入了循环,用RVV的利益就是循环过程中步长会自动调整,比如说长度为18,如果每次步长为8,传统的SIMD须要8+8+3,8是可以用向量指令集去实现,但是3这里就须要采用普通for循环手写,但是RVV会自动忽略掉这个过程,不用担心越界,只需关注循环内部本身即可,由于硬件会根据情况自动调整为向量步长为3。另外,这里vsetvli加载了num操纵数到t0寄存器,寄存器存的是向量步长,e8代表元素大小,相当于int8范例,由于函数参数传入的也是int8 *的指针。
  1. "  vwmul.vv v8,v24,v16                      \n\t"
  2. "  add %[u],%[u],t0                         \n\t"
  3. "  add %[v],%[v],t0                         \n\t"
复制代码
  vwmul.vv指令首先对v24和v16这两个向量寄存器组里面的元素求和,然后扩展位宽到16位,存到v8向量寄存器里面。之以是要扩展位宽,是由于8位的数乘8位的数可能会酿成16位的数。add这里就是分别对传入的两个函数参数进行指针的移动。
  1. "  vsetvli zero,zero,e16,m4,tu,ma           \n\t"
  2. "  vwadd.wv v0,v0,v8                        \n\t"
  3. "  bnez %[num],1b                           \n\t"
复制代码
  到了这一步,vsetvli重新将操纵数范例变为16位的,由于刚刚上面乘法的时候已经扩展为16位了。接着vwadd.wv将v8的结果累加到v0向量寄存器组,由于末了返回值是32位的,这里同样用了扩展位宽的加法。
  1. "  vsetvli t0,zero,e32,m8,ta,ma             \n\t"
  2. "  vmv.s.x v8,zero                          \n\t"
  3. "  vredsum.vs v0,v0,v8                      \n\t"
  4. "  vmv.x.s %[total],v0                      \n\t"
复制代码
  末了是这里,vsetvli重新调整向量寄存器中的元素为32位,接下来对v8清零,将v0寄存器组的所有元素归约求和到v0寄存器中,末了再将结果移到变量total当中,这个函数到此就实现完成了。
  这么看来,RVV在自动调整步长方面还是很有上风的(比起SIMD),不过由于汇编比较晦涩难明,以是下次打算寻找RVV C-Intrinsics的代码进行解剖。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4