Как ускорить этот код при преобразовании float в строку?

Я профилировал мой unit тест и большую часть времени, когда приложение запускается, расходуется на этот раздел кода. Это функция, которая преобразует float в строку. Как я могу переписать код ниже, чтобы иметь более высокую скорость?

Я неправильно читаю отчет, а узкое место - где-то в другом месте?

В отчетах профиля говорится:

Total CPU% = 13.02%, Self CPU%.07, Total CPU (ms) 769, Self CPU из 100 процентов 769 мс.

769 из 5907 образцов.

std::string FloatToScientificString(float val, int width, int precision)
{
    std::stringstream buffer;
    buffer << std::scientific << std::setw(width) << std::setprecision(precision) << std::setfill(' ') << val;
    return buffer.str();
}

Ответ 1

Если использовать внешнюю библиотеку для достижения этой цели, вы можете пойти с fmtlib (эта библиотека, вероятно, превратится в стандарт), которая утверждает, что она быстрее, чем другие подходы (см. Их тесты).

#include <fmt/format.h>

std::string FloatToScientificString(float val, int width, int precision)
{
    return fmt::format("{:>{}.{}e}", val, width, precision);
}

Это должно возвращать идентичную строку в качестве исходной функции, и вы не жертвуете безопасностью типа, как при подходах std::*printf. При использовании abseil вместо этого (они утверждают, что они заметно быстрее, чем printf -family здесь), функция выглядит так:

#include <absl/strings/str_format.h>

std::string FloatToScientificString(float val, int width, int precision)
{
    return absl::StrFormat("%*.*e", width, precision, val);
}

Существует также форматированный формат, который не позволяет передавать спецификатор ширины или точности в качестве аргументов, но это работает одинаково хорошо:

#include <boost/format.hpp>

std::string FloatToScientificString(float val, int width, int precision)
{
    const std::string fmt = "%" + std::to_string(width) + "." +
        std::to_string(precision) + "e";

    return boost::str(boost::format(fmt) % val);
}

и, наконец, без каких-либо внешних зависимостей, отличных от стандартной библиотеки (обратите внимание, что использование std::snprintf превосходит std::sprintf при проверке размера буфера, но ни одна из функций не является безопасной по типу):

#include <cstdio>

std::string FloatToScientificString(float val, int width, int precision)
{
    static const int bufSize = 100;
    static char buffer[bufSize];

    std::snprintf(buffer, bufSize, "%*.*e", width, precision, val);

    return std::string(buffer);
}

Правильный анализ эффективности этих параметров, вероятно, сам по себе. Любой из этих параметров должен быть заметно быстрее, чем исходный подход, используя std::stringstream, хотя и все фрагменты, кроме std::snprintf являются безопасными по типу.

Ответ 2

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

Это позволяет сравнивать бинарные/поплавковые данные с бинарными/плавающими данными без необходимости делать дополнительные покрытия во время выполнения.

А также вы можете выполнять свои тестовые тесты, записывать входящие данные в какое-то хранилище и сравнивать позже с этим хранилищем. Таким образом, вы только один раз сравниваете свое строковое представление, а затем сравниваете его с двоичными хранимыми данными. Это можно сделать так долго, пока ваши тестовые тесты остаются нетронутыми.