I/O优化

I/O基本知识


整个I/O操作由应用程序、文件系统和磁盘共同完成,应用程序将I/O命令发送到文件系统,文件系统在合适的时机把I/O操作发送给磁盘。整个流程的瓶颈在于磁盘I/O,但有时文件系统为了降低磁盘对应用程序的影响,会采用各种方式进行优化,因为文件系统的性能变得十分重要。

1、文件系统

文件系统,简单来说既是存储和组织数据的方式。对于Android来说普遍采用的文件系统是Linux的常用的ext4文件系统,华为在EMUI5.0以后就使用F2FS取代ext4,谷歌也在最新的旗舰手机Pixel3使用了F2FS文件系统。F2FS文件系统在小文件随机读写方面比ext4更快,不足之处在于可靠性方面出现过一点问题,随着谷歌和华为的投入和使用,未来F2FS将成为Android的主流文件系统。
应用程序调用read()方法,系统会通过中断从用户空间进入内核处理流程,然后经过VFS(Virtual File System,虚拟文件系统)、具体文件系统、页缓存Page Cache。下面是Linux一个通用I/O架构模型


I/O架构模型
  • 虚拟文件系统(VFS)。它主要用于实现屏蔽具体的文件系统,为应用程序的操作提供一个统一的接口。
  • 文件系统(File System)。文件系统需要考虑具体文件元数据如何组织、目录和索引结构如何设计,怎么分配和清理数据。
  • 页缓存(Page Cache)。Page Cache就像我们经常使用的数据缓存,是文件系统对数据的缓存,目的是提升内存命中率。通过/proc/meminfo文件可以查看缓存的内存占用情况,当手机内存不足时,系统会回收他们的内存,这样整体I/O的性能就会有所降低。

2、磁盘

磁盘指的就是系统的存储设备。当应用程序要read()的数据没有在页缓存中,这时候就需要真正想磁盘发起I/O请求,这个过程要先经过内核的通用模块层、I/O调度层、设备驱动层,最后才会交给具体的硬件设备处理。


  • 通用块层。系统中能够随机访问固定大小数据块(block)的设备成为设备,CD、磁盘和SSD这些都输设备。通用块层主要作用是接收上层发出的磁盘请求,并最终发出I/O,他的作用类似VFS,提供通用接口,屏蔽下层实现。
  • I/O调度层。为了降低真正的磁盘I/O,我们不能接收到I/O操作后就立即执行,而是交由I/O调度层,根据不同的算法,对请求进行合并和排序。
  • 块设备驱动层。 块设备驱动层根据具体的物理设备,选择对应的驱动程序操控硬件设备完成最终的I/O请求。

Android I/O

闪存是手机常用的存储设备,Android手机前几年通常使用eMMC标准,近年来通常会使用性能更好的UFS2.0/2.1标准。

1、文件为什么会损坏:

一个文件的格式或者内容,如果没有按照应用程序写入时的结果都属于文件损坏。

  • 应用程序。大部分I/O方法都是非原子操作,文件的跨进程或多线程写入、使用一个已经关闭的文件描述符fd来操作文件,它们都有可能导致文件内容修改和删除。
  • 文件系统。文件系统把数据写入到页缓存,在合适的时机写入到磁盘,内核崩溃和断电就有可能导致写入到磁盘异常和没有写入。
  • 磁盘。磁盘属于电子设备,内容在传输过程中存在错误的可能,同时闪存的寿命也可能导致文件错误。

2、I/O为什么会突然变慢:

  • 内存不足,当手机内存不足时,系统会回收Page Cache和Buffer Cache的内存,大部分的写操作会直接落盘,导致性能低下。
  • 写入放大,闪存重复写入需要先擦除操作,这个擦除操作是以block块为基本单元的,一个page的写入操作会引起整个块数据迁移。低端机或者使用很久的设备,由于磁盘碎片多、剩余空间少,非常容易出现写入放大的现象。
  • 低端机的CPU和闪存性能相对较差,在高负载的情况下容易出现瓶颈。
    系统为了缓解磁盘碎片问题,可以引入fstrim/TRIM机制,在锁屏、充电等一些时机会触发磁盘碎片整理。

I/O的性能评估

1、I/O性能指标

最核心的指标为吞吐量和IOPS。吞吐量是指单位时间的数据读取或写入峰值,IOPS指的是每秒可以读写的次数。

2、I/O测量

  • 使用proc
  • 使用strace,可以跟踪I/O相关的系统调用次数和耗时
  • 使用vmstat

I/O的三种方式

1、标准I/O

应用程序平时用到的read/write操作都属于标准I/O,也就是缓存I/O(Buffered I/O)。其特点为:

  • 对于读操作来说,当应用程序读取到某块数据时,如果这块数据已经存放在页缓存中,那么这块数据之间返回,不需要实际的物理操作。
  • 对于写操作来书,应用程序也会将数据写到页缓存中去,数据是否被立即写到磁盘上取决于应用程序所采用的写操作机制。默认系统采用延迟写机制,应用程序只需写入页缓存,系统负责定期写入到磁盘。
    Page Cache中被修改的内存称为“脏页”,内核通过flush线程定期将数据写入磁盘,具体写入的条件我们可以通过/proc/sys/vm文件或sysctl -a | grep vm命令得到。如果某些数据非常重要,不允许出现丢失的风险,这个时候可以采用同步写机制,在应用程序中使用sync、fsync、msync等系统调用时,内核都会将相应的数据写回到磁盘。


2、直接I/O


直接I/O访问文件方式减少了一次数据拷贝和一些系统调用时间,很大程度上降低了CPU的使用率以及内存的占用。但是,直接I/O有时候也会对性能产生不良影响:

  • 对于读操作来说,读数据操作会造成磁盘同步读,导致进程需要较长时间才能执行完;
  • 对于写操作来说,使用直接I/O也需要同步执行,也会导致程序等待。

3、mmap

它是通过吧文件映射到进程的地址空间,带来的好处有:

  • 减少系统调用。只需一次mmap()系统调用,后续所有调用像操作内存一样,而不会出现大量的read/write系统调用。
  • 减少数据拷贝。普通的read()操作需要经过两次拷贝,而mmap只需要从磁盘拷贝一次,并且由于做过内存映射,也不需要再拷贝回用户空间。
  • 可靠性高。mmap把数据写入缓存,跟缓存I/O的延迟写机制是一样的,可以依靠内核线程定期写回磁盘。但是在内核崩溃或断电的时候,同样可能引起内容丢失,也可以使用msync来强制同步写。



    mmap同样也存在缺点:

  • 虚拟内存增大。mmap会导致虚拟内存增大,mmap一个大文件,有可能出现虚拟内存不足而导致OOM。
  • 磁盘延迟。mmap通过缺页中断向磁盘发起真正的磁盘I/O,所以如果当前的问题在于磁盘I/O的高延迟,那么用mmap()消除小小的系统调用开销是杯水车薪的。类重排技术,就是将Dex中类按照启动顺序重新排列,主要为了减少缺页中断造成的磁盘I/O延迟。
    mmap比较适合对同一块区域频繁读写的情况,用户日志、数据上报都满足这种场景,另外跨进程通讯的时候也是不错的选择。Android跨进程通讯的Binder机制,其内部实现就是采用的mmap实现。
    利用mmap,Binder在跨进程通讯只需要一次数据拷贝,比传统的Socket、管道等跨进程通讯方式会少一次数据拷贝。


多线程阻塞I/O和NIO

1、多线程阻塞I/O

文件读写受到I/O性能瓶颈的影响,在到达一定速度后整体性能就会受到明显影响,过多的线程反而导致应用整体性能明显下降。

2、NIO


非阻塞NIO将I/O以事件的方式通知,的确可以减少线程切换的开销。其缺点是导致应用程序实现变得更复杂。其实NIO最大作用不是减少读取文件的耗时,而是最大化提升应用整体的CPU利用率。在CPU繁忙地时候,我们可以将线程等待磁盘I/O的时间来做部分CPU操作。

I/O跟踪

1、Java Hook

出于稳定性的考虑,采用Java Hook的方案,通过动态代理的方式,在所有I/O相关方法前后加入插装代码,统计I/O操作相关信息。这个方法存在下列缺点:

  • 性能极差。
  • 无法监控Native代码。
  • 兼容性差。

2、Native Hook

Profilo使用PLT Hook方案,它的性能比GOT Hook要稍好些,不过GOT Hook的兼容性更好一些。

3、监控内容

线上监控

通过Native Hook方案可以采集到所有I/O相关的信息,对于I/O的线上监控,我们需要进一步抽象出规则,明确哪些情况可以定义为不良情况,需要上报到后台,进而推动开发去解决。


1、主线程I/O

有时候I/O的写入会突然放大,所以尽量不要在主线程上操作,线上也经常发现一些I/O操作明明数据量不大,但是最后还是出现ANR。如果将主线程的所有I/O都收集起来,数据量会非常大,所以可以加上“连续读写时间超过100毫秒”这样的条件,之所以连续读写,是因为不少情况这是打开了文件句柄,但不是一次读写完的。

2、读写Buffer过小

文件系统读写是以Block为单位的,对于磁盘是以Page为单位读写,如果我们的Buffer太小会导致对此无用的系统调用和内存拷贝,导致read/write次数增多,从而影响性能。Buffer的大小对文件的读写的耗时有非常大的影响,耗时减小主要得益于系统图调用于内存拷贝的优化,Buffer的大小一般推荐使用4KB以上。

3、重复读

如果频繁的读取某个文件,并且这个文件一直没有被写入跟新,我们可以通过缓存来提升性能,不过未来减少上报量,通常增加几个条件:

  • 重复读取次数超过3次,并且读取的内容相同。
  • 读取期间文件内容没有被更新,也没有发生过write

4、资源泄漏

资源泄漏是指打开资源包括文件、cursor等没有及时关闭,从而引起的泄漏。利用Android 框架中的StrictMode实现监控资源泄漏,StrictMode利用CloseGuard.java类在很多系统代码已经预制了埋点,我们可以增加更多的埋点,据图步骤如下:

  • 利用反射,吧CloseGuard中的ENABLED的值为true。
  • 利用动态代理,把REPORTER替换成我们定义的proxy。

I/O启动优化

  • 对大文件使用mmap或者NIO方式。
  • 安装包不压缩。对启动过程需要的文件,指定不压缩,这样会加快启动速度,但会带来安装包体积增大的问题。
  • Buffer复用。重用技巧,如Okio很大程度上减少CPU和内存的消耗。
  • 存储结构和算法优化。从存储结构和算法优化方面减少甚至去除I/O操作。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 文件系统优化 ** 动态调整请求队列数来提高效率,默认请求队列数为:128, 可配置512 **[root@c37...
    肖金光xjg阅读 4,780评论 1 8
  • Bitmap decode BitmapFactory.java提供多个decode Bitmap的API,有de...
    developerChenxi阅读 3,830评论 0 0
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,813评论 0 5
  • I/O 操作是编程离不开的话题,它不仅是读写那么简单,还涉及底层的文件系统和存储设备。I/O 的快慢影响程序的执行...
    落英坠露阅读 2,941评论 0 3
  • 今天看到一位朋友写的mysql笔记总结,觉得写的很详细很用心,这里转载一下,供大家参考下,也希望大家能关注他原文地...
    信仰与初衷阅读 4,723评论 0 30