DateTime到字符串,在f#中,可以进一步优化吗?



我有一个系统,它做一些模拟并输出大量带有时间戳的日志信息。

我很惊讶地看到DateTime.ToString()在循环中非常昂贵(它实际上被调用了很多次),所以我试图制作一个更快的版本来输出日期、时间和毫秒。

这可以做得更快吗(输出需要毫秒)?我没有尝试使用指针,因为我认为(可能是错误的)对于这么一小段代码,固定对象的开销会更高。

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)
下面是测试代码:
let a = DateTime.UtcNow
let iterations = 10_000_000
let sw = Stopwatch()
sw.Start()
for i = 0 to iterations do
a.ToString() |> ignore
sw.Stop()
printfn $"original no ms display {sw.ElapsedMilliseconds} ms"
sw.Reset()
sw.Start()
for i = 0 to iterations do
a.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture) |> ignore
sw.Stop()
printfn $"original with ms display {sw.ElapsedMilliseconds} ms"
sw.Reset()
sw.Start()
for i = 0 to iterations do
a |> DateTimeFormatter.format |> ignore
sw.Stop()
printfn $"new with ms display {sw.ElapsedMilliseconds} ms"

和测试结果(MBP i7 2019):

original no ms display   2892 ms
original with ms display 4042 ms
new with ms display      1435 ms

我想改进的地方

  • 避免重新创建字符数组

  • 避免在字符数组中重新分配分隔符-它们永远不会改变

  • 避免重新分配未更改的部分日期。如果你担心毫秒,我认为年、月、日、小时、分钟甚至秒都不会经常变化。

  • 避免重新计算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)
    

与您的测试用例一起运行它,与您的解决方案相比显示x2的性能,与原始解决方案相比显示x5的性能。

original no ms display 2354 ms
original with ms display 3545 ms
new with ms display 1221 ms
newest with ms display 691 ms

进一步的优化可以避免DateTime属性调用,并根据Ticks手动计算值。

重用数组和不重写常量字符已经在评论中提到。进一步考虑:

  • 在这里使用inline关键字似乎并不影响"内联"的编译器优化。对应表达式;通过避免对数组索引
  • 进行算术运算,可以更好地实现这一目标。
  • 调用System.DateTime属性getter似乎是昂贵的
  • 返回值是通过调用System.String类型的构造函数生成的,new string(c)是c#调用它的方式

因此,看一下这个-额外的分割1似乎并没有减慢它:

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

这将有望被编译成以下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);
}

相关内容

  • 没有找到相关文章

最新更新