- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我一直在研究 Windows 和 Linux (Debian) 中一些 C++ REST API 框架的内存使用情况。我特别看过这两个框架:cpprestsdk和 cpp-httplib .在这两者中,都创建了一个线程池并用于为请求提供服务。
我从 cpp-httplib 中获取了线程池实现并将其放在下面的最小工作示例中,以显示我在 Windows 和 Linux 上观察到的内存使用情况。
#include <cassert>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
using namespace std;
// TaskQueue and ThreadPool taken from https://github.com/yhirose/cpp-httplib
class TaskQueue {
public:
TaskQueue() = default;
virtual ~TaskQueue() = default;
virtual void enqueue(std::function<void()> fn) = 0;
virtual void shutdown() = 0;
virtual void on_idle() {};
};
class ThreadPool : public TaskQueue {
public:
explicit ThreadPool(size_t n) : shutdown_(false) {
while (n) {
threads_.emplace_back(worker(*this));
cout << "Thread number " << threads_.size() + 1 << " has ID " << threads_.back().get_id() << endl;
n--;
}
}
ThreadPool(const ThreadPool&) = delete;
~ThreadPool() override = default;
void enqueue(std::function<void()> fn) override {
std::unique_lock<std::mutex> lock(mutex_);
jobs_.push_back(fn);
cond_.notify_one();
}
void shutdown() override {
// Stop all worker threads...
{
std::unique_lock<std::mutex> lock(mutex_);
shutdown_ = true;
}
cond_.notify_all();
// Join...
for (auto& t : threads_) {
t.join();
}
}
private:
struct worker {
explicit worker(ThreadPool& pool) : pool_(pool) {}
void operator()() {
for (;;) {
std::function<void()> fn;
{
std::unique_lock<std::mutex> lock(pool_.mutex_);
pool_.cond_.wait(
lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; });
if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
fn = pool_.jobs_.front();
pool_.jobs_.pop_front();
}
assert(true == static_cast<bool>(fn));
fn();
}
}
ThreadPool& pool_;
};
friend struct worker;
std::vector<std::thread> threads_;
std::list<std::function<void()>> jobs_;
bool shutdown_;
std::condition_variable cond_;
std::mutex mutex_;
};
// MWE
class ContainerWrapper {
public:
~ContainerWrapper() {
cout << "Destructor: data map is of size " << data.size() << endl;
}
map<pair<string, string>, double> data;
};
void handle_post() {
cout << "Start adding data, thread ID: " << std::this_thread::get_id() << endl;
ContainerWrapper cw;
for (size_t i = 0; i < 5000; ++i) {
string date = "2020-08-11";
string id = "xxxxx_" + std::to_string(i);
double value = 1.5;
cw.data[make_pair(date, id)] = value;
}
cout << "Data map is now of size " << cw.data.size() << endl;
unsigned pause = 3;
cout << "Sleep for " << pause << " seconds." << endl;
std::this_thread::sleep_for(std::chrono::seconds(pause));
}
int main(int argc, char* argv[]) {
cout << "ID of main thread: " << std::this_thread::get_id() << endl;
std::unique_ptr<TaskQueue> task_queue(new ThreadPool(40));
for (size_t i = 0; i < 50; ++i) {
cout << "Add task number: " << i + 1 << endl;
task_queue->enqueue([]() { handle_post(); });
// Sleep enough time for the task to finish.
std::this_thread::sleep_for(std::chrono::seconds(5));
}
task_queue->shutdown();
return 0;
}
当我运行这个 MWE 并查看 Windows 与 Linux 中的内存消耗时,我得到了下图。对于 Windows,我使用了
perfmon
获取私有(private)字节值。在 Linux 中,我使用了
docker stats --no-stream --format "{{.MemUsage}}
记录容器的内存使用情况。这符合
res
对于来自
top
的过程在容器内运行。从图中可以看出,当一个线程为
map
分配内存时Windows 中的变量
handle_post
函数,当函数在下一次调用函数之前退出时,内存会被返还。这是我天真地期待的行为类型。我没有关于操作系统如何处理当线程保持事件状态时正在线程中执行的函数分配的内存的经验,例如在线程池中。在 Linux 上,看起来内存使用量一直在增长,并且在函数退出时不会返回内存。当所有 40 个线程都被使用,并且还有 10 个任务要处理时,内存使用量似乎停止增长。有人可以从内存管理的角度对 Linux 中发生的事情给出一个高层次的看法,甚至可以提供一些关于在哪里寻找关于这个特定主题的背景信息的提示吗?
rss
的输出值从运行
ps -p <pid> -h -o etimes,pid,rss,vsz
Linux 容器中的每一秒,其中
<pid>
是正在测试的进程的 ID。与
docker stats --no-stream --format "{{.MemUsage}}
的输出相符.
handle_post
从 MWE 中删除了映射具有以下功能并添加包含
#include <cstdlib>
和
#include <cstring>
.现在,
handle_post
函数只是为 500K 分配和设置内存
int
s 大约为 2MiB。
void handle_post() {
size_t chunk = 500000 * sizeof(int);
if (int* p = (int*)malloc(chunk)) {
memset(p, 1, chunk);
cout << "Allocated and used " << chunk << " bytes, thread ID: " << this_thread::get_id() << endl;
cout << "Memory address: " << p << endl;
unsigned pause = 3;
cout << "Sleep for " << pause << " seconds." << endl;
this_thread::sleep_for(chrono::seconds(pause));
free(p);
}
}
我在这里得到相同的行为。在示例中,我将线程数减少到 8 个,将任务数减少到 10 个。下图显示了结果。
valgrind
下运行了示例的
massif
工具。
massif
命令行参数在下图中。我用
--pages-as-heap=yes
运行它,下面的第二张图片,如果没有这个标志,下面的第一张图片。第一个图像表明 ~2MiB 内存分配给(共享)堆作为
handle_post
函数在线程上执行,然后在函数退出时释放。这是我所期望的,也是我在 Windows 上观察到的。我不知道如何用
--pages-as-heap=yes
解释图表然而,即第二张图片。
massif
的输出在第一个图像中,值为
rss
来自
ps
上图中显示的命令。如果我运行 Docker 镜像并使用
docker run --rm -it --privileged --memory="12m" --memory-swap="12m" --name=mwe_test cpp_testing:1.0
将容器内存限制为 12MB ,容器在第 7 次分配时内存不足,并被操作系统杀死。我收到
Killed
在输出中,当我查看
dmesg
时,我看到
Killed process 25709 (cpp_testing) total-vm:529960kB, anon-rss:10268kB, file-rss:2904kB, shmem-rss:0kB
.这表明
rss
值来自
ps
准确地反射(reflect)了进程实际使用的(堆)内存,而
massif
工具正在计算它应该基于的内容
malloc
/
new
和
free
/
delete
调用。这只是我从这个测试中得到的基本假设。我的问题仍然存在,即为什么在
handle_post
时堆内存没有被释放或解除分配,或者看起来没有被释放或释放。函数退出?
main
开始时添加了 5 秒的暂停这是图表中前 ~5 秒的初始平线。看起来,无论线程数如何,在处理第一个任务后都会释放内存,但在任务 2 到 10 之后没有释放内存(保留以供重用?)。这可能表明在执行期间调整了某些内存分配参数任务 1 执行(只是大声思考!)?
MALLOC_ARENA_MAX
在运行示例之前到 1 和 2。这给出了下图中的输出。这是基于答案中对该变量影响的解释所预期的。
最佳答案
许多现代分配器,包括您正在使用的 glibc 2.17 中的分配器,使用多个 arena(一种跟踪空闲内存区域的结构)以避免想要同时分配的线程之间的争用。
释放回一个arena的内存不能被另一个arena分配(除非触发了某种类型的跨arena传输)。
默认情况下,每次新线程进行分配时,glibc 都会分配新的 arenas,直到达到预定义的限制(默认为 8 * CPU 数),如您所见 examining the code .
这样做的一个后果是,在一个线程上分配然后释放的内存可能无法用于其他线程,因为它们使用不同的区域,即使该线程没有做任何有用的工作。
您可以尝试设置 glibc malloc tunable glibc.malloc.arena_max
至 1
为了强制所有线程进入同一个领域,看看它是否改变了你正在观察的行为。
请注意,这与用户空间分配器(在 libc 中)有关,而与操作系统的内存分配无关:操作系统永远不会被告知内存已被释放。即使您强制使用单个 arena,也不意味着用户空间分配器将决定通知操作系统:它可能只是保留内存以满足 future 的请求(也有调整此行为的可调参数)。
但是,在您的测试中使用单个 arena 应该足以防止不断增加的内存占用,因为内存在下一个线程启动之前被释放,因此我们希望它被下一个任务重用,该任务在不同的线程上启动。
最后,值得指出的是,发生的事情高度依赖于条件变量如何通知线程:大概 Linux 使用 FIFO 行为,其中最近排队(等待)的线程将是最后被通知的。这会导致您在添加任务时循环遍历所有线程,从而创建许多竞技场。更有效的模式(出于各种原因)是 LIFO 策略:将最近排队的线程用于下一个作业。这将导致在您的测试中重复使用相同的线程并“解决”问题。
最后说明:许多分配器,但不是您正在使用的旧版 glibc 中的分配器,也实现了每线程缓存,允许分配快速路径在没有任何原子操作的情况下进行。这可以产生与使用多个领域类似的效果,并且随着线程的数量不断扩展。
关于c++ - Windows 与 Linux - C++ 线程池内存使用情况,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63454529/
我在具有 2CPU 和 3.75GB 内存 (https://aws.amazon.com/ec2/instance-types/) 的 c3.large Amazon EC2 ubuntu 机器上运
我想通过用户空间中的mmap-ing并将地址发送到内核空间从用户空间写入VGA内存(视频内存,而不是缓冲区),我将使用pfn remap将这些mmap-ed地址映射到vga内存(我将通过 lspci
在 Mathematica 中,如果你想让一个函数记住它的值,它在语法上是很轻松的。例如,这是标准示例 - 斐波那契: fib[1] = 1 fib[2] = 1 fib[n_]:= fib[n] =
我读到动态内存是在运行时在堆上分配的,而静态内存是在编译时在堆栈上分配的,因为编译器知道在编译时必须分配多少内存。 考虑以下代码: int n; cin>>n; int a[n]; 如果仅在运行期间读
我是 Python 的新手,但我之前还不知道这一点。我在 for 循环中有一个基本程序,它从站点请求数据并将其保存到文本文件但是当我检查我的任务管理器时,我发现内存使用量只增加了?长时间运行时,这对我
我正在设计一组数学函数并在 CPU 和 GPU(使用 CUDA)版本中实现它们。 其中一些函数基于查找表。大多数表占用 4KB,其中一些占用更多。基于查找表的函数接受一个输入,选择查找表的一两个条目,
读入一个文件,内存被动态分配给一个字符串,文件内容将被放置在这里。这是在函数内部完成的,字符串作为 char **str 传递。 使用 gdb 我发现在行 **(str+i) = fgetc(aFil
我需要证实一个理论。我正在学习 JSP/Java。 在查看了一个现有的应用程序(我没有写)之后,我注意到一些我认为导致我们的性能问题的东西。或者至少是其中的一部分。 它是这样工作的: 1)用户打开搜索
n我想使用memoization缓存某些昂贵操作的结果,这样就不会一遍又一遍地计算它们。 两个memoise和 R.cache适合我的需要。但是,我发现缓存在调用之间并不可靠。 这是一个演示我看到的问
我目前正在分析一些 javascript shell 代码。这是该脚本中的一行: function having() { memory = memory; setTimeout("F0
我有一种情况,我想一次查询数据库,然后再将整个数据缓存在内存中。 我得到了内存中 Elasticsearch 的建议,我用谷歌搜索了它是什么,以及如何在自己的 spring boot 应用程序中实现它
我正在研究 Project Euler (http://projecteuler.net/problem=14) 的第 14 题。我正在尝试使用内存功能,以便将给定数字的序列长度保存为部分结果。我正在
所以,我一直在做 Java 内存/注意力游戏作业。我还没有达到我想要的程度,它只完成了一半,但我确实让 GUI 大部分工作了......直到我尝试向我的框架添加单选按钮。我认为问题可能是因为我将 JF
我一直在尝试使用 Flask-Cache 的 memoize 功能来仅返回 statusTS() 的缓存结果,除非在另一个请求中满足特定条件,然后删除缓存。 但它并没有被删除,并且 Jinja 模板仍
我对如何使用 & 运算符来减少内存感到非常困惑。 我可以回答下面的问题吗? clase C{ function B(&$a){ $this->a = &$a; $thi
在编写代码时,我遇到了一个有趣的问题。 我有一个 PersonPOJO,其 name 作为其 String 成员之一及其 getter 和 setter class PersonPOJO { priv
在此代码中 public class Base { int length, breadth, height; Base(int l, int b, int h) { l
Definition Structure padding is the process of aligning data members of the structure in accordance
在 JavaScript Ninja 的 secret 中,作者提出了以下方案,用于在没有闭包的情况下内存函数结果。他们通过利用函数是对象这一事实并在函数上定义一个属性来存储过去调用函数的结果来实现这
我正在尝试找出 map 消耗的 RAM 量。所以,我做了以下事情;- Map cr = crPair.collectAsMap(); // 200+ entries System.out.printl
我是一名优秀的程序员,十分优秀!