【发布时间】:2017-01-11 20:01:30
【问题描述】:
我在 MSVC14 (VS2015) 中观察到 std::unordered_map 的奇怪行为。
考虑以下场景。我创建了一个无序映射并用消耗大量内存的虚拟结构填充它,比如说 1Gb,总共插入了 100k 个元素。然后您开始从地图中删除元素。假设您已经删除了一半的元素,那么您希望释放一半的内存。正确的?错误的!我看到当 map 中的元素数量超过某个阈值时释放内存,在我的例子中是 1443 个元素。
有人可能会说这是 malloc 优化使用 VirtualAllocEx 或 @987654324 从操作系统分配大块@ 实际上它并没有将内存释放回系统,因为优化决定了策略,并且可能不会调用 HeapFree 以供将来重用已分配的内存。
为了消除这种情况,我为 allocate_shared 使用了自定义分配器,它没有成功。所以主要的问题是为什么会发生这种情况以及可以做些什么来“压缩”unordered_map 使用的内存?
代码
#include <windows.h>
#include <memory>
#include <vector>
#include <map>
#include <unordered_map>
#include <random>
#include <thread>
#include <iostream>
#include <allocators>
HANDLE heap = HeapCreate(0, 0, 0);
template <class Tp>
struct SimpleAllocator
{
typedef Tp value_type;
SimpleAllocator() noexcept
{}
template <typename U>
SimpleAllocator(const SimpleAllocator<U>& other) throw()
{};
Tp* allocate(std::size_t n)
{
return static_cast<Tp*>(HeapAlloc(heap, 0, n * sizeof(Tp)));
}
void deallocate(Tp* p, std::size_t n)
{
HeapFree(heap, 0, p);
}
};
template <class T, class U>
bool operator==(const SimpleAllocator<T>&, const SimpleAllocator<U>&)
{
return true;
}
template <class T, class U>
bool operator!=(const SimpleAllocator<T>& a, const SimpleAllocator<U>& b)
{
return !(a == b);
}
struct Entity
{
Entity()
{
_6 = std::string("a", dis(gen));
_7 = std::string("b", dis(gen));
for(size_t i = 0; i < dis(gen); ++i)
{
_9.emplace(i, std::string("c", dis(gen)));
}
}
int _1 = 1;
int _2 = 2;
double _3 = 3;
double _4 = 5;
float _5 = 3.14f;
std::string _6 = "hello world!";
std::string _7 = "A quick brown fox jumps over the lazy dog.";
std::vector<unsigned long long> _8 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
std::map<long long, std::string> _9 = {{0, "a"},{1, "b"},{2, "c"},{3, "d"},{4, "e"},
{5, "f"},{6, "g"},{7, "h"},{8, "e"},{9, "j"}};
std::vector<double> _10{1000, 3.14};
std::random_device rd;
std::mt19937 gen = std::mt19937(rd());
std::uniform_int_distribution<size_t> dis = std::uniform_int_distribution<size_t>(16, 256);
};
using Container = std::unordered_map<long long, std::shared_ptr<Entity>>;
void printContainerInfo(std::shared_ptr<Container> container)
{
std::cout << std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())
<< ", Size: " << container->size() << ", Bucket count: " << container->bucket_count()
<< ", Load factor: " << container->load_factor() << ", Max load factor: " << container->max_load_factor()
<< std::endl;
}
int main()
{
constexpr size_t maxEntites = 100'000;
constexpr size_t ps = 10'000;
stdext::allocators::allocator_chunklist<Entity> _allocator;
std::shared_ptr<Container> test = std::make_shared<Container>();
test->reserve(maxEntites);
for(size_t i = 0; i < maxEntites; ++i)
{
test->emplace(i, std::make_shared<Entity>());
}
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> dis(0, maxEntites);
size_t cycles = 0;
while(test->size() > 0)
{
size_t counter = 0;
std::cout << "Press any key..." << std::endl;
std::cin.get();
while(test->size() > 1443)
{
test->erase(dis(gen));
}
printContainerInfo(test);
std::cout << "Press any key..." << std::endl;
std::cin.get();
std::cout << std::endl;
}
return 0;
}
到目前为止我尝试过的事情:
当负载因子达到某个阈值时尝试重新散列/调整大小 - 在擦除 while 中添加类似这样的内容
if(test->load_factor() < 0.2)
{
test->max_load_factor(1 / test->load_factor());
test->rehash(test->size());
test->reserve(test->size());
printContainerInfo(test);
test->max_load_factor(1);
test->rehash(test->size());
test->reserve(test->size());
}
然后当它没有帮助尝试一些愚蠢的事情时,比如创建临时容器,复制/移动剩余的条目,清除原始条目,然后从临时复制/移动回原始条目。像这样的
if(test->load_factor() < 0.2)
{
Container tmp;
std::copy(test->begin(), test->end(), std::inserter(tmp, tmp.begin()));
test->clear();
test.reset();
test = std::make_shared<Container>();
std::copy(tmp.begin(), tmp.end(), std::inserter(*test, test->begin()));
}
最后,将shared_ptr 替换为allocate_shared 并将SimpleAllocator 实例传递给它。
此外,我还到处修改了STL 代码,例如在std::unordered_map's @ 上调用std::vector::shrink_to_fit 987654337@(unordered_map 的 msvc stl 实现基于list 和vector),它也不起作用。
EDIT001:适用于所有非信徒。以下代码与前面的代码大致相同,但使用std::vector<Entity> 而不是unordered_map。内存被操作系统回收。
#include <memory>
#include <vector>
#include <map>
#include <random>
#include <thread>
#include <iostream>
struct Entity
{
Entity()
{
_6 = std::string("a", dis(gen));
_7 = std::string("b", dis(gen));
for(size_t i = 0; i < dis(gen); ++i)
{
_9.emplace(i, std::string("c", dis(gen)));
}
}
int _1 = 1;
int _2 = 2;
double _3 = 3;
double _4 = 5;
float _5 = 3.14f;
std::string _6 = "hello world!";
std::string _7 = "A quick brown fox jumps over the lazy dog.";
std::vector<unsigned long long> _8 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
std::map<long long, std::string> _9 = {{0, "a"}, {1, "b"}, {2, "c"}, {3, "d"}, {4, "e"},
{5, "f"}, {6, "g"}, {7, "h"}, {8, "e"}, {9, "j"}};
std::vector<double> _10{1000, 3.14};
std::random_device rd;
std::mt19937 gen = std::mt19937(rd());
std::uniform_int_distribution<size_t> dis = std::uniform_int_distribution<size_t>(16, 256);
};
using Container = std::vector<std::shared_ptr<Entity>>;
void printContainerInfo(std::shared_ptr<Container> container)
{
std::cout << std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())
<< ", Size: " << container->size() << ", Capacity: " << container->capacity() << std::endl;
}
int main()
{
constexpr size_t maxEntites = 100'000;
constexpr size_t ps = 10'000;
std::shared_ptr<Container> test = std::make_shared<Container>();
test->reserve(maxEntites);
for(size_t i = 0; i < maxEntites; ++i)
{
test->emplace_back(std::make_shared<Entity>());
}
std::random_device rd;
std::mt19937 gen(rd());
size_t cycles = 0;
while(test->size() > 0)
{
std::uniform_int_distribution<size_t> dis(0, test->size());
size_t counter = 0;
while(test->size() > 0 && counter < ps)
{
test->pop_back();
++counter;
}
++cycles;
if(cycles % 7 == 0)
{
std::cout << "Inflating..." << std::endl;
while(test->size() < maxEntites)
{
test->emplace_back(std::make_shared<Entity>());
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
printContainerInfo(test);
std::cout << std::endl;
}
return 0;
}
【问题讨论】:
-
你怎么知道内存没有被释放?
-
查看任务管理器中的提交大小或 Sysinternals 的 RAMMap 中的“总内存”
-
@kreuzerkrieg 释放的内存实际上并没有从正在运行的进程返回给操作系统。您将无法在任务管理器中看到它。使用 valgrind 之类的工具来检测内存泄漏。
-
a) 没有内存泄漏 b) 所以,正如你所说,如果我用
vector<Entity>替换unordered_map我会看到同样的行为吗?错误的!内存将立即释放到操作系统。此外,如果您的说法是正确的,clean不会释放内存,但它确实会释放内存,此外,当unordered_map中的项目数低于〜1400 项时,为什么操作系统会回收内存? -
@πάνταῥεῖ ῥεῖ,为什么它与要求 Linux 和 glibc 的问题重复,涉及所有领域的东西?
标签: c++ visual-c++ memory-management stl unordered-map