[性能优化]轻度优化DateFormatter

2023-10-19 11:49

本文主要是介绍[性能优化]轻度优化DateFormatter,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

建了一个面试题解答的项目,大家可以看一看,希望大家帮忙给一个star,谢谢了!
项目地址:https://github.com/NotFound9/interviewGuide

![image.png](https://upload-images.jianshu.io/upload_images/12609483-5982faaa963753bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


### 为什么写这篇文章
1.之前在一些性能优化的文章[《性能优化之NSDateFormatter
》](https://www.cnblogs.com/tingxins/p/6021614.html)中,看到有提到“创建DateFormatter开销会比较大”,也有的文章[《(多帖总结) iOS性能优化技巧》](http://www.devqinwei.com/2015/12/02/%E5%A4%9A%E8%B4%B4%E6%80%BB%E7%BB%93-ios%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7/)里面说是“设置日期格式”这个方法较为耗时,但实际上测试发现是“生成字符串”这个方法较为耗时,所以我觉得可以纠正一些这些说法
```
let formatter = DateFormatter()//创建DateFormatter实例对象
formatter.dateFormat = "yyyy年MM月dd日"//设置日期格式
string = formatter.string(from: date)//生成字符串
```
2.很多同学可能只是跟我之前一样,只是知道这个方法比较耗时,但是对于进行缓存优化后的效果对比并不清楚,所以自己写了一个小Demo,对优化前后进行一些性能测试,方便大家参考,也方便大家在项目中使用

### 运行时间对比
```
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        testInOldWay(1)
        testInNewWay(1)
        
        testInOldWay(10)
        testInNewWay(10)
        
        testInOldWay(100)
        testInNewWay(100)
        
        testInOldWay(1000)
        testInNewWay(1000)
        
        testInOldWay(10000)
        testInNewWay(10000)
        
        testInOldWay(100000)
        testInNewWay(100000)
        
        testInOldWay(1000000)
        testInNewWay(1000000)
    }
    //不进行缓存
    func testInOldWay(_ times: Int) {
        var string = ""
        let date = Date.init()
        let startTime = CFAbsoluteTimeGetCurrent();
        for _ in 0..<times {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy年MM月dd日"
            string = formatter.string(from: date)
        }
        let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
        print("使用oldWay计算\n\(times)次,总耗时\n\(duration) ms\n")
    }
    //进行缓存
    func testInNewWay(_ times: Int) {
        var string = ""
        let date = Date.init()
        let startTime = CFAbsoluteTimeGetCurrent();
        for _ in 0..<times {
            string = DateFormatterCache.shared.dateFormatterOne.string(from: date)
        }
        let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
        print("使用newWay计算\n\(times)次,总耗时\n\(duration) ms\n")
    }
}


//创建单例进行缓存
class DateFormatterCache {
    //使用方法
    //let timeStr = DateFormatterCache.shared.dateFormatterOne.string(from: publishTime)
    static let shared = DateFormatterCache.init()
    
    lazy var dateFormatterOne: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy年MM月dd日"
        return formatter
    }()
    lazy var dateFormatterTwo: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .full
        formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z"
        formatter.locale = Locale.init(identifier: "en_US")
        return formatter
    }()
}

```
#### 日志输出
```
使用oldWay计算
1次,总耗时
7.187008857727051 ms

使用newWay计算
1次,总耗时
0.1609325408935547 ms

使用oldWay计算
10次,总耗时
0.552058219909668 ms

使用newWay计算
10次,总耗时
0.05888938903808594 ms

使用oldWay计算
100次,总耗时
4.320979118347168 ms

使用newWay计算
100次,总耗时
0.6080865859985352 ms

使用oldWay计算
1000次,总耗时
47.60599136352539 ms

使用newWay计算
1000次,总耗时
5.526900291442871 ms

使用oldWay计算
10000次,总耗时
427.8249740600586 ms

使用newWay计算
10000次,总耗时
45.81403732299805 ms

使用oldWay计算
100000次,总耗时
4123.620986938477 ms

使用newWay计算
100000次,总耗时
459.98501777648926 ms

使用oldWay计算
1000000次,总耗时
40522.77398109436 ms

使用newWay计算
1000000次,总耗时
4625.54395198822 ms
```
执行时间统计:
![image](http://upload-images.jianshu.io/upload_images/12609483-8afc48ed72a8ddd3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

在测试中,我们发现执行一次formatter的创建和设置日期格式需要7.187008857727051 ms,执行10次却只需要0.552058219909668 ms,这是因为第一次执行
``let formatter = DateFormatter()``这行代码时可能会涉及到DateFormatter类相关的一些初始资源的初始化,而后续执行十次时已经不包含这一过程所需要的耗时,所以看上去执行一次的时间反而长一些,我们在计算性能比较时可以通过增加执行次数,来忽略这些因素的影响,当我们执行1000000次时,不进行缓存使用oldWay计算需要40522.77398109436 ms,而一次初始化的开销最大为第一次的执行的耗时7.187008857727051 ms,
```
7.18/40522.77 = 0.0177%
```
占比为0.0177,这些因素的影响已经降低为万分之一了,所以我们可以将执行1000000次时,不使用缓存和使用缓存的执行一次所需平均时间方法耗时
``` 
不使用缓存(oldWay,每次创建DateFormatter对象并且设置格式)
执行一次耗时:40.52 us
使用缓存(oldWay,每次创建DateFormatter对象并且设置格式)
执行一次耗时:4.625 us

使用缓存的方案的执行时间大概是不使用缓存的方案的时间的11.4%
```
### 究竟是创建DateFormatter对象耗时还是设置日期格式耗时?

```
 func testPartInOldWay(_ times: Int) {
        var string = ""
        let date = Date.init()
        var startTime1: CFAbsoluteTime = 0;
        var startTime2: CFAbsoluteTime = 0;
        var startTime3: CFAbsoluteTime = 0;
        var startTime4: CFAbsoluteTime = 0;

        var duration1: CFAbsoluteTime = 0;
        var duration2: CFAbsoluteTime = 0;
        var duration3: CFAbsoluteTime = 0;

        for i in 0..<times {
            startTime1 = CFAbsoluteTimeGetCurrent();
            let formatter = DateFormatter()
            startTime2 = CFAbsoluteTimeGetCurrent();
            formatter.dateFormat = "yyyy年MM月dd日"
            startTime3 = CFAbsoluteTimeGetCurrent();
            string = formatter.string(from: date)
            startTime4 = CFAbsoluteTimeGetCurrent();
            
            duration1 += (startTime2 - startTime1) * 1000.0;
            duration2 += (startTime3 - startTime2) * 1000.0;
            duration3 += (startTime4 - startTime3) * 1000.0;
        }
        print("创建DateFormatter对象耗时=\(duration1)ms\n设置日期格式耗时=\(duration2)ms\n生成字符串耗时=\(duration3)ms\n\n")
    }

```

#### 输出结果:
```
执行1次
创建DateFormatter对象耗时=0.030994415283203125ms
设置日期格式耗时=0.3859996795654297ms
生成字符串耗时=1.6570091247558594ms

执行10次
创建DateFormatter对象耗时=0.019073486328125ms
设置日期格式耗时=0.012159347534179688ms
生成字符串耗时=0.5759000778198242ms

执行100次
创建DateFormatter对象耗时=0.0768899917602539ms
设置日期格式耗时=0.06973743438720703ms
生成字符串耗时=4.322528839111328ms

执行1000次
创建DateFormatter对象耗时=0.7123947143554688ms
设置日期格式耗时=0.702977180480957ms
生成字符串耗时=41.77117347717285ms

执行10000次
创建DateFormatter对象耗时=6.549596786499023ms
设置日期格式耗时=5.913138389587402ms
生成字符串耗时=370.6216812133789ms

执行100000次
创建DateFormatter对象耗时=65.13833999633789ms
设置日期格式耗时=59.78119373321533ms
生成字符串耗时=3586.0002040863037ms

执行1000000次
创建DateFormatter对象耗时=661.7592573165894ms
设置日期格式耗时=575.5696296691895ms
生成字符串耗时=35309.07988548279ms
```
可以从输出结果中发现是``string = formatter.string(from: date)``这行代码耗费时间最多,所以主要耗时并不在于执行DateFormatter.init()和formatter.dateFormat = "yyyy年MM月dd日",在对我们项目使用Instrument进行分析时,测试结果也证明了这一点


测试环境:iPhone 7

测试系统:iOS 12.1(16B92)


![image](http://upload-images.jianshu.io/upload_images/12609483-b296ea05905150f4?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![image](http://upload-images.jianshu.io/upload_images/12609483-45ad32499ae01ffe?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

app启动后的60s内,快速滑动feed流页面,在这一过程中,主线程的执行时间大概是10.59s,我们项目中日期处理主要在``func detailString(date: Date) -> String``这个方法中进行,这个方法的运行时间为730ms,而其中            ``timeStr = formatter.string(from: date)``这行代码的运行时间为628ms,所以也说明了生成日期字符串的方法耗时较多。

### 在项目中的实际提升
测试环境:iPhone 7

测试系统:iOS 12.1(16B92)

测试时间:app启动后的60s

测试步骤:使用Instruments的Time Profiler启动app,在启动后的60s内,快速滑动列表页。


#### 没有对DateFormatter进行缓存时:

在我们项目中,detailString方法每次调用时会创建DateFormatter,生成日期字符串

```
            let formatter = DateFormatter()
            formatter.dateFormat = "MM月dd日"
            timeStr = formatter.string(from: date)

```
测试结果:
![image](http://upload-images.jianshu.io/upload_images/12609483-0caad24f1465c94b?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


app启动后的60s内,主线程执行时间10.59s,detailString的执行730ms

#### 对DateFormatter进行缓存后:
```
    timeStr = DateFormatterCache.shared.dateFormatterOne.string(from: date)
    class DateFormatterCache {
        //使用方法
        //let timeStr = DateFormatterCache.shared.dateFormatterOne.string(from: publishTime)
        static let shared = DateFormatterCache.init()
        
        lazy var dateFormatterOne: DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateFormat = "MM月dd日"
            return formatter
    }()

```
我们通过DateFormatterCache的单例对象shared来获取dateFormatterOne

测试结果:

![image](http://upload-images.jianshu.io/upload_images/12609483-b9746c460946d9bc?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
app启动后的60s内,主线程执行时间10.58s,detailString的执行76ms

从执行时间上对比,缓存后,执行时间是之前的10.4%,对性能的提升还是比较大的

### 最后
Demo在这里https://github.com/577528249/SwiftDemo/tree/master/DateFormatterDemo


最近加了一些iOS开发相关的QQ群和微信群,但是感觉都比较水,里面对于技术的讨论比较少,所以自己建了一个iOS开发进阶讨论群,欢迎对技术有热情的同学扫码加入,加入以后你可以得到:

1.技术方案的讨论,会有在大厂工作的高级开发工程师尽可能抽出时间给大家解答问题

2.每周定期会写一些文章,并且转发到群里,大家一起讨论,也鼓励加入的同学积极得写技术文章,提升自己的技术

3.如果有想进大厂的同学,里面的高级开发工程师也可以给大家内推,并且针对性得给出一些**面试建议**

群已经满100人了,想要加群的小伙伴们可以扫码加这个微信,备注:“加群+昵称”,拉你进群,谢谢了
![image](http://upload-images.jianshu.io/upload_images/12609483-6967093918715b62?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这篇关于[性能优化]轻度优化DateFormatter的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/239565

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

黑神话,XSKY 星飞全闪单卷性能突破310万

当下,云计算仍然是企业主要的基础架构,随着关键业务的逐步虚拟化和云化,对于块存储的性能要求也日益提高。企业对于低延迟、高稳定性的存储解决方案的需求日益迫切。为了满足这些日益增长的 IO 密集型应用场景,众多云服务提供商正在不断推陈出新,推出具有更低时延和更高 IOPS 性能的云硬盘产品。 8 月 22 日 2024 DTCC 大会上(第十五届中国数据库技术大会),XSKY星辰天合正式公布了基于星

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

PR曲线——一个更敏感的性能评估工具

在不均衡数据集的情况下,精确率-召回率(Precision-Recall, PR)曲线是一种非常有用的工具,因为它提供了比传统的ROC曲线更准确的性能评估。以下是PR曲线在不均衡数据情况下的一些作用: 关注少数类:在不均衡数据集中,少数类的样本数量远少于多数类。PR曲线通过关注少数类(通常是正类)的性能来弥补这一点,因为它直接评估模型在识别正类方面的能力。 精确率与召回率的平衡:精确率(Pr