{fmt}
{fmt}是一个开源格式化库,提供了快速且安全的C stdio和C++ iostreams的替代方案。
如果您喜欢这个项目,请考虑向BYSOL捐赠以帮助白俄罗斯的政治压迫受害者:[捐赠链接](https://www.facebook.com/donate/759400044849707/108388587646909/)
Q&A:在带有标签fmt的[StackOverflow](https://stackoverflow.com/questions/tagged/fmt)上提问。
在[编译器探索器](https://godbolt.org/z/Eq5763)中试用{fmt}。
特性
- 简单的格式API,具有位置参数以进行本地化
- 实现了[cppreference.com](https://cppreference.cn/w/cpp/utility/format)的C++20 std::format
- 格式字符串语法类似于Python的[format](https://docs.pythonlang.cn/3/library/stdtypes.html#str.format)
- 快速IEEE 754浮点数格式化器,具有正确的舍入、短精度和往返保证
- 安全的[printf实现](https://fmt.dev/latest/api.html#printf-formatting),包括对位置参数的POSIX扩展
- 可扩展性:[支持用户自定义类型](https://fmt.dev/latest/api.html#formatting-user-defined-types)
- 高性能:比常见的标准库实现更快,包括(s)printf、iostreams、to_string和to_chars,请参阅[速度测试](#speed-tests)和[将一亿个整数转换为字符串每秒](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
- 代码大小小:在最小配置下只包含三个文件,core.h、format.h和format-inl.h,以及编译后的代码;请参阅[编译时间和代码膨胀](#compile-time-and-code-bloat)
- 可靠性:该库有一系列广泛的[测试](https://github.com/fmtlib/fmt/tree/master/test)并被[持续模糊测试](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1)
- 安全性:该库完全类型安全,格式字符串中的错误可以编译时报告,自动内存管理防止缓冲区溢出错误
- 易用性:小型自包含代码库,无外部依赖,采用宽容的MIT授权协议
- 兼容性良好,跨平台输出一致,并支持老旧的编译器
- 干净的代码库,无警告,即使在高级警告级别如
-Wall -Wextra -pedantic
下也是如此 - 默认情况下具有地区独立性
- 可以使用
FMT_HEADER_ONLY
宏选择性地启用仅头文件配置
想要获取更多详细信息,请参阅文档
示例
输出到标准输出 (运行)
#include <fmt/core.h>
int main() {
fmt::print("Hello, world!\n");
}
格式化字符串 (运行)
std::string s = fmt::format("The answer is {}.", 42);
// s == "The answer is 42."
使用位置参数格式化字符串 (运行)
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
打印chrono持续时间 (运行)
#include <fmt/chrono.h>
int main() {
using namespace std::literals::chrono_literals;
fmt::print("Default format: {} {}\n", 42s, 100ms);
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
}
输出
Default format: 42s 100ms strftime-like format: 03:15:30
打印容器 (运行)
#include <vector>
#include <fmt/ranges.h>
int main() {
std::vector<int> v = {1, 2, 3};
fmt::print("{}\n", v);
}
输出
{1, 2, 3}
在编译时检查格式字符串
std::string s = fmt::format(FMT_STRING("{:d}"), "don't panic");
这将引发编译时错误,因为d
不是一个适用于字符串的无效格式说明符。
从单个线程写入文件
#include <fmt/os.h>
int main() {
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");
}
这比fprintf快5到9倍。(参考)
用颜色和文本样式打印
#include <fmt/color.h>
int main() {
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
"Hello, {}!\n", "world");
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
fmt::emphasis::underline, "Hello, {}!\n", "мир");
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
"Hello, {}!\n", "世界");
}
在现代终端上的输出
基准测试
速度测试
库 | 方法 | 运行时间,秒 |
---|---|---|
libc | printf | 1.04 |
libc++ | std::ostream | 3.05 |
{fmt} 6.1.1 | fmt::print | 0.75 |
Boost Format 1.67 | boost::format | 7.24 |
Folly Format | folly::format | 2.23 |
{fmt}是所有基准测试方法中最快的,比printf
快约35%。
上述结果由在macOS 10.14.6上使用clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT
构建的tinyformat_test.cpp
生成,取了三次运行中最好的一次。在测试中,格式字符串"%0.10f:%04d:%+g:%s:%p:%c:%%\n"
或等效字符串填充了2000000次,输出发送到/dev/null
;更多详细资料请参阅源代码。
{fmt}在浮点格式化方面比std::ostringstream
和sprintf
快20到30倍,比double-conversion和ryu快。
编译时间和代码膨胀
来自 format-benchmark 的脚本 bloat-test.py 测试了非平凡项目的编译时间和代码膨胀。它生成了 100 个翻译单位,并在每个中使用了五次 printf()
或其替代方案来模拟一个中等规模的项目。以下表格显示了生成的可执行文件大小和编译时间(Apple LLVM 版本 8.1.0(clang-802.0.42),macOS Sierra,三次最佳)。
优化构建(-O3)
方法 | 编译时间,s | 可执行文件大小,KiB | strip 后大小,KiB |
---|---|---|---|
printf | 2.6 | 29 | 26 |
printf+字符串 | 16.4 | 29 | 26 |
iostreams | 31.1 | 59 | 55 |
{fmt} | 19.0 | 37 | 34 |
Boost Format | 91.9 | 226 | 203 |
Folly Format | 115.7 | 101 | 88 |
如您所见,与 iostreams 相比,{fmt} 在输出二进制代码大小方面的开销减少60%,并且近乎接近 printf
。Boost Format 和 Folly Format 的开销最大。
printf+string
与 printf
相同,但增加了额外的 <string>
包含以测量后者的开销。
非优化构建
方法 | 编译时间,s | 可执行文件大小,KiB | strip 后大小,KiB |
---|---|---|---|
printf | 2.2 | 33 | 30 |
printf+字符串 | 16.0 | 33 | 30 |
iostreams | 28.3 | 56 | 52 |
{fmt} | 18.2 | 59 | 50 |
Boost Format | 54.1 | 365 | 303 |
Folly Format | 79.9 | 445 | 430 |
libc
、lib(std)c++
和 libfmt
均作为共享库链接,以比较格式化函数的开销。Boost Format 是一个头文件只库,因此它不提供任何链接选项。
运行测试
有关如何构建库和运行单元测试的说明,请参阅 构建库。
基准测试位于单独的存储库中,即 format-benchmarks,因此要运行基准测试,您首先需要克隆此存储库并使用 CMake 生成 Makefiles。
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git $ cd format-benchmark $ cmake .
然后您可以运行速度测试
$ make speed-test
或膨胀测试
$ make bloat-test
使用此库的项目
- 0 A.D.:一款免费、开源、跨平台的实时战略游戏
- AMPL/MP:一个开源的数学规划库
- Aseprite:动画精灵编辑器和像素画工具
- AvioBook:一个全面的飞机操作套件
- Blizzard Battle.net:一个在线游戏平台
- Celestia:实时三维空间可视化
- Ceph:一个可扩展的分布式存储系统
- ccache:编译器缓存
- ClickHouse:分析型数据库管理系统
- CUAUV:康奈尔大学自主水下航行器
- Drake:非线性动力学系统规划、控制和分析的工具箱(麻省理工学院)
- Envoy:C++ L7代理和通信总线(Lyft)
- FiveM:GTA V修改框架
- Folly:Facebook开源库
- HarpyWar/pvpgn:带有调整的玩家对战游戏网络
- KBEngine:开源MMOG服务器引擎
- Keypirinha:Windows语义启动器
- Kodi(原名xbmc):家用影院软件
- Knuth:高性能比特币全节点
- Microsoft Verona:并发所有权的研发编程语言
- MongoDB:分布式文档数据库
- MongoDB Smasher:生成随机数据集的小工具
- OpenSpace:开源天体可视化框架
- PenUltima Online (POL):兼容大多数Ultima Online客户端的MMO服务器
- PyTorch:开源机器学习库
- quasardb:分布式、高性能的关联数据库
- Quill:异步低延迟日志库
- QKW:将别名泛化以简化导航,并执行复杂的多行终端命令序列
- redis-cerberus:Redis集群代理
- redpanda:C++编写的10倍于Kafka®的替代品,适用于关键任务系统
- rpclib:现代C++ msgpack-RPC服务器和客户端库
- Salesforce Analytics Cloud:商业智能软件
- Scylla:兼容Cassandra的NoSQL数据存储,单服务器每秒可处理100万笔交易
- Seastar:适用于现代硬件的先进开源C++框架,用于高性能服务器应用程序
- spdlog:超快的C++日志库
- Stellar:金融平台
- Touch Surgery:手术模拟器
- TrinityCore:开源MMORPG框架
- Windows Terminal:新的Windows终端
如果您知道使用此库的其他项目,请通过电子邮件或通过提交问题告知我。
动机
为什么还需要另一个格式化库呢?
完成这项任务有许多方法,从标准的方法如printf函数族和iostreams到Boost Format和FastFormat库。创建新库的原因是,我找到的每个现有解决方案要么有严重问题,要么没有提供我需要的所有功能。
printf
关于printf的好处是,它非常快,并且作为C标准库的一部分很容易使用。主要的缺点是它不支持用户定义的数据类型。printf
也存在安全问题,尽管GCC中的__attribute__ ((format (printf, ...))) (见https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html)在一定程度上缓解了这些问题。POSIX扩展向printf添加了i18n所需的位置参数,但它不是C99的一部分,可能在一些平台上不可用。
iostreams
iostreams的主要问题最好通过一个例子来说明
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
相比printf来说,输入输出流在键盘输入时的打字要多很多。
printf("%.2f\n", 1.23456);
FastFormat的作者Matthew Wilson将其称为“箭头地狱”。iostreams按设计不支持位置参数。
好的一面是iostreams支持用户定义的类型,虽然是安全的,但错误处理有些笨拙。
Boost Format
这是一个非常强大的库,它支持类似于printf的格式字符串和位置参数。它主要的缺点是性能。根据各种基准测试,它的速度比这里考虑的其他方法慢得多。Boost Format还存在着过长的构建时间和严重的代码膨胀问题(请参阅
)。FastFormat
这是一个有趣的库,它快速、安全并支持位置参数。然而,它的局限性很大,如它的作者所提及的
无法适应当前设计的三项特性包括
- 前导零(或任何其他非空格填充)
- 八进制/十六进制编码
- 运行时宽度/对齐指定
它也相当庞大,并且对 STLSoft 有严重的依赖,这可能会在有些项目中使用时过于严格。
Boost Spirit.Karma
这实际上不是一个格式化库,但我还是决定将其包括在此处以保持完整性。由于使用了 iostreams,因此它与将文本字面量与参数混合的问题相似。这个库的速度相当快,但在 Karma 自己的基准测试中,在整数格式化方面慢于使用格式字符串编译的 fmt::format_to
,请参见每秒将一亿个整数转换为字符串。
许可证
{fmt}采用MIT 许可证发行。
文档许可证
文档中的格式化字符串语法部分基于 Python string 模块文档。因此,该文档采用可在doc/python-license.txt中找到的 Python 软件基金会许可证分发。仅在分发 {fmt} 的文档时适用。
维护者
{fmt}库由 Victor Zverovich (vitaut) 和 Jonathan Müller (foonathan) 维护,许多其他人也提供了贡献。有关一些命名人士的信息,请参阅贡献者和发行版。如果您在名单中未列出或提及不当,请告知我们,我们将更正。