【发布时间】:2021-04-18 12:59:18
【问题描述】:
我需要从a space-separated human-readable file 读取一系列数字并进行一些数学运算,但我在读取文件时遇到了一些真正奇怪的内存行为。
如果我读到这些数字并立即丢弃它们......
#include <fstream>
int main(int, char**) {
std::ifstream ww15mgh("ww15mgh.grd");
double value;
while (ww15mgh >> value);
return 0;
}
我的程序根据 valgrind 分配 59MB 内存,相对于文件大小线性缩放:
$ g++ stackoverflow.cpp
$ valgrind --tool=memcheck --leak-check=yes ./a.out 2>&1 | grep total
==523661== total heap usage: 1,038,970 allocs, 1,038,970 frees, 59,302,487
但是,如果我使用ifstream >> string 代替然后使用sscanf 来解析字符串,我的内存使用情况看起来更合理:
#include <fstream>
#include <string>
#include <cstdio>
int main(int, char**) {
std::ifstream ww15mgh("ww15mgh.grd");
double value;
std::string text;
while (ww15mgh >> text)
std::sscanf(text.c_str(), "%lf", &value);
return 0;
}
$ g++ stackoverflow2.cpp
$ valgrind --tool=memcheck --leak-check=yes ./a.out 2>&1 | grep total
==534531== total heap usage: 3 allocs, 3 frees, 81,368 bytes allocated
为了排除 IO 缓冲区的问题,我尝试了 ww15mgh.rdbuf()->pubsetbuf(0, 0);(这使得程序需要很长时间并且仍然进行 59MB 的分配)和 pubsetbuf 使用巨大的堆栈分配缓冲区(仍然是 59MB )。当使用来自gcc-libs 10.2.0 的/usr/lib/libstdc++.so.6 和来自glibc 2.32 的/usr/lib/libc.so.6 时,在gcc 10.2.0 和clang 11.0.1 上编译时,该行为会重现。系统语言环境设置为en_US.UTF-8,但如果我设置环境变量LC_ALL=C,这也会重现。
我第一次注意到问题的 ARM CI 环境是在 Ubuntu Focal 上使用 GCC 9.3.0、libstdc++6 10.2.0 和 libc 2.31 进行交叉编译的。
在advice in the comments 之后,我尝试了 LLVM 的 libc++,并在原始程序中获得了完全理智的行为:
$ clang++ -std=c++14 -stdlib=libc++ -I/usr/include/c++/v1 stackoverflow.cpp
$ valgrind --tool=memcheck --leak-check=yes ./a.out 2>&1 | grep total
==700627== total heap usage: 3 allocs, 3 frees, 8,664 bytes allocated
因此,这种行为似乎是 GCC 的 fstream 实现所独有的。在构建或使用ifstream 时我可以做些什么不同的事情来避免在GNU 环境中编译时分配大量堆内存?这是他们<fstream> 中的错误吗?
正如在 cmets 讨论中发现的那样,程序的实际内存占用是完全正常的 (84kb),它只是分配和释放相同的一小部分内存数十万次,这在使用像 ASAN 这样的自定义分配器时会产生问题避免重复使用堆空间。我发布了a follow-up question,询问如何在“ASAN”级别处理此类问题。
gitlab project that reproduces the issue in its CI pipeline 由 Stack Overflow 用户 @KamilCuk 慷慨捐赠。
【问题讨论】:
-
我不知道,但是 - 只是出于好奇 - 在检查内存消耗是否取决于数据大小时,我会准备两到十倍长的文件......
-
看起来像
istream类的operator >>(double&)实现中的内存泄漏......仍然不知道如何修复它。 :( -
我不认为您的代码有什么问题,您可以尝试将 Clang 与 libc++ 一起使用,看看该实现是否使用更少的内存。
-
在嵌入式 ARM 板上,它开始成为一个问题。在 ASAN(为每个分配中添加填充)下进行测试时,它实际上破坏了我的 ARM CI 运行器。而且,它是输入文件大小的 6.5 倍,这太荒谬了。
-
对,程序的实际内存使用量是完全合理的 81 KB。所以我认为你真正的问题是如何配置 ASAN 以更有效地处理这种分配模式。
标签: c++ memory fstream libstdc++