进程与线程栈大小的调整

原创
2013/12/05 21:44
阅读数 8.7K

问题现象

首先看一个现象,最近在嵌入式项目开发中发现的,下面是设备的内存总量及使用:

总量是24M左右,项目主程序大小1M不到,但是在默认的系统环境设置下,程序运行起来后的top看起来是这样:

VSZ的大小是221MB,所以计算出来的内存使用百分比是935.4% = 221MB/24MB.VSZ表示程序使用的总虚拟内存空间大小。在很久之前也曾遇到过同样的现象,只是当时没有去深入了解为什么。刚开始发现这个221MB时,非常地吃惊,无论如何也想不通为什么1M大小不到的程序会需要使用到200M以上的内存空间。

现象分析

程序是一个多线程的程序,而且有不少的线程是由线程再次创建的,系统环境是linux2.6.32的内核。通过对其它单进程的VSZ大小观察,发现VSZ的大小好像与程序使用的线程数目成正比关系。因此想到可以通过使用Posix Pthread库的pthread_attr_setstacksize接口来修改线程栈的大小,于是将20多个线程的栈的大小修改为512KB,虽然有点麻烦,但是再次运行,VSZ的确大幅地减少为30MB左右。

在分析解决问题的过程中,了解到另一个影响应用程序运行栈大小的系统设置:ulimit -s。通过这个命令可以查看系统默认的栈大小以及修改应用运行时的栈大小,默认的8192KB。这里再次分析上面的现象。linux系统中使用clone机制来实现线程,实际上线程就是一个轻量的进程,因此其栈大小依然是遵循系统的ulimit设置来配置的。所以20多个线程的程序在默认8M的栈大小设置下,会使用到200M左右的虚拟内存空间,包括程序的所有栈空间以及数据内存、堆内存和代码内存。

那么,就可以通过ulimit -s命令修改默认的栈大小,从而达到与调用pthread_attr_setstacksize接口一样的目的和效果。使用ulimit -s 512后,主程序使用的VSZ降低为25M左右,这是因为主线程使用的栈大小也被降低。

但是使用ulimit的一个后果就是它会影响到同一环境(同一shell或者终端)下后续启动的所有程序,如果修改成启动时设置的话就会影响到整个系统,这显然不是想要的。有两个方法可以能消除这个影响:

  1. 为需要修改栈大小的程序单独编写一个shell脚本,在程序启动前调用ulimit -s。因为子shell的环境不会影响到父shell,所以设置不会改变外部环境。
  2. 在程序运行前执行ulimit -s修改需要的栈大小,在程序运行后再次执行ulimit -s修改回原来的栈大小。

PS:虽然降低了程序使用的虚拟内存的大小,但是我还是有一个很大的疑问:

程序使用200M多的虚拟内存和使用20M多的虚拟内存,运行效果没有什么变化,好像没有带来什么有用的性能改善。我能想要的“好处”就是系统在进行地址转换和页面管理时会高效一点,但难道不应该有一些更重要的性能提升吗,不然除了让top内容中的VSZ和%MEM栏更好看合理点外,没必要去费精力调整?期待有人能帮忙解惑。

展开阅读全文
打赏
8
107 收藏
分享
加载中
水海云博主

引用来自“难易”的评论

引用来自“水海云”的评论

引用来自“难易”的评论

概念混乱,不知所云。。。
vsz指的是虚拟内存用量,也就是你的程序实际使用的内存数量,包括位于物理内存的rsz部分,还有位于swap的被系统交换给硬盘的部分,vsz可以超过物理内存总量

%MEM = rsz/ 物理内存总量,不可能超过100%,所以超过100%是出问题了,进程信息明显被破坏了。

你把栈的上限调大后,马上就可以看出你这个进程的vsz为30M,回复正常了。所以你原先的程序是栈异常,导致操作系统统计你的%MEM会超过100%,建议你用valgrind来测试一下你的程序为什么异常了,有可能是栈溢出,因为你的递归调用过深,或者声明了太大的临时变量导致的。

1.在嵌入式项目中,使用的linux和rootfs往往差别巨大,所以相同的top命令得到的输出可能不同。就我现在手上的两个项目来说,其中一个的(文中提到的)%MEM=VSZ/物理内存总量,因为系统的init进程以及文章图中的top进程都是符合这个算式的;而另一个项目中的就是你所说的%MEM = rsz/ 物理内存总量,此时的值的确不会超过100%。
2. 可能你没有仔细阅读文章,我是把栈的大小大幅调小,不是调大,而且调整前后程序都长期运行正常,所以程序不存在因此而导致的栈异常或者栈溢出问题,你的假设也不成立。
3. 对于VSZ的概念,你的理解可能并不适用于嵌入式。因为对于我的项目,物理内存24M加上Flash空间16M总共也不超过50M,但是程序的VSZ却可能高达200M,所以VSZ在这里更准确的含义是指程序的虚拟地址空间使用量。
对于linux系统而言,尤其在嵌入式中,因为开发和可定制,以及各种不同的内核版本,同样的行为完全有可能会产生不一样的现象,因此最好是具体问题具体分析。

好,经过你的解释总算明白了,把栈调小,所以vsz也变小了,原来如此。
那我试着回答你的问题。VSZ并不是内存的瓶颈,RSZ才是,由于虚拟内存系统的性质,不用的VSZ部分只有一个地址,在不使用之前是不会在内存或硬盘上开辟空间的。RSZ代表实际使用的物理内存,如果你的优化能减少RSZ的占用,同时你的系统中有其他进程需要RSZ,会产生优化效果。

看来我调整栈的大小真没有实际的作用。物理内存是最紧缺的资源,肯定地使用的越少越好,只是在项目中只要程序能正常运行,一般不会去考虑优化这块。因为一般除了应用进程,也不会有其它进程了,而且优化RSZ这事也比较麻烦。
2013/12/08 12:50
回复
举报
水海云博主

引用来自“中山野鬼”的评论

引用来自“难易”的评论

引用来自“中山野鬼”的评论

速度问题和你现在程序占用内存的大小并没有太多关联。内存大小和速度之间产生的关联,是cache的调度机制与数据在存储空间的分布有关。这个和你的程序逻辑有关。哈。
栈的大小一旦超越了一级cache,基本上没有太多变化,毕竟栈上空间的利用,除了长跳,否则是连续使用的方式。现在的机器架构设计,最这块优化的很不错。逻辑上的空间很大,只要连续访问,无论什么方向,系统硬件都能给出不错的解决方案。
存储和速度有关联的例子,说两个,代码方面,你的两个函数,频繁反问,恰好cache的调度策略和两个代码的存储位置存在不协调,那么每进一次函数,就会从外部mem重新载入,这个就会慢。整体程序的反映就是速度上的抖动。
数据方面,总是长距离的跳转,或依赖多个存储位置的数据才能参与当前的整体逻辑处理,正好也和cache的调度策略不协调,那么会降低计算速度。
嵌入式方面,如果这块真要较真,需要根据不同模块的计算逻辑做存储上的调优。哈

大部分系统的瓶颈都在IO和缓存上,就是导数据那部分。单纯的代码执行产盛瓶颈,只在计算密集型应用上会有。

哈这个我认同。所以看系统目标了。如果系统目标是应用灵活性为主的,这些都不是个事,快速实现是正道。如果是服务抗压力为主的,那么分模块做计算和存储调优,是重点。单纯的一个逻辑设计,并不是值钱的东西。小规模的东西,很多团队都能做,大规模的,持续扩展的东西,是团队的竞争力。还是业务为导向,没有持续的业务,任何产品没有持续开发优化的空间。哈。

的确,业务是关键,任何的优化都应该是在已经实现了业务逻辑后再来考虑设计。在嵌入式系统项目中,应用规模一般不会太大,但灵活性比较大,能在有限的资源内实现好业务功能就不会过多地考虑速度优化问题,更多考虑的是稳定性问题。
依照你的分析,我对程序栈空间大小的分配实际上并没有什么真正意义的作用,因为嵌入式使用的ARM芯片,一级cache也就32KB,栈的大小肯定超过它。
2013/12/08 12:45
回复
举报

引用来自“难易”的评论

引用来自“中山野鬼”的评论

速度问题和你现在程序占用内存的大小并没有太多关联。内存大小和速度之间产生的关联,是cache的调度机制与数据在存储空间的分布有关。这个和你的程序逻辑有关。哈。
栈的大小一旦超越了一级cache,基本上没有太多变化,毕竟栈上空间的利用,除了长跳,否则是连续使用的方式。现在的机器架构设计,最这块优化的很不错。逻辑上的空间很大,只要连续访问,无论什么方向,系统硬件都能给出不错的解决方案。
存储和速度有关联的例子,说两个,代码方面,你的两个函数,频繁反问,恰好cache的调度策略和两个代码的存储位置存在不协调,那么每进一次函数,就会从外部mem重新载入,这个就会慢。整体程序的反映就是速度上的抖动。
数据方面,总是长距离的跳转,或依赖多个存储位置的数据才能参与当前的整体逻辑处理,正好也和cache的调度策略不协调,那么会降低计算速度。
嵌入式方面,如果这块真要较真,需要根据不同模块的计算逻辑做存储上的调优。哈

大部分系统的瓶颈都在IO和缓存上,就是导数据那部分。单纯的代码执行产盛瓶颈,只在计算密集型应用上会有。

哈这个我认同。所以看系统目标了。如果系统目标是应用灵活性为主的,这些都不是个事,快速实现是正道。如果是服务抗压力为主的,那么分模块做计算和存储调优,是重点。单纯的一个逻辑设计,并不是值钱的东西。小规模的东西,很多团队都能做,大规模的,持续扩展的东西,是团队的竞争力。还是业务为导向,没有持续的业务,任何产品没有持续开发优化的空间。哈。
2013/12/07 16:45
回复
举报

引用来自“中山野鬼”的评论

速度问题和你现在程序占用内存的大小并没有太多关联。内存大小和速度之间产生的关联,是cache的调度机制与数据在存储空间的分布有关。这个和你的程序逻辑有关。哈。
栈的大小一旦超越了一级cache,基本上没有太多变化,毕竟栈上空间的利用,除了长跳,否则是连续使用的方式。现在的机器架构设计,最这块优化的很不错。逻辑上的空间很大,只要连续访问,无论什么方向,系统硬件都能给出不错的解决方案。
存储和速度有关联的例子,说两个,代码方面,你的两个函数,频繁反问,恰好cache的调度策略和两个代码的存储位置存在不协调,那么每进一次函数,就会从外部mem重新载入,这个就会慢。整体程序的反映就是速度上的抖动。
数据方面,总是长距离的跳转,或依赖多个存储位置的数据才能参与当前的整体逻辑处理,正好也和cache的调度策略不协调,那么会降低计算速度。
嵌入式方面,如果这块真要较真,需要根据不同模块的计算逻辑做存储上的调优。哈

大部分系统的瓶颈都在IO和缓存上,就是导数据那部分。单纯的代码执行产盛瓶颈,只在计算密集型应用上会有。
2013/12/07 12:14
回复
举报

引用来自“水海云”的评论

引用来自“难易”的评论

概念混乱,不知所云。。。
vsz指的是虚拟内存用量,也就是你的程序实际使用的内存数量,包括位于物理内存的rsz部分,还有位于swap的被系统交换给硬盘的部分,vsz可以超过物理内存总量

%MEM = rsz/ 物理内存总量,不可能超过100%,所以超过100%是出问题了,进程信息明显被破坏了。

你把栈的上限调大后,马上就可以看出你这个进程的vsz为30M,回复正常了。所以你原先的程序是栈异常,导致操作系统统计你的%MEM会超过100%,建议你用valgrind来测试一下你的程序为什么异常了,有可能是栈溢出,因为你的递归调用过深,或者声明了太大的临时变量导致的。

1.在嵌入式项目中,使用的linux和rootfs往往差别巨大,所以相同的top命令得到的输出可能不同。就我现在手上的两个项目来说,其中一个的(文中提到的)%MEM=VSZ/物理内存总量,因为系统的init进程以及文章图中的top进程都是符合这个算式的;而另一个项目中的就是你所说的%MEM = rsz/ 物理内存总量,此时的值的确不会超过100%。
2. 可能你没有仔细阅读文章,我是把栈的大小大幅调小,不是调大,而且调整前后程序都长期运行正常,所以程序不存在因此而导致的栈异常或者栈溢出问题,你的假设也不成立。
3. 对于VSZ的概念,你的理解可能并不适用于嵌入式。因为对于我的项目,物理内存24M加上Flash空间16M总共也不超过50M,但是程序的VSZ却可能高达200M,所以VSZ在这里更准确的含义是指程序的虚拟地址空间使用量。
对于linux系统而言,尤其在嵌入式中,因为开发和可定制,以及各种不同的内核版本,同样的行为完全有可能会产生不一样的现象,因此最好是具体问题具体分析。

好,经过你的解释总算明白了,把栈调小,所以vsz也变小了,原来如此。
那我试着回答你的问题。VSZ并不是内存的瓶颈,RSZ才是,由于虚拟内存系统的性质,不用的VSZ部分只有一个地址,在不使用之前是不会在内存或硬盘上开辟空间的。RSZ代表实际使用的物理内存,如果你的优化能减少RSZ的占用,同时你的系统中有其他进程需要RSZ,会产生优化效果。
2013/12/07 12:12
回复
举报
速度问题和你现在程序占用内存的大小并没有太多关联。内存大小和速度之间产生的关联,是cache的调度机制与数据在存储空间的分布有关。这个和你的程序逻辑有关。哈。
栈的大小一旦超越了一级cache,基本上没有太多变化,毕竟栈上空间的利用,除了长跳,否则是连续使用的方式。现在的机器架构设计,最这块优化的很不错。逻辑上的空间很大,只要连续访问,无论什么方向,系统硬件都能给出不错的解决方案。
存储和速度有关联的例子,说两个,代码方面,你的两个函数,频繁反问,恰好cache的调度策略和两个代码的存储位置存在不协调,那么每进一次函数,就会从外部mem重新载入,这个就会慢。整体程序的反映就是速度上的抖动。
数据方面,总是长距离的跳转,或依赖多个存储位置的数据才能参与当前的整体逻辑处理,正好也和cache的调度策略不协调,那么会降低计算速度。
嵌入式方面,如果这块真要较真,需要根据不同模块的计算逻辑做存储上的调优。哈
2013/12/06 23:13
回复
举报
水海云博主

引用来自“难易”的评论

概念混乱,不知所云。。。
vsz指的是虚拟内存用量,也就是你的程序实际使用的内存数量,包括位于物理内存的rsz部分,还有位于swap的被系统交换给硬盘的部分,vsz可以超过物理内存总量

%MEM = rsz/ 物理内存总量,不可能超过100%,所以超过100%是出问题了,进程信息明显被破坏了。

你把栈的上限调大后,马上就可以看出你这个进程的vsz为30M,回复正常了。所以你原先的程序是栈异常,导致操作系统统计你的%MEM会超过100%,建议你用valgrind来测试一下你的程序为什么异常了,有可能是栈溢出,因为你的递归调用过深,或者声明了太大的临时变量导致的。

1.在嵌入式项目中,使用的linux和rootfs往往差别巨大,所以相同的top命令得到的输出可能不同。就我现在手上的两个项目来说,其中一个的(文中提到的)%MEM=VSZ/物理内存总量,因为系统的init进程以及文章图中的top进程都是符合这个算式的;而另一个项目中的就是你所说的%MEM = rsz/ 物理内存总量,此时的值的确不会超过100%。
2. 可能你没有仔细阅读文章,我是把栈的大小大幅调小,不是调大,而且调整前后程序都长期运行正常,所以程序不存在因此而导致的栈异常或者栈溢出问题,你的假设也不成立。
3. 对于VSZ的概念,你的理解可能并不适用于嵌入式。因为对于我的项目,物理内存24M加上Flash空间16M总共也不超过50M,但是程序的VSZ却可能高达200M,所以VSZ在这里更准确的含义是指程序的虚拟地址空间使用量。
对于linux系统而言,尤其在嵌入式中,因为开发和可定制,以及各种不同的内核版本,同样的行为完全有可能会产生不一样的现象,因此最好是具体问题具体分析。
2013/12/06 19:28
回复
举报
概念混乱,不知所云。。。
vsz指的是虚拟内存用量,也就是你的程序实际使用的内存数量,包括位于物理内存的rsz部分,还有位于swap的被系统交换给硬盘的部分,vsz可以超过物理内存总量

%MEM = rsz/ 物理内存总量,不可能超过100%,所以超过100%是出问题了,进程信息明显被破坏了。

你把栈的上限调大后,马上就可以看出你这个进程的vsz为30M,回复正常了。所以你原先的程序是栈异常,导致操作系统统计你的%MEM会超过100%,建议你用valgrind来测试一下你的程序为什么异常了,有可能是栈溢出,因为你的递归调用过深,或者声明了太大的临时变量导致的。

2013/12/06 16:19
回复
举报
水海云博主

引用来自“junsun”的评论

我看了下我的VPS,里面的是:stack size (kbytes, -s) 10240,那我java进程里面启动很多线程,这个线程跟你说的线程是一样的么?

我不清楚java中的线程实现是使用怎样的一个机制,如果是封装或者使用Posix的Pthread机制那就是一样的,此时,可以使用pthread_attr_getstacksize接口(或者java的封装接口)来获取默认的线程栈大小,就可以判断线程的默认设置是否与进程的一样。
2013/12/06 13:45
回复
举报
我看了下我的VPS,里面的是:stack size (kbytes, -s) 10240,那我java进程里面启动很多线程,这个线程跟你说的线程是一样的么?
2013/12/06 09:43
回复
举报
更多评论
打赏
10 评论
107 收藏
8
分享
在线直播报名
返回顶部
顶部