3

我需要格式化一个宽字符串缓冲区的 FILETIME 值信息,并且配置提供了格式字符串。

我实际上在做什么:

  • Config 提供格式字符串:L"{YYYY}-{MM}-{DD} {hh}:{mm}:{ss}.{mmm}"

  • 将 FILETIME 转换为系统时间:

    SYSTEMTIME stUTC;
    FileTimeToSystemTime(&fileTime, &stUTC);
  • 格式化字符串
    fmt::format_to(std::back_inserter(buffer), strFormat,        
             fmt::arg(L"YYYY", stUTC.wYear),
             fmt::arg(L"MM", stUTC.wMonth),
             fmt::arg(L"DD", stUTC.wDay),
             fmt::arg(L"hh", stUTC.wHour),
             fmt::arg(L"mm", stUTC.wMinute),
             fmt::arg(L"ss", stUTC.wSecond),
             fmt::arg(L"mmm", stUTC.wMilliseconds));

我完全理解服务会带来成本 :) 但我的代码会调用此语句数百万次,并且明显存在性能损失(超过 6% 的 CPU 使用率)。

欢迎我为改进此代码所做的“任何事情”。

我看到 {fmt} 有时间 API 支持。不幸的是,它似乎无法格式化时间/日期的毫秒部分,并且需要一些从FILETIMEstd::time_t...的转换工作

我应该忘记“自定义”格式字符串并为FILETIME(或SYSTEMTIME)类型提供自定义格式化程序吗?这会导致显着的性能提升吗?

我很感激你能提供的任何指导。

4

1 回答 1

3

在评论中,我建议将您的自定义时间格式字符串解析为一个简单的状态机。它甚至不必是这样的状态机。它只是一系列线性指令。

目前,fmt该类需要做一些工作来解析格式类型,然后将整数转换为填充零的字符串。尽管不太可能,但它有可能像我将要建议的那样进行了高度优化。

基本思想是有一个(大)查找表,当然可以在运行时生成,但为了快速说明:

const wchar_t zeroPad4[10000][5] = { L"0000", L"0001", L"0002", ..., L"9999" };

如果需要,您可以拥有 1 位、2 位和 3 位查找表,或者如果您仅添加偏移量,则可以识别这些值都包含在 4 位查找表中。

所以要输出一个数字,你只需要知道偏移量SYSTEMTIME是什么,值是什么类型,以及应用什么字符串偏移量(0 表示 4 位,1 表示 3 位等)。它使事情变得更简单,因为其中的所有结构元素SYSTEMTIME都是相同的类型。并且您应该合理地假设没有值需要范围检查,尽管如果不确定,您可以添加它。

你可以像这样配置它:

struct Output {
    int dataOffset;  // offset into SYSTEMTIME struct
    int count;       // extra adjustment after string lookup
};

文字字符串呢?好吧,您可以复制这些内容,也可以重新Output使用负数dataOffset来表示格式字符串中从哪里开始,并count保存在该模式下要输出的字符数。如果您需要额外的输出模式,请使用mode成员扩展此结构。

Anwyay,让我们以您的字符串L"{YYYY}-{MM}-{DD} {hh}:{mm}:{ss}.{mmm}"为例。在你解析这个之后,你最终会得到:

Output outputs[] {
    { offsetof(SYSTEMTIME, wYear), 0 },         // "{YYYY}"
    { -6, 1 },                                  // "-"
    { offsetof(SYSTEMTIME, wMonth), 2 },        // "{MM}"
    { -11, 1 },                                 // "-"
    { offsetof(SYSTEMTIME, wDay), 2 },          // "{DD}"
    { -16, 1 },                                 // " "
    // etc...  you get the idea
    { offsetof(SYSTEMTIME, wMilliseconds), 1 }, // "{mmm}"
    { -1, 0 },                                  // terminate
};

不用花太多时间看到,当您有一个SYSTEMTIME作为输入、一个指向原始格式字符串的指针、查找表和这个基本指令数组时,您可以继续并将结果输出到一个预先确定大小的缓冲区中迅速地。

我相信你可以想出代码来有效地执行这些指令。

这种方法的主要缺点是查找表的大小可能会导致缓存问题。但是,大多数查找将发生在前 100 个元素中。您还可以将表压缩为普通char值,然后wchar_t在复制时注入零字节。

与往常一样:实验、测量并玩得开心!

于 2019-07-19T00:08:27.290 回答