Optimization DateTime to string,用F#表示,是否可以进一步优化?
我有一个系统,可以进行一些模拟,并输出大量带有时间戳的日志信息 我很惊讶地看到DateTime.ToString()在循环中非常昂贵(它的调用非常多),所以我尝试制作一个更快的版本来输出日期、时间和毫秒 这可以更快地完成吗(输出需要毫秒)?我没有尝试使用指针,因为我假设(可能是错误的)对于这样一小段代码,固定对象的开销会更高Optimization DateTime to string,用F#表示,是否可以进一步优化?,optimization,f#,Optimization,F#,我有一个系统,可以进行一些模拟,并输出大量带有时间戳的日志信息 我很惊讶地看到DateTime.ToString()在循环中非常昂贵(它的调用非常多),所以我尝试制作一个更快的版本来输出日期、时间和毫秒 这可以更快地完成吗(输出需要毫秒)?我没有尝试使用指针,因为我假设(可能是错误的)对于这样一小段代码,固定对象的开销会更高 module DateTimeFormatter = let inline private valueToDigit (value: int) : char =
module DateTimeFormatter =
let inline private valueToDigit (value: int) : char =
char (value + int '0')
let inline private write2Characters (c: char[]) offset value =
c.[offset + 0] <- valueToDigit (value / 10)
c.[offset + 1] <- valueToDigit (value % 10)
let inline private write3Characters (c: char[]) offset value =
c.[offset + 0] <- valueToDigit (value / 100)
c.[offset + 1] <- valueToDigit ((value % 100) / 10)
c.[offset + 2] <- valueToDigit (value % 10)
let format (dateTime: DateTime) =
let c = Array.zeroCreate<char> 23
write2Characters c 0 (dateTime.Year / 100)
write2Characters c 2 (dateTime.Year % 100)
c.[4] <- '-'
write2Characters c 5 dateTime.Month
c.[7] <- '-'
write2Characters c 8 dateTime.Day
c.[10] <- ' '
write2Characters c 11 dateTime.Hour
c.[13] <- ':'
write2Characters c 14 dateTime.Minute
c.[16] <- ':'
write2Characters c 17 dateTime.Second
c.[19] <- '.'
write3Characters c 20 dateTime.Millisecond
new string(c)
以及测试结果(MBP i7 2019):
注释中已经提到重用数组而不是重写常量字符。进一步考虑:
- 这里使用
关键字似乎不会影响“内联”相应表达式的编译器优化;避免对数组索引进行算术运算可以更好地实现这一目标inline
- 调用
属性获取程序似乎代价高昂System.DateTime
- 返回值是通过调用类型为
的构造函数生成的,其中System.String
是调用它的c方式newstring(c)
let internal c = "0000-00-00T00:00:00.000".ToCharArray()
let internal (%&) x m = char(48 + (x / m) % 10)
let format (a : System.DateTime) =
let y, m, d, h, min, s, ms =
a.Year, a.Month, a.Day,
a.Hour, a.Minute, a.Second,
a.Millisecond
c.[ 0] <- y %& 1000
c.[ 1] <- y %& 100
c.[ 2] <- y %& 10
c.[ 3] <- y %& 1
c.[ 5] <- m %& 10
c.[ 6] <- m %& 1
c.[ 8] <- d %& 10
c.[ 9] <- d %& 1
c.[11] <- h %& 10
c.[12] <- h %& 1
c.[14] <- min %& 10
c.[15] <- min %& 1
c.[17] <- s %& 10
c.[18] <- s %& 1
c.[20] <- ms %& 100
c.[21] <- ms %& 10
c.[22] <- ms %& 1
System.String c
我要改进的事情
- 避免重新创建字符数组
- 避免在字符数组中重新分配分隔符-它们永远不会更改
- 避免重新分配未更改的部分日期。如果你担心毫秒,我会假设年、月、日、小时、分钟甚至秒不会经常改变
- 避免重新计算
的值-它永远不会更改int'0'
- 避免额外的函数调用
let format = let mutable year = -1 let mutable month = -1 let mutable day = -1 let mutable hour = -1 let mutable minute = -1 let mutable second = -1 let array = "0000-00-00 00:00:00.000".ToCharArray() let zeroChar = int '0' fun (dateTime: DateTime) -> if dateTime.Year <> year then year <- dateTime.Year array.[0] <- char (zeroChar + year / 1000) array.[1] <- char (zeroChar + (year % 1000) / 100) array.[2] <- char (zeroChar + (year % 100) / 10) array.[3] <- char (zeroChar + (year % 10)) if dateTime.Month <> month then month <- dateTime.Month array.[5] <- char (zeroChar + month / 10) array.[6] <- char (zeroChar + month % 10) if dateTime.Day <> day then day <- dateTime.Day array.[8] <- char (zeroChar + day / 10) array.[9] <- char (zeroChar + day % 10) if dateTime.Hour <> hour then hour <- dateTime.Hour array.[11] <- char (zeroChar + hour / 10) array.[12] <- char (zeroChar + hour % 10) if dateTime.Minute <> minute then minute <- dateTime.Minute array.[14] <- char (zeroChar + minute / 10) array.[15] <- char (zeroChar + minute % 10) if dateTime.Second <> second then second <- dateTime.Second array.[17] <- char (zeroChar + second / 10) array.[18] <- char (zeroChar + second % 10) let ms = dateTime.Millisecond array.[20] <- char (zeroChar + ms / 100) array.[21] <- char (zeroChar + (ms % 100) / 10) array.[22] <- char (zeroChar + ms % 10) new string(array)
进一步的优化可能是避免DateTime属性调用,并根据记号手动计算值。取决于格式化函数是否需要线程安全:您是否衡量了重复使用数组(并且只设置一次常量字符)?我没有想到这一点。从数组中创建一个字符串,但随后它被嵌入到另一个字符串中,因此,到那时,数组可能会被删除。好主意,我试试看@CaringDev,我只是将array init从方法中去掉,使其成为模块的一部分,性能下降了13%,不知道为什么!可能有一些静态初始化检查。。。如果只在循环之外移动它会怎么样?我只是尝试在format调用之外创建缓冲区并将其作为参数传递,它运行速度快了11%!理想情况下,我需要找到一种方法,以保持在同一模块的缓冲区。非常好!我使用稀疏数据,其中日期可能跨越许多天,没有发生任何事情,然后在一秒钟内发生20件事情:)但我肯定可以写一次年份。我假设编译器这样的“int 0”的值将被评估一次(例如,它将以C++为例)。与.net?@Thomas不同的是,它可能会在运行时通过JIT进行优化(没有人知道),但它肯定会编译为
和ldc.i4.s48
指令conv.i4
let internal c = "0000-00-00T00:00:00.000".ToCharArray() let internal (%&) x m = char(48 + (x / m) % 10) let format (a : System.DateTime) = let y, m, d, h, min, s, ms = a.Year, a.Month, a.Day, a.Hour, a.Minute, a.Second, a.Millisecond c.[ 0] <- y %& 1000 c.[ 1] <- y %& 100 c.[ 2] <- y %& 10 c.[ 3] <- y %& 1 c.[ 5] <- m %& 10 c.[ 6] <- m %& 1 c.[ 8] <- d %& 10 c.[ 9] <- d %& 1 c.[11] <- h %& 10 c.[12] <- h %& 1 c.[14] <- min %& 10 c.[15] <- min %& 1 c.[17] <- s %& 10 c.[18] <- s %& 1 c.[20] <- ms %& 100 c.[21] <- ms %& 10 c.[22] <- ms %& 1 System.String c
public static string format(DateTime a) { int year = a.Year; ... c[0] = (char)(48 + year / 1000 % 10); c[1] = (char)(48 + year / 100 % 10); c[2] = (char)(48 + year / 10 % 10); c[3] = (char)(48 + year / 1 % 10); ... return new string(c); }
let format = let mutable year = -1 let mutable month = -1 let mutable day = -1 let mutable hour = -1 let mutable minute = -1 let mutable second = -1 let array = "0000-00-00 00:00:00.000".ToCharArray() let zeroChar = int '0' fun (dateTime: DateTime) -> if dateTime.Year <> year then year <- dateTime.Year array.[0] <- char (zeroChar + year / 1000) array.[1] <- char (zeroChar + (year % 1000) / 100) array.[2] <- char (zeroChar + (year % 100) / 10) array.[3] <- char (zeroChar + (year % 10)) if dateTime.Month <> month then month <- dateTime.Month array.[5] <- char (zeroChar + month / 10) array.[6] <- char (zeroChar + month % 10) if dateTime.Day <> day then day <- dateTime.Day array.[8] <- char (zeroChar + day / 10) array.[9] <- char (zeroChar + day % 10) if dateTime.Hour <> hour then hour <- dateTime.Hour array.[11] <- char (zeroChar + hour / 10) array.[12] <- char (zeroChar + hour % 10) if dateTime.Minute <> minute then minute <- dateTime.Minute array.[14] <- char (zeroChar + minute / 10) array.[15] <- char (zeroChar + minute % 10) if dateTime.Second <> second then second <- dateTime.Second array.[17] <- char (zeroChar + second / 10) array.[18] <- char (zeroChar + second % 10) let ms = dateTime.Millisecond array.[20] <- char (zeroChar + ms / 100) array.[21] <- char (zeroChar + (ms % 100) / 10) array.[22] <- char (zeroChar + ms % 10) new string(array)
original no ms display 2354 ms original with ms display 3545 ms new with ms display 1221 ms newest with ms display 691 ms