遇到了一个有意思的“性能”问题:
今天在 3A6000 上用 box64 运行空洞骑士(Unity)时发现即使是在菜单页,fps 也只有个位数。要知道空洞骑士的硬件要求并不高,应该是可以轻松跑满帧的。
打开 perf top,发现一切正常,绝大部分的时间都花在了运行 JIT code 中。但这个时候又发现了另一个神奇现象:打开 perf top 后,游戏菜单页的 fps 竟然提升到了 20 左右。关掉 perf top 就会再次回落到个位数。
于是用 gdb attach 上去,然后根据 perf top 在最热的地址那里打上断点,发现是如图的代码。也就是说,当前的线程卡死在了 ll/sc 序列里面出不来了。
然后就瞬间发现是
所以为什么性能会下降也就容易理解了,本来是
那为什么使用 perf top 会“提升”性能呢,猜测是 perf 的实现应该也大量使用了 ll/sc 序列,导致失败率上升了。
今天在 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 做的事情是:
那么如何使用 LSX 实现这条指令呢,我发现 VABSD.BU 可以用于计算
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 叫
有趣的是,据说禁用掉这个选项后,虽然会一定程度影响性能,但会更接近 x86 的强内存模型,因此更适合 x86 模拟器。虽然没有实际测试,但感觉合理,毕竟禁用掉了 store buffer 等同于禁用了 store 顺序的重排(?)
编辑:理解有误,请看评论区。
IMPCTL1.STFILL ,没理解错的话控制的应该是 store buffer 的启用与否。这个选项在 4k 页的内核中是禁用的:https://github.com/torvalds/linux/commit/d23b77953f5a4fbf94c05157b186aac2a247ae32。有趣的是,据说禁用掉这个选项后,虽然会一定程度影响性能,但会更接近 x86 的强内存模型,因此更适合 x86 模拟器。虽然没有实际测试,但感觉合理,毕竟禁用掉了 store buffer 等同于禁用了 store 顺序的重排(?)
编辑:理解有误,请看评论区。
Arm® Architecture Reference Manual Supplement, The Scalable Matrix Extension (SME), for Armv9-A
卧槽这玩意也太生猛了!
卧槽这玩意也太生猛了!
😱1👌1
最近做了个 DynamoRIO 的 poster,突然想起来之前不知道在哪儿看过的一个神奇越狱:
DynamoRIO 和 Pin 这类高性能 DBI 的核心工作原理都是将应用程序(下称 app)的代码“拷贝”到一个由自己管理的代码缓存中然后运行。在“拷贝”的过程中需要将代码转换为 PIC,并完成插桩等工作。这样既对 app 有了绝对的控制权,又可以不改变 app 的行为。
—— 但现实是不改变 app 的行为意味着 DBI 的存在必须对 app 完全透明,而这个先拷贝再运行的做法实际上已经引入了不可避免的不透明性。
app 首先运行一个特殊构造的指纹。这个指纹必须足够简单,DBI 不会做任何修改就可以原样放入代码缓存,比如下面的指令序列:
addi zero, zero, 114
addi zero, zero, 514
运行之后,app 就可以通过扫描 self/maps 找到这个指纹,即可成功确认自己是否运行于 DBI 中。
更进一步,既然找到了这个指纹,我们还能偷偷把它修改掉,比方说把它 patch 为对某个函数的调用。
这里你可能会有疑问:patch 后的函数调用实际上是 app 的代码地址,那 DBI 只需要把 app 的内存全都 map 成不可执行不就可以了?答案是不行,因为 DBI 必须保持透明,将本可执行的内存 map 成不可执行这个行为破坏了透明性。
所以,当 patch 完成,app 再次运行指纹的时候,此时就会调用到 app 的代码中,至此成功完成了越狱。
DynamoRIO 和 Pin 这类高性能 DBI 的核心工作原理都是将应用程序(下称 app)的代码“拷贝”到一个由自己管理的代码缓存中然后运行。在“拷贝”的过程中需要将代码转换为 PIC,并完成插桩等工作。这样既对 app 有了绝对的控制权,又可以不改变 app 的行为。
—— 但现实是不改变 app 的行为意味着 DBI 的存在必须对 app 完全透明,而这个先拷贝再运行的做法实际上已经引入了不可避免的不透明性。
app 首先运行一个特殊构造的指纹。这个指纹必须足够简单,DBI 不会做任何修改就可以原样放入代码缓存,比如下面的指令序列:
addi zero, zero, 114
addi zero, zero, 514
运行之后,app 就可以通过扫描 self/maps 找到这个指纹,即可成功确认自己是否运行于 DBI 中。
更进一步,既然找到了这个指纹,我们还能偷偷把它修改掉,比方说把它 patch 为对某个函数的调用。
这里你可能会有疑问:patch 后的函数调用实际上是 app 的代码地址,那 DBI 只需要把 app 的内存全都 map 成不可执行不就可以了?答案是不行,因为 DBI 必须保持透明,将本可执行的内存 map 成不可执行这个行为破坏了透明性。
所以,当 patch 完成,app 再次运行指纹的时候,此时就会调用到 app 的代码中,至此成功完成了越狱。
👌3