Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimize io #978

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

feat: optimize io #978

wants to merge 1 commit into from

Conversation

wzv5
Copy link
Contributor

@wzv5 wzv5 commented Feb 10, 2025

Pull request

Feature

添加一个试验性的激进优化选项 default/experimental/optimize_io,开启后,会立即加载整个文件进内存,后期使用时不再有任何 IO 访问,避免硬盘速度拖慢出词速度。

使用方法
default.custom.yaml 文件中添加

patch:
  experimental:
    optimize_io: true

然后重新部署。
根据 schema 中加载的词库,内存占用会相应增加,比如白霜拼音方案会增加大概 40MB,但换来了后期丝滑流畅的出词体验。

Unit test

  • Done

Manual test

  • Done

Code Review

  1. Unit and manual test pass
  2. GitHub Action CI pass
  3. At least one contributor reviews and votes
  4. Can be merged clean without conflicts
  5. PR will be merged by rebase upstream base

Additional Info

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 10, 2025

如果想要尝鲜,欢迎下载测试(windows x64):rime.dll.zip

这个 rime.dll 已包含 #977

@lotem
Copy link
Member

lotem commented Feb 10, 2025

這會有什麼效果呢?原本就是讀內存,這個 PR 多複製了一遍。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 10, 2025

這會有什麼效果呢?原本就是讀內存,這個 PR 多複製了一遍。

原本是内存映射文件,并不是读内存,系统会自动管理IO,查词时还是会有IO读取,这可以通过各种系统IO查看工具来确认。

@lotem
Copy link
Member

lotem commented Feb 10, 2025

這會有什麼效果呢?原本就是讀內存,這個 PR 多複製了一遍。

原本是内存映射文件,并不是读内存,系统会自动管理IO,查词时还是会有IO读取,这可以通过各种系统IO查看工具来确认。

如果第一次查詞讀整個文件性能可以接受,後續查詞再讀其中一部分也應該不成問題?

由於詞典的數據格式主要用於隨機訪問,逐塊加載能少讀、少佔內存。如果全書都要放在內存裏,就沒必要用內存映射文件了。

PR 這個做法,等於一上來就完整地讀了一遍磁盤文件,全局視角該從磁盤讀到內存的都沒省下。
究竟會有哪些提升,恐怕需要大量的實驗來證明。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 10, 2025

因为硬盘负载是动态的,很可能在某个时刻出现高负载,影响出词,一次性加载整个词库进内存就可以避免这种情况。
而且很多时候,性能瓶颈都在硬盘IO上,CPU和内存负载并不高,如果能用内存换取没有硬盘IO瓶颈,我认为是值得的。

当然,我也使用了 experimental 选项来说明这是试验性的。
至于效果,可以先发布试试,让大家来尝试一下,experimental 特性可以不用保证后期可用性。

@ksqsf
Copy link
Member

ksqsf commented Feb 10, 2025

two cents:

  1. 页面载入后常用页大概率会驻留到内存里,所以这个PR大概只能提升“初次访问某个页面”的效率。既然如此,我猜想只需在初始化时把所有页面踩一遍,就能得到本PR宣称的效果。(在 Linux 上可以使用 madvise POPULATE。)

  2. 匿名页也有可能被换出到磁盘上,所以其实也不能避免高负载情况下的磁盘IO。

最后,我感觉固态磁盘访问并非性能瓶颈,至少比起用户词库和 Lua GC 来说固态词库访问速度够快了。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 10, 2025

我猜想只需在初始化时把所有页面踩一遍,就能得到本PR宣称的效果

事实上我观察到的情况是,修改前,在打字时,会持续抓到系统硬盘IO操作,一直打字就会一直有IO,此时算法服务进程的内存占用非常稳定,基本没有波动。
说明内存映射文件并没有被缓存,或者说缓存是不可靠的。所以靠“踩一遍所有页面”来迫使系统缓存是不可能的,至少在windows系统上是这样。
这也可能是因为我开着几个Visual Studio项目,系统的文件缓存容量有限,前台进程还不够用,一个后台进程不被缓存可太正常了。
与其依赖不可靠、不透明的系统缓存,不如自己做一个可靠的缓存。

匿名页也有可能被换出到磁盘上

至于说内存页面被换出到硬盘上,这只在内存紧张时才会发生,平时基本不会出现,系统肯定会优先使用物理内存的。
顺便一提,我更加激进,我关了系统的虚拟内存,根本就不可能出现硬盘换页。放着大内存不用,这不是浪费嘛。

我感觉固态磁盘访问并非性能瓶颈,至少比起用户词库和 Lua GC 来说固态词库访问速度够快了。

不是所有人都有高速固态硬盘,lua哪怕再慢,也还是比硬盘IO快得多,这就不是一个数量级的,硬盘IO延迟那个高啊。

@lotem
Copy link
Member

lotem commented Feb 11, 2025

這種觀察不意味着有性能問題。「系统硬盘IO操作」可能是用戶詞典。

一次把文件全部讀到內存不失爲一種實現方式。真要那樣做,直接讀文件就行~
既然都用了 mapped file,兩者又沒有太大區別。
如果沒有明確的數據支撐,我感覺沒動力做這個實驗。

另外一說,這些代碼是在普及 SSD 之前寫的,據用戶稱,本品在蘋果電腦上相對於大公司的輸入法有着可明顯感知的性能優勢,可知 mapped file 用於機械硬盤也有可靠的性能。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 11, 2025

那能否添加一个选项,按 @ksqsf 说的那样,手动踩一遍所有分页,迫使系统把整个文件加载进内存,这样也可以减少硬盘IO。
现在这样分块加载文件,频繁触发IO真的很烦,明明内存足够用的。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 11, 2025

顺便补充一下,优化硬盘IO不在于减少读取量,而在于减少IO次数,或者说寻址次数。
硬盘寻址耗时太长了,这也是为什么哪怕是最顶级的SSD,4K随机读写性能也还是不行。
而现在的分块加载形式,本质上就是4K随机读取,性能会很差,延迟非常高。
如果能一次性加载整个文件,对于硬盘来说就变成了大量连续读取,就能跑到硬盘标称的理论最快速度。

@lotem
Copy link
Member

lotem commented Feb 11, 2025

這個不屬於普通用戶的需求吧……
完全不懂爲什麼有這樣的擔憂
大概
因爲是電腦專家纔有這些煩惱……

要不你先試試:
寫一個 librime 插件
dictionary 重新註冊,替換成另一個實現。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 11, 2025

其实是因为我加载了万象拼音的100MB语法模型,抓到这个模型受到IO速度影响,想改善模型查询速度,然后一路找到了内存映射文件这里。
然后发现只要稍微改下内存映射文件的实现,就能轻松优化包括词典、模型在内的所有IO,于是就从这里改了。

@lotem
Copy link
Member

lotem commented Feb 12, 2025

其实是因为我加载了万象拼音的100MB语法模型,抓到这个模型受到IO速度影响,想改善模型查询速度,然后一路找到了内存映射文件这里。 然后发现只要稍微改下内存映射文件的实现,就能轻松优化包括词典、模型在内的所有IO,于是就从这里改了。

那不妨做一個語法插件。不然都沒法只在這個模型上開啓實驗選項。

@yfdyh000
Copy link

我赞成“踩一遍所有页面”可能有相同的效果。
“内存页面被换出到硬盘”对很多低内存用户来说可能属常见,所以此开关对他们可能是带来负效果。
固态硬盘的高IO延迟,可能不是很常见,除非硬盘能力本身有问题?或者,能调高线程的IO优先级吗,对于高IO负载下的用户体验。

如果只是为了预取文件,自己写一个小程序来自启动、定期启动,将需要的文件读入内存,使系统缓存文件,应该能产生同等效果?

“IO次数”方面,是librime读取的块太小导致预取、缓存不足吗,是否可能预测性的将常用块放入一个缓存池。目前代码上似乎没有预取逻辑。如果读取模式是完全随机的,预取可能会意义有限,但小文件在大空闲内存下完全或大部分预读取到内存可能仍有帮助。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 14, 2025

具体实现方式可以再讨论,我这个代码只是最偷懒的做法。但我不推荐提高优先级的做法,调高优先级对系统整体影响还是比较大的。

不过我想大家都能承认,在内存足够的情况下,预加载文件是能够优化使用体验的吧?

如果读取模式是完全随机的,预取可能会意义有限

为什么这么说,预取不就是优化随机读取么,把硬盘随机读取变成了,硬盘大量连续读取+内存随机读取。

@yfdyh000
Copy link

具体实现方式可以再讨论,我这个代码只是最偷懒的做法。但我不推荐提高优先级的做法,调高优先级对系统整体影响还是比较大的。

应对其他程序持续高IO的考虑。

如果读取模式是完全随机的,预取可能会意义有限

为什么这么说,预取不就是优化随机读取么,把硬盘随机读取变成了,硬盘大量连续读取+内存随机读取。

我说的预取是块预读,例如read请求一次读1MB的连续数据,而非频繁读KB级别的文件数据块。不是完全预取,用户可能有数百兆的词库文件。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants