[性能优化]深度优化DateFormatter

2023-10-19 11:49

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

本文已收录到1.1K Star数开源学习指南——《大厂面试指北》,如果想要了解更多大厂面试相关的内容及获取《大厂面试指北》离线PDF版,请扫描下方二维码码关注公众号“大厂面试”,谢谢大家了!

《大厂面试指北》最佳阅读地址:

http://notfound9.github.io/interviewGuide/

《大厂面试指北》项目地址:

https://github.com/NotFound9/interviewGuide

获取《大厂面试指北》离线PDF版,请扫描下方二维码关注公众号“大厂面试”

《大厂面试指北》项目截图:
在这里插入图片描述

前言

在iOS开发中,对日期进行格式化处理通常有三个步骤:

  • 创建DateFormatter对象
  • 设置日期格式
  • 使用DateFormatter对象对日期进行处理

在上篇文章《DateFormatter性能优化》中,我们通过创建单例对象的方式对创建DateFormatter对象,设置日期格式两个步骤进行了缓存,将方法耗时降低为不缓存的方案的10%左右,但是这种优化方法受制于DateFormatter的几个系统方法的执行效率,本身具有一定的局限性。之前在一些文章中,也看到了使用C语言的

size_t	 strftime_l(char * __restrict, size_t, const char * __restrict,const struct tm * __restrict, locale_t)__DARWIN_ALIAS(strftime_l) __strftimelike(3);

函数对日期格式化进行处理,所以本文将对以下几种情况的方法耗时进行评测:

  • 使用Objective-C,不缓存DateFormatter对象
  • 使用Objective-C,缓存DateFormatter对象
  • 使用Objective-C,调用strftime_l做日期处理
  • 使用Swift,不缓存DateFormatter对象
  • 使用Swift,缓存DateFormatter对象
  • 使用Swift,调用strftime_l做日期处理

Objective-C的三种情况下的代码


//不缓存DateFormatter对象
-(void)testDateFormatterInOCWithoutCache:(NSInteger)times {NSString *string = @"";NSDate *date;CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();for (int i=0; i<times; i++) {NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];[dateFormatter setDateFormat:@"yyyy年MM月dd日HH时mm分ss秒"];date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)];string = [dateFormatter stringFromDate:date];}CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;NSLog(@"\n不缓存DateFormatter对象的方案:\n计算%ld次\n耗时%f ms\n", (long)times, duration);
}//缓存DateFormatter对象
-(void)testDateFormatterInOCWithCache:(NSInteger)times {NSString *string = @"";NSDate *date;CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();for (int i=0; i<times; i++) {date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)];string = [[DateFormatterCache shareInstance].formatterOne stringFromDate:date];}CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;NSLog(@"\n缓存DateFormatter对象的方案:\n计算%ld次\n耗时%f ms\n", (long)times, duration);
}//使用C语言来做日期处理
-(void)testDateFormatterInC:(NSInteger)times {NSString *string = @"";NSDate *date;time_t timeInterval;char buffer[80];CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();for (int i=0; i<times; i++) {date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)];timeInterval = [date timeIntervalSince1970];strftime(buffer, sizeof(buffer), "%Y年%m月%d日%H时%M分%S秒", localtime(&timeInterval));string = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];}CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;NSLog(@"==%@", string);NSLog(@"\n使用C语言的方案:\n计算%ld次\n耗时%f ms\n", (long)times, duration);
}

这里对于咱们iOS开发的同学来说比较陌生的就是
strftime_l(buffer, sizeof(buffer), "%Y年%m月%d日%H时%M分%S秒", localtime(&timeInterval), NULL);这这行代码的调用,strftime_l函数接受四个参数,第一个参数buffer是C语言中字符数组用于存储日期格式化后的字符串,第二个参数是写入buffer数组的最大值,如果格式化的字符串大于这个值,那么只会取字符串的的一部分,第三个参数"%Y年%m月%d日%H时%M分%S秒"是日期格式,第四个参数localtime(&timeInterval)是指向使用当地时区对时间戳处理得到tm类型结构体的指针

附上tm结构体:

struct tm {int	tm_sec;		/* seconds after the minute [0-60] */int	tm_min;		/* minutes after the hour [0-59] */int	tm_hour;	/* hours since midnight [0-23] */int	tm_mday;	/* day of the month [1-31] */int	tm_mon;		/* months since January [0-11] */int	tm_year;	/* years since 1900 */int	tm_wday;	/* days since Sunday [0-6] */int	tm_yday;	/* days since January 1 [0-365] */int	tm_isdst;	/* Daylight Savings Time flag */long	tm_gmtoff;	/* offset from UTC in seconds */char	*tm_zone;	/* timezone abbreviation */
};

Swift三种情况下的代码

    //不进行缓存func testInOldWay(_ times: Int) {var string = ""var date = Date.init()let startTime = CFAbsoluteTimeGetCurrent();for i in 0..<times {let formatter = DateFormatter()formatter.dateFormat = "yyyy年MM月dd日HH时mm分ss秒"date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i))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 = ""var date = Date.init()let startTime = CFAbsoluteTimeGetCurrent();for i in 0..<times {date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i))string = DateFormatterCache.shared.dateFormatterOne.string(from: date)}let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;print("使用缓存Formatter的方案计算\n\(times)次,总耗时\n\(duration) ms\n")}//使用C语言来做日期处理func testFormatterInC(_ times: Int) {var date = Date.init()var dateString = ""var buffer = [Int8](repeating: 0, count: 100)var time = time_t(date.timeIntervalSince1970)let format = "%Y年%m月%d日%H时%M分%S秒"let startTime = CFAbsoluteTimeGetCurrent();for i in 0..<times {date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i))time = time_t(date.timeIntervalSince1970)strftime(&buffer, buffer.count, format, localtime(&time))dateString = String.init(cString: buffer, encoding: String.Encoding.utf8) ?? ""}let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;print("使用C语言的方案计算\n\(times)次,总耗时\n\(duration) ms\n")print(dateString)}

iOS 12.1 iPhone 7

测试结果:

image

测试结果:

在Objective-C中,
不使用缓存,使用缓存,使用C语言函数处理的耗时比约为100:10.7:3.5

在Swift中,
不使用缓存,使用缓存,使用C语言函数处理的耗时比约为100:11.7:6.6

Swift在使用DateFormatter进行处理时,不论是缓存的方案还是不缓存的方案,跟使用Objective-C的耗时基本一致,而在Swift中使用C语言的函数来做日期处理时,时间约为使用Objective-C的两倍,而且当只做一次日期处理时,由于涉及到一些初始资源的初始化,所以看上去比后面执行10次的时间还多

最后

如果项目是Objective-C的项目,我觉得可以采用这种C语言的strftime来做日期处理,能将时间降低为缓存NSDateFormatter的方案的33%左右,如果是Swift项目,调用C语言函数的效率没有在Objective-C项目中那么高,虽然能将时间降低为缓存NSDateFormatter的方案的56%左右,但是在Swift中使用C语言的函数存在一定的风险,在这里风险之一就是time = time_t(date.timeIntervalSince1970)这行代码返回的值是time_t类型,time_t类型的定义如下:

public typealias time_t = __darwin_time_t
public typealias __darwin_time_t = Int /* time() */

time_t其实就是Int,当Swift项目运行在32位设备(也就是iphone 5,iphone 5C)上时,Int类型是32位的,最大值为2147483647,如果这是一个时间戳的值,转换为正常时间是2038-01-19 11:14:07,也就是处理的时间是未来的日期,2038年以后的话,会出现数值溢出。

Demo在这里:
https://github.com/577528249/SwiftDemo

参考资料:

https://forums.developer.apple.com/thread/29054
https://stackoverflow.com/questions/24255020/swift-nsdate-formatting-with-strftime-localtime

PS:

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

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

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

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

群已经满100人了,想要加群的小伙伴们可以扫码加这个微信,备注:“加群+昵称”,拉你进群,谢谢了
image

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



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

相关文章

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

Spring Boot基于 JWT 优化 Spring Security 无状态登录实战指南

《SpringBoot基于JWT优化SpringSecurity无状态登录实战指南》本文介绍如何使用JWT优化SpringSecurity实现无状态登录,提高接口安全性,并通过实际操作步骤... 目录Spring Boot 实战:基于 JWT 优化 Spring Security 无状态登录一、先搞懂:为什

SQL 注入攻击(SQL Injection)原理、利用方式与防御策略深度解析

《SQL注入攻击(SQLInjection)原理、利用方式与防御策略深度解析》本文将从SQL注入的基本原理、攻击方式、常见利用手法,到企业级防御方案进行全面讲解,以帮助开发者和安全人员更系统地理解... 目录一、前言二、SQL 注入攻击的基本概念三、SQL 注入常见类型分析1. 基于错误回显的注入(Erro

CPython与PyPy解释器架构的性能测试结果对比

《CPython与PyPy解释器架构的性能测试结果对比》Python解释器的选择对应用程序性能有着决定性影响,CPython以其稳定性和丰富的生态系统著称;而PyPy作为基于JIT(即时编译)技术的替... 目录引言python解释器架构概述CPython架构解析PyPy架构解析架构对比可视化性能基准测试测

Java JAR 启动内存参数配置指南(从基础设置到性能优化)

《JavaJAR启动内存参数配置指南(从基础设置到性能优化)》在启动Java可执行JAR文件时,合理配置JVM内存参数是保障应用稳定性和性能的关键,本文将系统讲解如何通过命令行参数、环境变量等方式... 目录一、核心内存参数详解1.1 堆内存配置1.2 元空间配置(MetASPace)1.3 线程栈配置1.

Java枚举类型深度详解

《Java枚举类型深度详解》Java的枚举类型(enum)是一种强大的工具,它不仅可以让你的代码更简洁、可读,而且通过类型安全、常量集合、方法重写和接口实现等特性,使得枚举在很多场景下都非常有用,本文... 目录前言1. enum关键字的使用:定义枚举类型什么是枚举类型?如何定义枚举类型?使用枚举类型:2.

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4