在大多数 Linux 编译器中,
std::vector
将以 2 倍扩展。我已经在 g++11 上进行了测试。
所以,我假设当 std::vector 从 N 扩展到 2N 时会发生以下情况:
2*X
元素数组分配 2*N
字节内存。因此向量总共可能需要 3*X
字节。N
大小的数组复制或移动到 2*N
大小的数组中。2*X
字节的内存。作为结论,向量的内存使用峰值应该是
3*X
。因此,我编写了以下代码来测试此行为。
在代码中,我们将 2M 个
Writes
实例放入向量中。并将使用 getPeakRSS
记录此程序中的最大内存占用。
我会不时打印
Current memory usage
。 usage
信息来自getPeakRSS
。 expected
是通过将 Writes
的大小乘以向量 ww
的当前计数来计算的。 amp
字段为 usage / expected
。如果我是正确的,我可以发现 amp
的最大值约为 3。
#include <fstream>
#include <sstream>
#include <string>
#include <iostream>
#include <vector>
struct Writes16 {
uint64_t w1;
uint64_t w2;
};
struct Writes32 {
Writes16 w1;
Writes16 w2;
};
struct Writes128 {
Writes32 w1;
Writes32 w2;
Writes32 w3;
Writes32 w4;
};
struct Writes {
Writes128 w1;
Writes16 w2;
uint64_t w3;
};
/*
* Author: David Robert Nadeau
* Site: http://NadeauSoftware.com/
* License: Creative Commons Attribution 3.0 Unported License
* http://creativecommons.org/licenses/by/3.0/deed.en_US
*/
#if defined(_WIN32)
#include <windows.h>
#include <psapi.h>
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
#include <unistd.h>
#include <sys/resource.h>
#if defined(__APPLE__) && defined(__MACH__)
#include <mach/mach.h>
#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
#include <fcntl.h>
#include <procfs.h>
#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
#include <stdio.h>
#endif
#else
#error "Cannot define getPeakRSS( ) or getCurrentRSS( ) for an unknown OS."
#endif
/**
* Returns the peak (maximum so far) resident set size (physical
* memory use) measured in bytes, or zero if the value cannot be
* determined on this OS.
*/
size_t getPeakRSS( )
{
#if defined(_WIN32)
/* Windows -------------------------------------------------- */
PROCESS_MEMORY_COUNTERS info;
GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) );
return (size_t)info.PeakWorkingSetSize;
#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
/* AIX and Solaris ------------------------------------------ */
struct psinfo psinfo;
int fd = -1;
if ( (fd = open( "/proc/self/psinfo", O_RDONLY )) == -1 )
return (size_t)0L; /* Can't open? */
if ( read( fd, &psinfo, sizeof(psinfo) ) != sizeof(psinfo) )
{
close( fd );
return (size_t)0L; /* Can't read? */
}
close( fd );
return (size_t)(psinfo.pr_rssize * 1024L);
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* BSD, Linux, and OSX -------------------------------------- */
struct rusage rusage;
getrusage( RUSAGE_SELF, &rusage );
#if defined(__APPLE__) && defined(__MACH__)
return (size_t)rusage.ru_maxrss;
#else
return (size_t)(rusage.ru_maxrss * 1024L);
#endif
#else
/* Unknown OS ----------------------------------------------- */
return (size_t)0L; /* Unsupported. */
#endif
}
std::vector<Writes> ww;
int main() {
static_assert(sizeof(Writes) == 152);
const size_t K = 1024;
const size_t M = 1024 * K;
size_t location = 0;
for (int m = 0; m < 2; m++) {
for (int j = 0; j < M; j++) {
ww.emplace_back(Writes());
if (j % 4096 == 0) {
if (j == 0) {
location = (size_t)&ww[0];
}
size_t usage = getPeakRSS();
size_t expected = ww.size() * 152;
double rate = usage * 1.0 / expected;
printf("Current memory usage %d: %u %.4fMB expected %u %.4fMB amp %.4f cap %u\n", m, usage, usage / 1024.0 / 1024.0, expected, expected / 1024.0 / 1024.0, rate, ww.capacity());
}
}
}
// To ensure the elements are copied. There is no realloc happening.
printf("Location prev %u end %u\n", location, (size_t)&ww[0]);
return 0;
}
但是,我发现了以下内容。看来在扩展过程中,内存使用峰值只有
2*X
字节,而不是3*X
。
...
Current memory usage 0: 163250176 155.6875MB expeted 158761112 151.4064MB amp 1.0283 cap 1048576
Current memory usage 1: 322584576 307.6406MB expeted 159383704 152.0001MB amp 2.0239 cap 2097152
...
我想知道为什么会保存额外的
X
字节?
=====
我知道 std::vector 的扩展方式完全取决于编译器。所以我想知道 g++ 中会发生什么优化,从而使一些内存分配“消除”。
区别在于残余内存和虚拟内存。
让我们通过从一个较大的分配(假设为 1M)到下一个更高的分配(2M)来简化所发生的情况:
AS
(例如在 prlimit
中)或 VIRT
(例如在 top
中)RSS 使用峰值出现在步骤 4 结束时。此时,1M 元素的旧映射已完全映射。然而,新映射仅填充了现有 1M 元素的一半。分配的额外容量尚未被触及,因此未分配物理内存页,因此不考虑 RSS。这是您观察到的放大器系数 2。