ksco 的工作日志
245 subscribers
168 photos
10 videos
4 files
84 links
内容主要取决于我正在做的东西,目前主要是模拟器 / DBT 之类的散乱话题。
Download Telegram
感谢 @sterpr1m 分享的 spike-dasm 跨架构平替,效果:
$ ./la64dasm 700be063
0: 700be063 vadd.d $vr3, $vr3, $vr24


代码:
$ cat la64dasm 
#!/usr/bin/env bash

TEMP=$(mktemp)

echo $1 | tac -rs .. | echo "00000000: $(tr -d '\n')" | xxd -r > $TEMP
objdump -b binary -m Loongarch64 -D $TEMP | grep 0:
rm $TEMP
🥰4
May the 4th be with you.
🥰71
如何用一天的时间实现 numpy RVV 支持

背景:

numpy 的基本架构是有一套 universal intrinsic,然后各个后端(比如 Neon、SSE)用自己的 intrinsic 去实现这个 universal 的。

方案一:

所以如果要增加 RVV 的支持,比较正统的做法就是增加一个 RVV 的后端,然后去实现这套 universal intrinsic。

但就目前情况来说,上游自己也觉得维护这么多后端的担子太重了,所以他们其实有砍掉整个 universal intrinsic,换用更加成熟的 google/highway 的想法。

这也决定了上游几乎不太可能接受一个巨大的 RVV backend patch 了,方案一属于费力不讨好。

方案二:

既然上游有用 highway 的意愿,那就帮助上游把所有用 universal intrinsic 写的函数都用 highway 重写一遍,因为 highway 支持 RVV,这样 numpy RVV 也就顺手支持了。

方案二最大的问题就是上游推进 highway 支持的进度非常慢,这对其他已有的后端几乎没有影响,但对于 RVV 来说就是有还是无的致命问题。

方案三:

那如果放弃提交上游,有没有更加快捷,不需要完整实现一个 RVV 后端的方法呢?

方案三就是今天要介绍的做法,neon2rvv[1] 是一个将 ARM Neon intrinsic 翻译成 RVV intrinsic 的兼容层,举个例子:


uint32x2_t vadd_u32(uint32x2_t a, uint32x2_t b) {
return __riscv_vadd_vv_u32m1(a, b, 2);
}


可以看到 vadd_u32__riscv_vadd_vv_u32m1 是一对一的翻译,当然也存在一对多的情况。

因为 Neon 的寄存器是 128 位的,这就要求 vlen 必须大于等于 128 位,并且超出 128 位的部分会被完全浪费掉。

本着有总比没有强的原则,使用这个兼容层,我们就可以原地利用 Neon 后端来实现 RVV 后端了,理想情况下,要做的就只是 #include neon2rvv.h

当然,理想情况并不存在,在实际的实现过程中,我发现了一些无法用 RVV 实现的 Neon intrinsic;neon2rvv 尚未实现的 intrinsic;neon2rvv bug 等等。

最终的结果就是:

numpy 加了一个很小的 patch [2],neon2rvv 提交了一个很小的 PR [3],numpy RVV 就有了初步的支持。

在新发布的 BananaPi F3(顺序 8 核,vlen=256) 上运行 pytest 结果如下:


237 failed, 42599 passed


可以看到,目前的支持度还是挺不错的,失败的都是一些比较极端的情况,还需要花更多时间来 debug。

做了一个简单的 benchmark,在 absolute_f32 上对比标量有 4 倍的性能提升。因为这台机器的 vlen 是 256,所以预期值应该是 8 倍,这里 4 倍是因为高 128 位完全没利用起来。

[1] https://github.com/howjmay/neon2rvv
[2] https://github.com/plctlab/numpy/commit/edee45e5e184d23929a605c9441607aca03a019a
[3] https://github.com/howjmay/neon2rvv/pull/396
👍6
接下来是不是可以上 C-Reduce 了
2🤔1
ksco 的工作日志
接下来是不是可以上 C-Reduce 了
跑出来了,精简到了 <60 行:https://ksco.cool/a/wnwr

edit:已交由同事提交到了 Debian:https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1071140
🗿5
正在用 gdb 調試 gdb,感覺我 CPU 快燒了
🤯10
自文档的 Makefile
🥰6
Box64 新版本:
https://github.com/ptitSeb/box64/releases/tag/v0.2.8

「Loongarch dynarec! Linux games are running, and with good speed, especialy on 3A6000 platforms. Games on Wine are not working yet due to 16k pagesize limitation.」
🥰5👍1
TIL 如何计算一个 uint8 的 parity?

(parity 是指一个数字中 1 的个数的奇偶性,偶数结果为 1,奇数则结果为 0)

uint8 a = ...
a ^= (a >> 4)
a ^= (a >> 2)
a ^= (a >> 1)
a &= 1
a ^= 1
return a


其实就是按对来取消所有的 bits,最后留下的 least significant bit 取反就是结果。
🤔1
Forwarded from 刘阳
卧槽
Forwarded from 刘阳
我他妈知道了
Forwarded from 刘阳
龙芯的 amswap* 系列指令前面两个寄存器不能是同一个
😁7
Forwarded from 刘阳
那你倒是报个 sigill 啊
Forwarded from 刘阳
妈的真服了
索尼在日开放 JIT 岗位,难道真的要做 Arm 掌机?

https://x.com/firstadopter/status/1791347164662153306
🥰3
盘点一下 box64 中用于调试的基础设施:

trace
开启之后,可以在指定的 x64 pc 范围内逐指令打印指令执行后的微架构状态及变化。
一旦锁定问题的范围,这几乎是无敌的调试手段,但如果这个范围刚好在热点代码上,可能会生成上百 GB 的 trace 文件,做我的客人,看去吧。

dump
开启之后,动态重编译器在编译期会把生成的代码块打印出来,如下图。
如果锁定了问题范围,可以通过 dump 非常方便地看到 box64 对某条 x86 指令生成了什么 native 指令序列。

nodynarec
DynaRec 是 box64 中动态重编译器的名字,这个选项顾名思义就是可以在某个 x64 pc 范围内禁用掉 DynaRec,让这部分代码 fallback 到解释器实现。
可以用于 bisect 来定位问题范围,配合上面两个选项使用。

missing
可以打印出 DynaRec 在编译过程中遇到的未实现的指令,方便开发者为 DynaRec 添加新指令实现。

test
这个是大杀器,co-simulation。开启后,DynaRec 会逐条指令和解释器对比微架构状态的变化,并打印出不一致的地方。
但是这个手段对于一些存在 racing 的情况会失效并吐出大量的垃圾信息,比如 LOCK prefix 指令和多线程程序。所以仅适合调试较为简单的 guest 程序。

show segv
开启后 box64 会打印出所有遇到的 sigsegv 和 sigbus。
很多程序都会有自己的 sighandler,开启该选项后可以让一些不应该发生的 crash 暴露出来而不是被 guest 的 sighandler 捕获。

nosigsegv
开启后 box64 不会注册任何 guest sighandler,因为有上面的选项,这个不怎么常用。

rolling log
上述都是对于 DynaRec 的调试手段,rolling log 则是针对 libwrap 的。这个选项会记录所有的 native lib function call,并在程序死掉后打印出最后调用的 n 个,非常有用的调试手段。

showbt
开启后 box64 会在死掉之后打印 backtrace,比较通用的调试手段,偶尔会用。
今日上班,做屁屁踢
🆒3
遇到了一个有意思的“性能”问题:

今天在 3A6000 上用 box64 运行空洞骑士(Unity)时发现即使是在菜单页,fps 也只有个位数。要知道空洞骑士的硬件要求并不高,应该是可以轻松跑满帧的。

打开 perf top,发现一切正常,绝大部分的时间都花在了运行 JIT code 中。但这个时候又发现了另一个神奇现象:打开 perf top 后,游戏菜单页的 fps 竟然提升到了 20 左右。关掉 perf top 就会再次回落到个位数。

于是用 gdb attach 上去,然后根据 perf top 在最热的地址那里打上断点,发现是如图的代码。也就是说,当前的线程卡死在了 ll/sc 序列里面出不来了。

然后就瞬间发现是 bnez 写错了,应该是 beqz 。出现这个 typo 的原因是很多差不多的指令实现我都是直接从 RV64 后端拷贝过来然后改一改。龙架构和 RV 的 sc 指令最后的结果成功与否是相反的,RV 用 bnez ,龙架构用 beqz 。实现 LOCK DEC 指令时忘记改了。

所以为什么性能会下降也就容易理解了,本来是 sc 成功才会通过,现在变成了 sc 失败才会通过。很明显正常情况下, sc 成功的概率是远大于失败的概率的,不然无锁编程也没什么意义了。

那为什么使用 perf top 会“提升”性能呢,猜测是 perf 的实现应该也大量使用了 ll/sc 序列,导致失败率上升了。
🤯11
x86_64 SSE2 指令 PSADBW xmm1, xmm2/m128 做的事情是:

Computes the absolute differences of the packed unsigned byte integers from xmm2/m128 and xmm1; the 8 low differences and 8 high differences are then summed separately to produce two unsigned word integer results.


那么如何使用 LSX 实现这条指令呢,我发现 VABSD.BU 可以用于计算 absolute differences 部分,但 LSX 竟然没有求和指令用于实现第二部分。最后想出来了如下实现:

VABSD.BU v0, v0, v1

VPICKEV.B v2, v0, v0 // 挑出偶数位置的 8 位元素
VPICKOD.B v3, v0, v0 // 挑出奇数位置的 8 位元素
VEXTH.HU.BU v2, v2 // 8 位扩充为 16 位
VEXTH.HU.BU v3, v3 // 8 位扩充为 16 位
VADD.H v0, v2, v3 // 完成一次 8 位到 16 位的合并

VPICKEV.H v2, v0, v0 // 挑出偶数位置的 16 位元素
VPICKOD.H v3, v0, v0 // 挑出奇数位置的 16 位元素
VEXTH.WU.HU v2, v2 // 16 位扩充为 32 位
VEXTH.WU.HU v3, v3 // 16 位扩充为 32 位
VADD.W v0, v2, v3 // 完成一次 16 位到 32 位的合并

VPICKEV.W v2, v0, v0 // 挑出偶数位置的 32 位元素
VPICKOD.W v3, v0, v0 // 挑出奇数位置的 32 位元素
VEXTH.DU.WU v2, v2 // 32 位扩充为 64 位
VEXTH.DU.WU v3, v3 // 32 位扩充为 64 位
VADD.D v0, v2, v3 // 完成一次 32 位到 64 位的合并
🤔1
今天知道 LoongArch64 中有一个内核态的 CSR field 叫 IMPCTL1.STFILL ,没理解错的话控制的应该是 store buffer 的启用与否。这个选项在 4k 页的内核中是禁用的:https://github.com/torvalds/linux/commit/d23b77953f5a4fbf94c05157b186aac2a247ae32。

有趣的是,据说禁用掉这个选项后,虽然会一定程度影响性能,但会更接近 x86 的强内存模型,因此更适合 x86 模拟器。虽然没有实际测试,但感觉合理,毕竟禁用掉了 store buffer 等同于禁用了 store 顺序的重排(?)

编辑:理解有误,请看评论区。