c语言sscanf函数的用法是什么
276
2022-11-30
apr_thread使用内存之谜
问题起因
问题的起因是因为使用了一个apr的服务,产生了巨大的virtual memory,具体的表现是,在top中可以看到该进程的VIRT和RES,VIRT比实际上使用的要大很多。
在google上找到如下文章
have noticed that my multithreaded APR program consumes a *very*large amount of virtual memory per thread and I can't think of areason why. I am running Debian 3.1 (latest kernel 2.6.8-3-686) and Itried the test program below with both the APR 0.9.x that comes withDebian and with the latest APR 1.2.x version.我注意到多线程的APR程序每一个线程会消耗很大的virtual memory,我没有想明白是为什么。我使用的是Debian 3.1的系统,基于apr版本 APR 0.9.x和 APR 1.2.xIn 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 RAMAm 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
样例中运行100个apr_thread
这个是样例代码的结果。
可以看到RES仅仅1.7M,但是VIRT确实906M,将近一个G了。这个差距也太大了。确实有文章里说的问题。
2. 执行thread_join
如果将所有线程join,如下
#define THREAD_NUM 100void* 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 100void* 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 100void* 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 100void* 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
与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 100static 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 100static 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的参数影响较大。
优化解决方案
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~