Получение максимальной производительности из {fmt}

Мне нужно отформатировать информацию о значении FILETIME в широкий строковый буфер, а конфигурация предоставляет строку формата.

Что я на самом деле делаю:

  • Config предоставляет строку формата: L"{YYYY}-{MM}-{DD} {hh}:{mm}:{ss}.{mmm}"

  • Перевести ФИЛЬТРАЦИЮ в Системное время:

    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% загрузки ЦП).

"Все, что я мог сделать, чтобы улучшить этот код, будет приветствоваться.

Я видел, что {fmt} имеет поддержку API времени. К сожалению, кажется, что невозможно отформатировать миллисекундную часть времени / даты, и это требует определенных усилий по преобразованию из FILETIME в std::time_t...

Должен ли я забыть о "пользовательской" строке формата и предоставить пользовательский форматер для FILETIME (или же SYSTEMTIME) типы? Приведет ли это к значительному повышению производительности?

Буду признателен за любые рекомендации, которые вы можете предоставить.

1 ответ

Решение

В комментариях я предложил разобрать строку пользовательского формата времени в простой конечный автомат. Это даже не должен быть конечный автомат как таковой. Это просто линейная серия инструкций.

В настоящее время fmt Класс должен проделать небольшую работу, чтобы проанализировать тип формата и затем преобразовать целое число в строку, дополненную нулями. Возможно, хотя и маловероятно, что он так сильно оптимизирован, как я собираюсь предположить.

Основная идея состоит в том, чтобы иметь (большую) справочную таблицу, которая, конечно, может быть сгенерирована во время выполнения, но в целях быстрой иллюстрации:

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

Вы можете иметь одно-, двух- и трехзначные таблицы поиска, если хотите, или альтернативно распознать, что все эти значения содержатся в четырехзначной таблице поиска, если просто добавить смещение.

Таким образом, чтобы вывести число, вам просто нужно знать, какое смещение в 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 ноль байт при копировании.

Как всегда: экспериментируйте, измеряйте и веселитесь!

Другие вопросы по тегам