为连续读取优化的一种缓存策略

事情的起因是boss要把对samba的支持从kernel转移到user模式的gio。这个迁移到还不是很复杂,改一些接口就可以。迁移过来后发现通过samba播放视频的性能下降的很厉害,在arm平台上,以前能流畅播放的视频现在完全不可看。

于是测了一下速率,使用gio通过gstreamer播放samba的视频,传输率只有100k/s多一点。进一步计时,发现gio在arm平台,即便只拷贝1个字节的文件,也要花0.16s左右的时间,而kernel模式下,只要0.01s。但是在连续读的情况下,gio可以达到600k/s多,而kernel只有400k/s。看来问题在于arm上gio由于引入dbus等操作,导致第一次读取时的延迟很长。因为gstreamer的gio插件默认一次只读4096字节,这样每次读取都要花掉0.16s,与kernel的0.01-0.02s的速度比起来,自然是慢了很多。

另外,由于卧艹的avi格式的存在,简单的顺序缓存效果不好。avi在播放过程中并不是流式读取,而是每隔一段时间就向后跳一段距离,读一小块的内容,然后再跳回来接着读。而且连续读这部分也不是严格连续,每次读取间都有一定的间隔,大概数据类似(偏移/大小):

10030 13446 18334 50430 22045

加重的那个就是非常卧艹的地方。

解决办法是使用三个cache:current,preload,seek。current和preload存储的是顺序数据,固定的大小,在不影响操作和系统内存的情况下,尽可能大。当current命中n次后,将紧邻current的下一块数据读入到preload里。如果命中了preload,就交换preload和current,何时读新块,由上一条规则决定。如果某次读取在current和preload都没有命中,就读取需要的数据到seek里(或者读取在可接受延迟下的最大块)。如果要读取的是紧接着seek的内容,就直接更新current。

一个可以稍微优化一下的地方是,current和preload可以有一定的交叠,这样遇到边界跳转读取的情况时,可以减少对seek的读取。

一个问题是,由于arm本身速度慢,读满一个buffer可能会花很长时间,这个在开始播放和跳转的时候对用户不够流畅。可能的解决办法是先读够用的一小块,然后后面起thread去读。不过我对arm上的thread性能表现表示怀疑。这个已经有了一些实验代码,但还没在arm上实践过。

另一个问题是,为什么user下的gio,连续读取的速度反而要快过kernel呢?

Googol Lee

多年生软件工程师,信仰开源

Munich, Germany http://air.googol.im