{fmt}
并编写了一个小程序来看看它如何处理大型容器。看起来 fmt::print()
(最终将输出发送到 stdout
)在内部首先将整个结果组成一个字符串。在下面的测试程序中,我使用每个条目消耗 100 字节的格式字符串来格式化 10,000,000 大小的 vector<char>
,在开始将结果转储到 stdout
之前,积累了完整的 100 * 10,000,000 = 1 GB RAM。虽然你无法从我的测试程序的输出中看出,但格式化和输出结果所花费的 1.7 秒几乎全部都花在了格式化上——而不是输出上。 (如果您不重定向到 /dev/null,则在开始打印到标准输出之前会有很长的暂停。)如果您尝试构建管道工具,这不是一个好的行为。
Q1。我确实在文档中看到了一些对
fmt::format_to()
的引用。是否可以以某种方式用于在格式化完成之前开始流式传输并丢弃结果,从而避免完整结果的核心组合?
Q2。继续沿着这条线探索,而不是传递一个容器,有没有一种方法可以传递两个迭代器(可能指向一个非常大文件的开头和结尾)并将该数据通过 {fmt} 进行处理(从而避免必须首先将整个文件读入内存)?
#include <iostream>
#include <vector>
#include "fmt/format.h"
#include "fmt/ranges.h"
#include "time.h"
using namespace std;
inline long long
clock_monotonic_raw() {
struct timespec ct;
clock_gettime(CLOCK_MONOTONIC_RAW, &ct);
return ct.tv_sec * 1000000000LL + ct.tv_nsec;
}
inline double
dt() {
static long long t0 = 0;
if (t0 == 0) {
t0 = clock_monotonic_raw();
return 0.0;
}
long long t1 = clock_monotonic_raw();
return (t1 - t0) / 1.0e9;
}
int main(int argc, char** argv) {
fprintf(stderr, "%10.6f: ENTRY\n", dt());
vector<char> v;
for (int i = 0; i < 10'000'000; ++i)
v.push_back('A' + i % 26);
string pad(98, ' ');
fprintf(stderr, "%10.6f: INIT\n", dt());
fmt::print(pad + "{}\n", fmt::join(v, "\n" + pad));
fprintf(stderr, "%10.6f: DONE\n", dt());
return 0;
}
matt@dworkin:fmt_test$ g++ -o mem_fmt -O3 -I ../fmt/include/ mem_fmt.cpp ../fmt/libfmt.a
matt@dworkin:fmt_test$ ./mem_fmt > /dev/null
0.000000: ENTRY
0.034582: INIT
1.769687: DONE
[运行时从另一个窗口]
matt@dworkin:fmt_test$ ps -aux | egrep 'COMMAND|mem_fmt' | grep -v grep
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
matt 30292 2.8 6.2 1097864 999208 pts/0 S+ 17:40 0:01 ./mem_fmt
注意 VSZ 1.097864 GB
当前版本的{fmt}有一个优化,允许直接写入流缓冲区。目前它仅适用于内置类型和字符串类型。一旦更广泛地启用,在您的示例中几乎不会分配额外的内存,
fmt::print
将仅使用 C 流缓冲区。
此优化也针对
P3107R5中的
std::print
提出
允许有效地实现 std::print.