C 性能优化实践(一)

数字转换为字符串

在日常开发中我们经常需要将数字转换为字符串,c++11提供了std::to_string方法来做转换,c++11之前还有其它一些方法也可以实现数字到字符串的转换,比如std::stringstream, sprintf,c++17中又提供了std::to_chars将数字转换为字符串,除此之外,还有一些format库也提供了相应的转换方法。因此,在c++17中我们有至少5种方法来实现数字到字符串的转换,那么我们到底应该选择哪种方法更好呢?

一个简单的选择标准是性能最优就是最好的选择。不妨做一个简单的性能测试看看这些方法哪个的性能更好。

#include <iostream>#include <chrono>#include <charconv>#include <string>#include <sstream>#include <fmt/core.h>#include <stdio.h>
class ScopedTimer { public: ScopedTimer(const char* name) : m_name(name), m_beg(std::chrono::high_resolution_clock::now()) { } ~ScopedTimer() { auto end = std::chrono::high_resolution_clock::now(); auto dur = std::chrono::duration_cast<std::chrono::nanoseconds>(end - m_beg); std::cout << m_name << ' : ' << dur.count() << ' ns\n'; } private: const char* m_name; std::chrono::time_point<std::chrono::high_resolution_clock> m_beg;};
void to_string(){ ScopedTimer timer('to_string'); for(size_t i=0; i<10000; i++) { std::to_string(i); }}
void ss_to_string() { ScopedTimer timer('ss_to_string'); for(size_t i=0; i<10000; i++) { std::stringstream ss; ss<<i; }}
void fmt_string() { ScopedTimer timer('fmt_to_string'); for(size_t i=0; i<10000; i++) { fmt::format('{}', i); }}
void printf_string() { ScopedTimer timer('sprintf_to_string'); for(size_t i=0; i<10000; i++) { char str[10]; sprintf(str, '%d', i); }}
void conv_string() { ScopedTimer timer('conv_to_string'); for(size_t i=0; i<10000; i++) { char str[10]; std::to_chars(str, str + 10, i); }}
int main() { to_string(); ss_to_string(); fmt_string(); printf_string(); conv_string();}
-O2编译选项输出结果:to_string: 822330 nsss_to_string: 3277426 nsfmt_to_string: 828938 nssprintf_to_string: 658275 nsconv_to_string: 61313ns

从测试结果看,stringstream是最慢的,主要原因是频繁构造了该对象,std::to_string和fat::format性能相当,sprintf性能比std::to_string快约20%, c++17的to_chars无疑问是最快的,比其他的to string方法快了一个数量级。

因此在c++17中应该优先使用std::to_chars来获得最优的性能。在c++17之前可以使用sprintf和std::to_string,尽量不要用stringstream。

虽然c++17的to_chars已经很快了,但是在一些场景下我们还能继续提升to_string的性能,比如彻底消除to_string的运行期开销,到这里大家应该知道优化的思路了:利用编译期计算来彻底消除to_string的运行期开销。

编译期数字转换为字符串

很多时候我们转换的数字是一个编译期常量,这时候就应该在编译期来做转换而不是在运行期做转换,消除运行期开销以实现最优的性能。

对于编译期数字常量在编译期转换为字符串最简单的方法是用一个字符串化的宏来实现,两行代码即可。

#define STR(x) #x#define TOSTRING(x) STR(x)TOSTRING(1); '1'TOSTRING(2); '2'

如果不希望用宏,那么可以用元编程实现编译期数字转换为字符串的元函数。

//https://stackoverflow.com/questions/23999573/convert-a-number-to-a-string-literal-with-constexpr/24000041namespace detail{ template<uint8_t... digits> struct positive_to_chars { static const char value[]; static constexpr size_t size = sizeof...(digits); }; template<uint8_t... digits> const char positive_to_chars<digits...>::value[] = {('0' + digits)..., 0};
template<uint8_t... digits> struct negative_to_chars { static const char value[]; }; template<uint8_t... digits> const char negative_to_chars<digits...>::value[] = {'-', ('0' + digits)..., 0};
template<bool neg, uint8_t... digits> struct to_chars : positive_to_chars<digits...> {};
template<uint8_t... digits> struct to_chars<true, digits...> : negative_to_chars<digits...> {};
template<bool neg, uintmax_t rem, uint8_t... digits> struct explode : explode<neg, rem / 10, rem % 10, digits...> {};
template<bool neg, uint8_t... digits> struct explode<neg, 0, digits...> : to_chars<neg, digits...> {};
template<typename T> constexpr uintmax_t cabs(T num) { return (num < 0) ? -num : num; }}
template<typename T, T num>struct string_from : ::detail::explode<num < 0, ::detail::cabs(num)> {};
string_from<unsigned, 1>::value; //'1'static_assert(string_from<unsigned, 1>::size==1);

简单和运行期数字转换为字符串做一个性能比较。

void compile_time_to_string() {    ScopedTimer timer('compile_time_to_string');    for(size_t i=0; i<10000; i++) {      string_from<unsigned, 1>::value;    }}void macro_string() {    ScopedTimer timer('macro_to_string');    for(size_t i=0; i<10000; i++) {      TOSTRING(1);    }}int main() {  to_string();  ss_to_string();  fmt_string();  printf_string();  conv_string();  macro_string();  compile_time_to_string();}//输出结果:to_string :              822330 nsss_to_string :           3277426 nsfmt_to_string :          828938 nssprintf_to_string :      658275 nsconv_to_string :         36267 nsmacro_to_string :        32 nscompile_time_to_string : 31 ns//https://godbolt.org/z/xsbsGjjWa

可以看到将数字常量在编译期转换为字符串的优化效果很明显,彻底消除了运行期开销!

后续还会继续谈一些c++性能优化方法和实践,请继续关注c++性能优化系列文章。

(0)

相关推荐

  • Tcl中的字符串处理

    string bytelength str返回用于存储字符串的字节数,由于UTF-8编码的原因,或许与string length返回的字符长度有所不同string compare ?-nocase? ...

  • C 中的Swap函数写法汇总

    swap函数几乎是所有初学者都写过的一个最基本的函数之一,通常是用它来了解函数概念.形参与实参.引用和指针.然而,这个极为基础的函数却有着非常重要的作用.正因为它的重要性.普遍性,意味着swap函数必 ...

  • C语言中的匕首

    - C风格字符串 原创IT之州2021-02-06 11:44:22 不同于其他主流编程语言,C语言本身对于字符串的处理采用了独特的设计,被称为C风格的字符串,以区别于C 创立的字符串string. ...

  • 【百度官方技术分享】百度智能小程序框架性能优化实践

    一.百度智能程序整体框架及演进 整个移动互联网一直是在 NA 和 H5 之间寻找权衡,NA 的性能好.能力强:H5 灵活性更高.我认为渲染分为两派,一派就是 NA 渲染派,一派叫做 H5 渲染派. N ...

  • C 性能优化实践(二)

    字符串性能优化 在平时的开发工作中,操作字符串是十分常见和频繁的,比如下面这个个简单的字符串操作函数: std::string log_str(const char* filename, int li ...

  • 干货:C 的性能优化

    前言 性能优化不管是从方法论还是从实践上都有很多东西,从 C++ 语言本身入手,介绍一些性能优化的方法,希望能做到简洁实用. 实例1 在开始本文的内容之前,让我们看段小程序: // 获取一个整数对应1 ...

  • Android 性能优化必知必会 · Android Performance

    做了这么久性能相关的工作,也接触了不少模块,说实话要做好性能这一块,真心不容易.为什么这么说? 是因为需要接触的知识实在是太多了, Android 是一个整体,牵一发而动全身,不是说只懂一个模块就可以 ...

  • 浅谈面向客户端的性能优化

    有朋友通过<智能音箱场景下的性能优化>一文找到了我,既然智能音箱的性能优化相当于一个超集,那么对其的一个子集--客户端系统如何进行性能优化呢? 反正隔离在家,不妨对客户端的性能优化梳理一下 ...

  • 智能音箱场景下的性能优化

    QCon是由InfoQ主办的综合性技术盛会,今年是Qcon举办的第10个年头,半吊子全栈工匠有幸作为演讲嘉宾分享一个近两年来的实践经验--智能音箱场景下的性能优化,隶属于曾波老师出品的"场景 ...

  • 如何配置Nginx压缩实现性能优化?怎么学Linux系统

    如何配置Nginx gzip压缩实现性能优化?是每个Linux运维管理人员都需要掌握的技能.互联网时代发展迅速,Linux运维技术的需求更多推进不少.市场对于Linux运维人才的需求也在逐渐加大.Li ...

  • Flutter 2.2发布:针对各平台的性能优化、完善生态支持

    局长 OSC开源社区 昨天 文 | 局长 出品 | OSC开源社区(ID:oschina2013) 谷歌在昨日举办的 Google I/O 2021 大会上宣布了 Flutter 2.2,其开发团队称 ...

  • Web性能优化之图片延迟加载

    来源:微信公众号CodeL 对于一些图片多,页面长的网页来说,如果每次打开页面加载全部的网页内容,页面加载速度势必会受到影响,如果每次打开网页只将网页可视区域的内容加载给用户 ,将大大提高网页浏览速度 ...