当前位置:网站首页>apr_thread使用内存之谜
apr_thread使用内存之谜
2022-08-10 20:41:00 【安安爸Chris】
问题起因
问题的起因是因为使用了一个apr的服务,产生了巨大的virtual memory,具体的表现是,在top中可以看到该进程的VIRT和RES,VIRT比实际上使用的要大很多。
在google上找到如下文章
https://lists.apache.org/thread/yvwxmssr90y2nnozj949w5gsg9gpxn1p
怕有人访问不了,直接把原文贴在这里
I have noticed that my multithreaded APR program consumes a *very*
large amount of virtual memory per thread and I can't think of a
reason why. I am running Debian 3.1 (latest kernel 2.6.8-3-686) and I
tried the test program below with both the APR 0.9.x that comes with
Debian and with the latest APR 1.2.x version.
我注意到多线程的APR程序每一个线程会消耗很大的virtual memory,我没有想明白是为什么。
我使用的是Debian 3.1的系统,基于apr版本 APR 0.9.x和 APR 1.2.x
In both cases I end up with around 800 MB of virtual RAM for 100 threads:
20385 ivanr 16 0 802m 956 1800 S 0.0 0.4 0:00.02 test
在如下两个例子中,运行100个线程大致使用了800MBvirtual RAM
Am I doing something wrong or is this a bug? Any help is greatly appreciated!
void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
apr_thread_exit(thread, 0);
}
int main(int argc, const char * const argv[]) {
apr_pool_t *pool;
int i;
apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);
apr_pool_create(&pool, NULL);
for(i = 0; i < 100; i++) {
apr_thread_t *thread = NULL;
apr_thread_create(&thread, NULL, thread_worker, NULL, pool);
}
apr_sleep(1000 * 1000 * 1000);
}
VIRT & RES & SHR
如果你执行top看一下进程的内存占用,会得到如下几列
VIRT
它是程序“需要”内存的总和,包含了加载的动态库内存,与其他进程共享的内存,以及分配给他的内存;
但是如果程序申请了100m但是仅仅使用了10m,这里还是按照100m计算统计RES
它是程序正在使用内存的总和,一个主要区别是如果程序申请了100m但是仅仅使用了10m,这里是按照10m计算统计SHR
加载的库的内存,如果仅仅是使用库里的一些函数,但是整个库还是会加载入内存。
一个“空白”程序,如下,它会占用多少VIRT,RES,SHR?
int main(int argc, const char * const argv[]) {
return 0;
}
它占用了4164kB的VIRT, 368KB的RES, 268KB的SHR。
SHR基本上为动态库加载。一个“什么都没有的"c程序,基础的链接是这样的
那么程序”自身“多大呢? RES-SHR = 368- 268 = 100KB
程序总共”需要“的大小 VIRT = 4164kB
如果我们申请1m内存再释放它,是否可以跟上述程序暂用内存一致呢?
来试一下
int main(int argc, const char * const argv[]) {
char *p = (char *)calloc(1048576, sizeof(char));
free(p); // break 1
return 0; // break 2
}
这里我们增加2个断点,来观察内存使用的变化;
break1
与上面”空白“程序相比,此时RES是增加1k; 但是VIRT也增长了1k
break2
与上面”空白“程序相比,VIRT是回到了原点。 RES仍有所增加,但是可以理解,毕竟多了几行代码,code也是要占用空间的;
SHR也增加了一点(这个是为什么?)
换成malloc呢?
int main(int argc, const char * const argv[]) {
char *p;
for (int i=0; i<1048576; i++)
p = malloc(sizeof(char));
//free(p);
return 0;
}
在不释放内存的情况下,RES可以涨到33M;VIRT涨到37M
在释放后,内存情况大体也相当; 说明多次调用malloc还是有一定开销的。(这里换成calloc也一样)
int main(int argc, const char * const argv[]) {
char *p;
for (int i=0; i<1048576; i++) {
p = malloc(sizeof(char));
free(p);
}
return 0;
}
apr_thread测试
1. 使用样例代码(不做thread_join)
上面的帖子跟遇到的情况有些类似,所以我把他的样例程序跑了一下
#include <apr-1/apr.h>
#include <apr-1/apr_pools.h>
#include <apr-1/apr_thread_proc.h>
#define THREAD_NUM 100
void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
apr_thread_exit(thread, 0);
}
int main(int argc, const char * const argv[]) {
apr_pool_t *pool;
int i;
apr_status_t status = 0;
apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);
apr_pool_create(&pool, NULL);
apr_thread_t *thread[THREAD_NUM];
for(i = 0; i < THREAD_NUM; i++) {
apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool);
}
apr_sleep(1000 * 1000 * 1000);
}
样例中运行100个apr_thread
这个是样例代码的结果。
可以看到RES仅仅1.7M,但是VIRT确实906M,将近一个G了。这个差距也太大了。确实有文章里说的问题。
2. 执行thread_join
如果将所有线程join,如下
#define THREAD_NUM 100
void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
apr_thread_exit(thread, 0);
}
int main(int argc, const char * const argv[]) {
apr_pool_t *pool;
int i;
apr_status_t status = 0;
apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);
apr_pool_create(&pool, NULL);
apr_thread_t *thread[THREAD_NUM];
for(i = 0; i < THREAD_NUM; i++) {
apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool);
}
//
for(i = 0; i < THREAD_NUM; i++) {
apr_thread_join(&status, thread[i]);
}
apr_sleep(1000 * 1000 * 1000);
return 0;
}
RES1.3M,几乎差不多;VIRT120M,比不join少了很多;但是仍有近100倍差距
3. 再做apr_pool_clear
如果在thread_join后面再调用apr_pool_clear(pool),主动clear一下pool呢?
几乎没有变化。
4. 移除apr_thread_exit
既然最后都有thread_join,把线程中的apr_thread_exit移除试试呢?
代码如下
#define THREAD_NUM 100
void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
//apr_thread_exit(thread, 0);
return NULL;
}
int main(int argc, const char * const argv[]) {
apr_pool_t *pool;
int i;
apr_status_t status = 0;
apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);
apr_pool_create(&pool, NULL);
apr_thread_t *thread[THREAD_NUM];
for(i = 0; i < THREAD_NUM; i++) {
apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool);
}
for(i = 0; i < THREAD_NUM; i++) {
apr_thread_join(&status, thread[i]);
}
apr_sleep(1000 * 1000 * 1000);
return 0;
}
对比发现;RES和SHR几乎没有变化;但是VIRT居然只有52M,比之前少了一半还多。
这么看来,如果最后有join的动作,还是不要执行apr_thread_exit的比较节省内存。
5. 将线程detach运行
#define THREAD_NUM 100
void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
//apr_thread_exit(thread, 0);
return NULL;
}
int main(int argc, const char * const argv[]) {
apr_pool_t *pool;
int i;
apr_status_t status = 0;
apr_threadattr_t *thread_attr;
apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);
apr_pool_create(&pool, NULL);
apr_threadattr_create(&thread_attr, pool);
apr_threadattr_detach_set(thread_attr, 1);
apr_thread_t *thread[THREAD_NUM];
for(i = 0; i < THREAD_NUM; i++) {
assert(apr_thread_create(&thread[i], thread_attr, thread_worker, NULL, pool)==APR_SUCCESS);
}
apr_sleep(1000 * 1000 * 1000);
return 0;
}
与joinable的thread几乎一样。
如果还原apr_thread_exit,也跟joinable的thread几乎一样
所以detach对内存几乎没有影响。
6. 限制thread的栈大小
#define THREAD_NUM 100
void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
//apr_thread_exit(thread, 0);
return NULL;
}
int main(int argc, const char * const argv[]) {
apr_pool_t *pool;
int i;
apr_status_t status = 0;
apr_threadattr_t *thread_attr;
apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);
apr_pool_create(&pool, NULL);
apr_threadattr_create(&thread_attr, pool);
//apr_threadattr_detach_set(thread_attr, 1);
apr_threadattr_stacksize_set(thread_attr,10240); //10k
apr_thread_t *thread[THREAD_NUM];
for(i = 0; i < THREAD_NUM; i++) {
assert(apr_thread_create(&thread[i], thread_attr, thread_worker, NULL, pool)==APR_SUCCESS);
}
for(i = 0; i < THREAD_NUM; i++) {
apr_thread_join(&status, thread[i]);
}
apr_sleep(1000 * 1000 * 1000);
return 0;
}
每一个线程的栈大小限制为10k,好像没有什么变化??
每个thread的栈默认是多少呢? 一般来说,默认为8M,即8,388,608bytes
可以通过ulimit -s或者ulimit -a看一下
可以通过**apr_threadattr_stacksize_set(thread_attr,8388608);**来还原一下默认值,跑出来结果跟不设置差不多,VIRT为52M;
好像真没有什么变化。
pthread测试
作为对比,我们同样的使用pthread来做一轮测试
1. 不做pthread_join
#include <pthread.h>
#include <assert.h>
#include <zconf.h>
#define THREAD_NUM 100
static void *thread_worker(void *data) {
pthread_exit(NULL);
return NULL;
}
int main(int argc, const char *const argv[]) {
int i = 0;
pthread_t thread[THREAD_NUM];
for (i = 0; i < THREAD_NUM; i++) {
assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0);
}
sleep(1000000);
return 0;
}
与apr_thread对比,VIRT893M 基本相当; RES 1.2M比之前的1.7M了一点;SHR 0.4M也小了一点;
注释掉 pthread_exit的结果如下:
VIRT826M,RES 1.1M,SHR 0.4M
做pthread_join
static void *thread_worker(void *data) {
pthread_exit(NULL);
return NULL;
}
int main(int argc, const char *const argv[]) {
int i = 0;
pthread_t thread[THREAD_NUM];
for (i = 0; i < THREAD_NUM; i++) {
assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0);
}
for (i = 0; i < THREAD_NUM; i++) {
pthread_join(thread[i], NULL);
}
sleep(1000000);
return 0;
}
相比apr_thread,VIRT 106M/ RES 0.7M/SHR 0.5M 都小一些
3. 去除pthread_exit
#define THREAD_NUM 100
static void *thread_worker(void *data) {
//pthread_exit(NULL);
return NULL;
}
int main(int argc, const char *const argv[]) {
int i = 0;
pthread_t thread[THREAD_NUM];
for (i = 0; i < THREAD_NUM; i++) {
assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0);
}
for (i = 0; i < THREAD_NUM; i++) {
pthread_join(thread[i], NULL);
}
sleep(1000000);
return 0;
}
5. 限制stacksize
#define THREAD_NUM 100
static void *thread_worker(void *data) {
//pthread_exit(NULL);
return NULL;
}
int main(int argc, const char *const argv[]) {
int i = 0;
int stacksize = 10240;
pthread_t thread[THREAD_NUM];
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, stacksize);
for (i = 0; i < THREAD_NUM; i++) {
assert(pthread_create(&thread[i], &attr, thread_worker, NULL) == 0);
}
pthread_attr_destroy(&attr);
sleep(1000000);
return 0;
}
相对于不设置大小,内存各项指标基本不变。(同apr_thread结果)
6. 结论
pthread的内存占用略比apr_thead少,但是整体都出现VIRT比RES大几百倍的情况; 尤其是pthread_exit的使用,似乎对VIRT的参数影响较大。
优化解决方案
边栏推荐
- C 语言 时间函数使用技巧(汇总)
- npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
- 【CMU博士论文】视频多模态学习:探索模型和任务复杂性,152页pdf
- TortoiseSVN小乌龟的使用
- PostgreSQL — Installation and Common Commands
- 七月券商金工精选
- MySQL数据库的主从复制部署详解
- (10) Sequence and deserialization of image data
- XML小讲
- 通用线程:POSIX 线程详解,第 2部分
猜你喜欢
【一致性hash】负载均衡器分发请求
Knowledge map Knowledge Graph
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
一次由groovy引起的fullGC问题排查
Echart饼状图标注遮盖解决方案汇总
npm warn config global `--global`, `--local` are deprecated. use `--location=global` instead.
paddle 35 paddledetection保存训练过程中的log信息
Pt/CeO2 monatomic nanoparticles enzyme | H - rGO - Pt @ Pd NPs enzyme | carbon nanotube load platinum nanoparticles peptide modified nano enzyme | leukemia antagonism FeOPtPEG composite nano enzyme
【语义分割】2015-UNet MICCAI
[mysql] 深入分析MySQL版本控制MVCC规则
随机推荐
PostgreSQL — Installation and Common Commands
mysql服务器参数设置
PostgreSQL — 安装及常用命令
OPPO Enco X2 迎来秋季产品升级 旗舰体验全面拉满
【图像分类】2018-MobileNetV2
CGO 初步认知和基本数据类型转换
npm‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件
数据标注太昂贵?这个方法可以用有限的数据训练模型实现基于文本的ReID!
实施MES管理系统前,这三个问题要考虑好
Kubernetes 笔记 / 入门 / 生产环境 / 用部署工具安装 Kubernetes / 用 kubeadm 启动集群 / 用 kubeadm 创建集群
【语义分割】2016-SegNet TPAMI
win7开机有画面进系统黑屏怎么办
【go】依赖注入
壁仞推出全球最大算力芯片,号称以7nm超越英伟达4nm最新GPU
【nvm】【node多版本管理工具】使用说明和踩坑(exit status 1)
组合导航精度分析
机器学习笔记:t-SNE
机器学习模型验证:被低估的重要一环
Ferritin particle-loaded raltitrexed/pemetrexed/sulfadesoxine/adamantane (scientific research reagent)
Auto.js中的悬浮窗