Получение максимальной производительности из {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
ноль байт при копировании.
Как всегда: экспериментируйте, измеряйте и веселитесь!