Optimization DateTime to string,用F#表示,是否可以进一步优化?

Optimization DateTime to string,用F#表示,是否可以进一步优化?,optimization,f#,Optimization,F#,我有一个系统,可以进行一些模拟,并输出大量带有时间戳的日志信息 我很惊讶地看到DateTime.ToString()在循环中非常昂贵(它的调用非常多),所以我尝试制作一个更快的版本来输出日期、时间和毫秒 这可以更快地完成吗(输出需要毫秒)?我没有尝试使用指针,因为我假设(可能是错误的)对于这样一小段代码,固定对象的开销会更高 module DateTimeFormatter = let inline private valueToDigit (value: int) : char =

我有一个系统,可以进行一些模拟,并输出大量带有时间戳的日志信息

我很惊讶地看到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)
以及测试结果(MBP i7 2019):


注释中已经提到重用数组而不是重写常量字符。进一步考虑:

  • 这里使用
    inline
    关键字似乎不会影响“内联”相应表达式的编译器优化;避免对数组索引进行算术运算可以更好地实现这一目标
  • 调用
    System.DateTime
    属性获取程序似乎代价高昂
  • 返回值是通过调用类型为
    System.String
    的构造函数生成的,其中
    newstring(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
我要改进的事情

  • 避免重新创建字符数组

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

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

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