我需要读取一系列数字以空格分隔的人类可读文件 https://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/ww15mgh.grd.z并做了一些数学计算,但我在读取文件时遇到了一些真正奇怪的内存行为。
如果我读到这些数字并立即丢弃它们......
#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 10.2.0 https://archlinux.org/packages/core/x86_64/gcc/ and clang 11.0.1 https://archlinux.org/packages/extra/x86_64/clang/使用时/usr/lib/libstdc++.so.6
from gcc-libs 10.2.0 https://archlinux.org/packages/core/x86_64/gcc-libs/ and /usr/lib/libc.so.6
from glibc 2.32 https://archlinux.org/packages/core/x86_64/glibc/。系统区域设置设置为en_US.UTF-8
但如果我设置环境变量,这也会重现LC_ALL=C
.
我第一次注意到这个问题的ARM CI环境是在Ubuntu Focal上使用交叉编译的海湾合作委员会9.3.0 https://packages.ubuntu.com/focal/g++-arm-linux-gnueabihf, libstdc++6 10.2.0 https://packages.ubuntu.com/focal/libstdc++6-armhf-cross and libc 2.31 https://packages.ubuntu.com/focal/libc6-dev-armhf-cross.
下列的评论中的建议 https://stackoverflow.com/questions/65703206/why-does-ifstream-double-allocate-so-much-memory?noredirect=1#comment116168866_65703206,我尝试了 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>
?
正如在评论讨论中发现的,程序的实际内存占用是完全正常的(84kb),它只是分配和释放相同的一小块内存数十万次,这在使用像 ASAN 这样避免重新分配的自定义分配器时会产生问题。 - 使用堆空间。我已经发布后续问题 https://stackoverflow.com/questions/65721208/how-do-i-exclude-allocations-in-a-tight-loop-from-asan询问“ASAN”层面如何应对此类问题。
A gitlab 项目在其 CI 管道中重现了该问题 https://gitlab.com/drmoose/why-does-gccs-ifstream-double-allocate-so-much-memory由 Stack Overflow 用户慷慨捐赠@卡米尔库克 https://stackoverflow.com/users/9072753/kamilcuk.