MetricKit框架详细解析(八) —— Reducing Disk Writes(一)

版本记录

版本号 时间
V1.0 2021.05.16 星期日

前言

MetricKit由iOS13系统进引入,用来汇总和分析有关异常和崩溃诊断以及电源和性能指标的每个设备的报告。下面我们就一起来看下这个框架。感兴趣的可以看下面几篇文章。
1. MetricKit框架详细解析(一) —— 基本概览(一)
2. MetricKit框架详细解析(二) —— Improving Your App's Performance(一)
3. MetricKit框架详细解析(三) —— Reducing Your App's Memory Use(一)
4. MetricKit框架详细解析(四) —— Gathering Information About Memory Use(一)
5. MetricKit框架详细解析(五) —— Making Changes to Reduce Memory Use(一)
6. MetricKit框架详细解析(六) —— Preventing Memory-Use Regressions & Responding to Low-Memory Warnings(一)
7. MetricKit框架详细解析(七) —— Reducing Your App's Launch Time(一)

Overview

通过减少应用程序写入磁盘的数据量来提高应用程序的响应速度。

固态存储驱动器(Solid-State storage drives - SSD)是每台iOS设备中安装的永久存储技术。 从SSD访问数据比从RAM访问数据要慢得多。 大量使用磁盘存储来访问其数据的应用可能会降低性能。

交错写入磁盘和从磁盘读取会增加读取操作的等待时间。 写入SSD的操作比读取操作要慢,并且磁盘必须完成写入操作才能满足未完成的读取数据请求。 驱动器将请求排队,以等待挂起的写入之后读取数据,从而延长了应用程序等待数据的时间。 iOS在等待数据时阻止了请求线程。

在磁盘磨损之前,iOS只能在有限次数内写入SSD的相同区域。 应用写的内容越多,驱动器的磨损就越快。

使用XcodeInstruments来了解您的应用程序的磁盘写入性能,并找出机会来提高响应速度并减少磁盘磨损。


View and Fix Disk-Write Exceptions

当在短时间内写入过多磁盘时,系统将引发异常。 在Xcode Organizer窗口的Disk Writes报表窗格中查看该应用程序的聚合异常日志,或使用MetricKit捕获它们。

Report List中的每个报告都显示生成异常的函数调用,以及该报告占磁盘写入总数的百分比。单击报告会显示示例堆栈跟踪以及Inspector中的其他详细信息,包括iOS软件版本,设备型号,总写入数,收到的日志数以及14天的报告趋势。

使用磁盘写操作的总百分比以及有关受影响的操作系统和设备的信息来选择要修复的异常。将函数签名用于Report List中的特定报告以及相应的堆栈跟踪,以标识导致写入增加的代码。修改代码后,将报告标记为已解决。


Gather Metrics About Your Apps Disk Usage

Xcode Organizer窗口的Disk Writes指标窗格中或使用MetricKit查看您的应用每天写入磁盘的数据量。

此窗格显示应用程序的发行版本逻辑上每天的磁盘写入量。比较版本以发现意外增加。使用过滤来查找设备之间的差异,并查看典型的写入数据量(第50个百分点)或最大写入数据量(第90个百分点)。 MetricKit报告相同的数据。

下面的屏幕快照显示,最新版本的Fruta写入的最大数据量比早期版本多26.4 MB /天。

评估记录的数据量对于您的应用程序是否合理。如果数字远远超出您的预期,则可能是因为写入数据的频率太高了。例如,如果您的应用程序文件总数为100KB,而您的应用程序每天将500MB数据写入磁盘,则可能需要调查每天将相同数据写入磁盘的次数。

使用各个版本的磁盘写入频率图来确定磁盘使用趋势。每天写入量增加的应用可能合法地在处理更多数据,或者可能无法有效地处理其已拥有的数据。峰值可能表示用户为您的应用程序创建或下载了新内容,或者可能意味着您的应用程序比平常修改了相同的内容。 Dips可以帮助您确定应用程序实际需要的最小数据子集。


Identify the Code Causing Significant Disk Writes

使用File Activity模板在Instruments中为您的应用配置文件。Instruments会跟踪您的应用的逻辑和对存储的物理使用。

Filesystem Activity instrument以系统调用的形式记录逻辑文件系统的使用,以读取,写入文件或将文件系统数据映射到内存中。 Instruments将每个事件与其大小,持续时间和backtrace关联起来,您可以使用它们来确定应用程序代码中负责文件系统使用的位置。 Disk Usage and Disk I/O Latency工具显示了这些文件系统事件如何导致物理使用存储介质,记录了磁盘读写的大小和延迟。

注意:磁盘上物理读写的大小与逻辑文件系统活动的大小无关。 磁盘控制器使用称为块(blocks)的区域,通常大小为4kb。 物理写入磁盘将替换整个块中的内容,因此,即使更改文件中的单个字节也意味着将4kb物理写入磁盘。

Filesystem Suggestion工具还可以识别文件系统使用中的常见问题,并提供解决方法。


Batch Multiple Write Operations

与不经常打开文件进行多次更改相比,反复打开和关闭同一文件可以增加写入磁盘的频率。从磁盘使用的角度来看,最好将文件上的尽可能多的小量的写操作收集在一起,然后将它们作为一个大批(batch)执行。但是,建立大量要写入的数据会增加应用程序在内存中的大小。设计应用程序的持久性模型,以有效平衡其对这两种资源的使用。请参阅Reducing Your App's Memory Use。


Use Core Data or SQLite Databases for Frequently Changing Documents

SQLite经过高度优化,可以有效访问存储。 SQLite数据库连接充分利用了内存中的缓存和对磁盘的批量写入,以确保高性能和最小的设备磨损。它使用旨在当应用程序插入新内容或更新现有内容时进行有效更新的数据结构。Core Data充分利用了SQLite高效的磁盘使用模式,并符合以下所述的最佳做法。

过去,许多应用程序都使用property list,JSON,XML或其他序列化格式来编写用户文档。这些格式适用于bundle元数据等只读内容,也适用于通过网络进行传输,但不是存储用户文档的最佳选择。更改序列化的文档需要重写整个文件,这增加了操作的延迟和设备的磨损。因此,SQLite是存储用户编辑的文档的更好选择。


Adopt SQLite Best Practices

为了充分利用SQLite的效率,您的应用程序应尽可能保持与数据库的连接处于打开状态,只有在明确需要时才关闭连接。打开和关闭SQLite连接是昂贵的操作,它迫使SQLite写出所有未决的更改以及包括一致性检查和日志记录日志在内的其他元数据。

汇总中的多个INSERT,UPDATEDELETE语句。当所有更改都发生在同一事务中时,SQLite会将对同一页面的多个更改合并到一个写操作中。随着时间的推移发生多个逻辑相关的更改,例如,当您的应用在单个文档中编辑多个字段时,请在事务中汇总更改。使用事务提供了原子操作带来的额外好处。 SQLite会提交所有更改,或者将数据库回滚到事务处理之前的状态。无论哪种情况,SQLite都不会使数据库处于中间状态(可能不一致)。

在数据库表上使用适当的索引,以减少搜索所需的时间并避免对磁盘进行不必要的写入。要了解这有何帮助,请考虑一个以SQLite格式存储邮件的电子邮件应用程序。要按时间顺序显示所有收件箱消息,应用程序将运行查询“ SELECT * FROM messages WHERE folder like'Inbox'ORDER BY sent_time”。有了send_time的索引,SQLite会按顺序读取消息并返回匹配的行。没有索引,SQLite必须在内存中创建一个临时的B树并读取整个表,然后对该B树执行排序。如果此B树对于内存高速缓存太大,则SQLite会将其写入磁盘,从而减慢SELECT查询的速度。

对于不考虑表中所有行的查询(例如,忽略其中特定列包含NULL的行),部分索引(即带有WHERE子句的索引)是最佳选择。部分索引比完整索引占用更少的磁盘空间,同时仍然为可以使用索引的查询提供性能优势。

使用预写日志记录(WAL)模式日志记录可以合并对同一页面的多次写入,减少SQLite对写入屏障的使用,并支持与写入器并行的多个数据库读取线程。

不要发出明确的VACUUM查询来告诉SQLite何时从其空闲列表中回收页面。将auto_vacuum()pragma设置为2,并使用incremental_vacuum()pragma从可用列表中删除页面(如果存在的话)。

使用SQLite持久存储时,Core Data会实现所有这些最佳实践。


Avoid Rapid File Creation and Deletion

创建文件时,除了文件的大小外,还引入了8kb的元数据写操作,因为iOS更新了包含目录以包含对该文件的引用。从该文件的包含目录中删除该文件时,删除该文件也将产生此消耗。快速创建和删除文件会对文件系统进行许多小的更改,从而降低性能并增加设备的磨损。

重命名或移动文件在更新文件系统元数据时最多需要写入16kb的内容。当使用Foundation中的任何atomic操作(在NSString,NSArray,NSDictionaryNSData上已弃用的write(to:atomically :)方法)创建带有文件中任何atomic操作的文件时,这种影响会增加到8kb的创建成本中。这些方法创建一个临时文件,写入内容,取消链接目标文件(如果存在),然后将临时文件重命名为其最终目标。用同一对象上的write(to :)方法替换对这些方法的调用。


Minimize Explicit Storage Synchronization

fsync(_ :)系统调用和fcntl(_:_ :)操作F_FULLFSYNC指示iOS将待处理的文件系统更改从其统一缓冲区高速缓存刷新到磁盘。执行完全同步会覆盖操作系统决定何时将更改写入存储的决定,这可能会导致操作系统写入次数超出了必要。

应用程序有时会将fsync用作barrier,因此iOS在完成后续文件系统更改之前,可以继续进行后续操作。在这些情况下,请对受影响的描述符使用F_BARRIERFSYNC fcntl操作。仅在对数据持久性有强烈期望的情况下,才使用F_FULLFSYNCF_FULLFSYNC表示尽最大努力保证iOS将数据写入磁盘,但是在突然断电的情况下仍然可能丢失数据。


Prevent Regressions in Disk-Write Frequency

您可以编写性能测试以使用XCTest测量代码的磁盘使用情况。创建一个测试,该测试将XCTStorageMetric的实例传递给measureWithMetrics:block:函数。在measure(metrics:block :)block参数中,调用将数据写入磁盘的代码。该测试将测量该block写入文件系统的数据量。您可以设置基线期望值,以便在写入的数据量大大超过基线的情况下测试失败。

func testDiskUse() {
  self.measure(metrics: [XCTStorageMetric()]) {
     // A disk-intensive operation
  }
}

后记

本篇主要讲述了Reducing Disk Writes,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(MetricKit框架详细解析(八) —— Reducing Disk Writes(一))