C++17 Trans int 到前缀为“0”字符串的快速方法

c++17 fast way for trans int to string with prefix '0'

提问人:f1msch 提问时间:6/27/2022 最后编辑:Jarod42f1msch 更新时间:6/28/2022 访问量:334

问:

我想将 an 转换为 . 我知道在 c++17 下,更好的方法是使用 .intstringstd::to_string

但现在我想用几个“0”填充前缀。 例如,是“1”,但我希望结果是“00001”(总长度为 5)。int i = 1std::to_string(i)

我知道使用或可能实现这一点。 但是哪个有更好的性能或其他方式?sprintfstringstream

C++ 字符串 C++17 ToString

评论

3赞 Jarod42 6/27/2022
“哪个有更好的性能”。一般来说,你必须测量。
1赞 Jeremy Friesner 6/27/2022
我的怀疑是,这比(尽管也不太安全——至少使用)更有效,但唯一真正知道的方法是测试和测量两种方式。(如果你无法衡量任何显着的差异,那么你使用哪个并不重要)sprintf()stringstreamsnprintf()
3赞 paolo 6/27/2022
您是否已经尝试过使用 std::to_chars
1赞 Marek R 6/27/2022
我建议您使用库,这是自 C++20 以来 std 的一部分。 这很糟糕,因为你不能保留缓冲区(通常重新分配很昂贵),但小字符串优化应该可以节省简单值的一天。因此,正如 Jarod42 建议的那样,首先使用反映您的场景的数据进行测量。结果可能是你的方案中最快的。fmtstd::stringstreamintstd::stringstream
0赞 Ted Lyngmo 6/27/2022
@paolo 计算出应该指向哪里以及如何处理负值是不是很困难?first

答:

0赞 ramsay 6/27/2022 #1

是的,使用 or 都可以将 int 转换为带前导的字符串。snprintfstringstream0

#include <chrono>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

using std::chrono::duration_cast;
using std::chrono::high_resolution_clock;
using std::chrono::milliseconds;

std::string ss_to_string(int data) {
  std::stringstream ss;
  ss << std::setfill('0') << std::setw(5) << data;
  return ss.str();
}

std::string snprint_to_string(int data) {
  char buffer[6];
  snprintf(buffer, 6, "%05d", data);
  return buffer;
}

int main(int argc, char **argv) {

  auto t1 = high_resolution_clock::now();
  for (int i = 0; i < 100000; i++) {
    ss_to_string(1);
  }
  auto t2 = high_resolution_clock::now();

  std::cout << "sstream version: "
            << duration_cast<milliseconds>(t2 - t1).count() << "ms\n";

  auto t3 = high_resolution_clock::now();
  for (int i = 0; i < 100000; i++) {
    snprint_to_string(1);
  }
  auto t4 = high_resolution_clock::now();

  std::cout << "snprintf version: "
            << duration_cast<milliseconds>(t4 - t3).count() << "ms\n";
}

在测量了版本和版本之后,事实证明实现效率更高。snprintfstringstreamsnprintf

sstream version: 60ms
snprintf version: 23ms

评论

2赞 Marek R 6/27/2022
请注意,在 C++ 中衡量性能并不像您想象的那么简单,因为存在“好像规则”。编译器可以删除大量经过测试的代码。因此,您的测试可能无效。你还使用了优化标志吗?
0赞 Marek R 6/27/2022
看起来差异更大(比例 4:1):quick-bench.com/q/dI0PGANZ-t1WKJyw-RkK2MVY37E
1赞 Mgetz 6/27/2022
我很想看看并包括std::formatstd::to_chars
0赞 Marek R 6/27/2022
@Mgetz可悲的是,编译器对 的支持仍然很差。std::format
1赞 Ted Lyngmo 6/27/2022
@MarekR尤其是在 C++17 年。:-)
4赞 Howard Hinnant 6/28/2022 #2

如果你对你的域名有所了解,并且不需要检查错误,那么没有什么比滚动你自己的域名更快的了。例如,如果您知道 ur 始终在 [0, 99'999] 范围内,则可以:int

std::string
convert(unsigned i)
{
    std::string r(5, '0');
    char* s = r.data() + 4;
    do
    {
        *s-- = static_cast<char>(i%10 + '0');
        i /= 10;
    } while (i > 0);
    return r;
}

通用库没有做出这种假设的奢侈。

4赞 Marek R 6/28/2022 #3

就像在评论中提到的那样,首先测量! 我已经创建了具有多种可能实现的存储库来测试它们。

  • std::ostringstream
  • 斯普林特夫
  • fmt::格式
  • 标准::to_chars
  • 完整的手动实现(复制表 Howard Hinnant) - 如果数字不适合 5 个字符,则失败。

请注意,不包括负数(预计会出现一些错误)!

这是现场演示(没有 fmt)。

#include <sstream>
#include <charconv>
#include <iomanip>

namespace fill5 {
namespace tag {
struct std_stream {};
struct std_to_chars {};
struct c_sprintf {};
struct manual {};
}

template<typename Tag>
std::string toString(int x);

namespace {
constexpr auto width = 5;
constexpr auto fill = '0';

std::string strBuf() {
    std::string r;
    r.resize(r.capacity());
    return r;
}
}

template<>
std::string toString<tag::std_stream>(int x)
{
    std::ostringstream r;
    r << std::setw(width) << std::setfill(fill) << x;
    return r.str();
}

template<>
std::string toString<tag::std_to_chars>(int value)
{
    auto r = strBuf();
    auto x = std::to_chars(r.data(), r.data() + r.size(), value);
    r.resize(x.ptr - r.data());
    if (r.size() < width) {
        r.insert(0, width - r.size(), fill);
    }
    return r;
}

template<>
std::string toString<tag::c_sprintf>(int value)
{
    auto r = strBuf();
    r.resize(std::sprintf(r.data(), "%05d", value));
    return r;
}

template<>
std::string toString<tag::manual>(int value)
{
    std::string r(5, '0');
    char* s = r.data() + 4;
    do
    {
        *s-- = char(value % 10) + '0';
        value /= 10;
    } while (value > 0);
    return r;
}
}

template <typename ImplTag>
void fill5ToString(benchmark::State& state) {
    constexpr int data[] {0, 1, 5, 13, 43, 343, 5344, 4234, 55555, 243422342};
    for (auto _ : state) {
        for (auto x : data) {
            benchmark::DoNotOptimize(x);
            auto r = fill5::toString<ImplTag>(x);
            benchmark::DoNotOptimize(r);
        }
    }
}

using namespace fill5::tag;

BENCHMARK(fill5ToString<std_stream>);
BENCHMARK(fill5ToString<c_sprintf>);
BENCHMARK(fill5ToString<std_to_chars>);
BENCHMARK(fill5ToString<manual>);

以下是我的机器(涵盖版本)的结果:fmt

% ./perf/Release/perf 
2022-06-28T01:36:36+02:00
Running ./perf/Release/perf
Run on (16 X 3600 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x8)
  L1 Instruction 32 KiB (x8)
  L2 Unified 256 KiB (x8)
  L3 Unified 16384 KiB (x1)
Load Average: 1.13, 1.28, 1.12
----------------------------------------------------------------------
Benchmark                            Time             CPU   Iterations
----------------------------------------------------------------------
fill5ToString<std_stream>         1782 ns         1782 ns       388470
fill5ToString<c_sprintf>           648 ns          648 ns      1070107
fill5ToString<std_to_chars>        227 ns          227 ns      3063779
fill5ToString<fmt>                 442 ns          442 ns      1584815
fill5ToString<manual>             46.7 ns         46.7 ns     14455075

正如预期的那样,手动实现摇摆不定。 版本最好来自简单的实现,版本是“像一样快”。fmtstd::ostringstream