V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
algas
V2EX  ›  Linux

Linux 多线程程序虚拟内存占用过大有什么问题吗?

  •  
  •   algas · 2016-10-09 11:40:21 +08:00 · 11730 次点击
    这是一个创建于 2968 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近写了一个程序,总是在计算规模比较大的时候异常退出
    [1] 47393 segmentation fault ./a.out 300 0.5

    发现退出总是发生在启动多线程的部分,多线程部不需要额外申请内存。
    在退出发生前,我观察到系统程序虚拟内存比较高, RSS ~ 80G , VSZ ~ 135G 。
    机器只有 128G 物理内存,没有分配 swap 分区和文件, VSZ 明显超过了物理内存,不知道是不是因为这个原因导致程序退出的,求指点。


    > ulimit -a

    -t: cpu time (seconds) unlimited
    -f: file size (blocks) unlimited
    -d: data seg size (kbytes) unlimited
    -s: stack size (kbytes) unlimited
    -c: core file size (blocks) 0
    -m: resident set size (kbytes) unlimited
    -u: processes 515250
    -n: file descriptors 1024
    -l: locked-in-memory size (kbytes) 64
    -v: address space (kbytes) unlimited
    -x: file locks unlimited
    -i: pending signals 515250
    -q: bytes in POSIX msg queues 819200
    -e: max nice 0
    -r: max rt priority 0
    -N 15: unlimited
    第 1 条附言  ·  2016-10-09 21:26:10 +08:00
    发现一些有用的知识

    2 、 segfault at 和 error 4 这两条信息可以得出是内存读出错, 4 的意义如下,可以对照参考:
    bit2:值为 1 表示是用户态程序内存访问越界,值为 0 表示是内核态程序内存访问越界
    bit1: 值为 1 表示是写操作导致内存访问越界,值为 0 表示是读操作导致内存访问越界
    bit0: 值为 1 表示没有足够的权限访问非法地址的内容,值为 0 表示访问的非法地址根本没有对应的页面,也就是无效地址

    抄自: http://blog.csdn.net/zhaohaijie600/article/details/45246569

    大概意思是说我的程序在用户态,对非法地址,写操作了....?
    第 2 条附言  ·  2016-10-10 00:16:23 +08:00
    感谢大家指点,学习到很多。

    我大概找到原因了,问题在于 int 型变量溢出了....

    int N = 220 * 220;
    double *correlationMatrix = malloc(N * N * sizeof(double));

    这里我没有检查 malloc 是否成功了,因为 N*N 会溢出 32 位整数,所以并没有申请到内存,
    所以多线程部分一写数据就挂了。
    43 条回复    2016-10-15 09:45:44 +08:00
    wayslog
        1
    wayslog  
       2016-10-09 11:50:34 +08:00
    那个……老兄……你开了多少线程……没记错的话,每个线程默认会分配 8M 的空间。而且,线程过多其实并不是一件好事儿, IO bound 的话考虑开核心数量的 2x 到 4x , CPU bound 的话干脆开 1x 就行……
    ryd994
        2
    ryd994  
       2016-10-09 12:20:35 +08:00 via Android
    虚拟内存不一定占用物理内存,不说 swap , mmap 也是计入的。
    不过如果是内存不足的话应该会 oom 异常。 segfault 建议还是先查内存 bug ,上 valgrind
    有可能是多线程有 bug 啊,特别是竞态导致超预期数据
    algas
        3
    algas  
    OP
       2016-10-09 12:43:13 +08:00
    @wayslog
    40 线程, cpu 是 12 物理核心,两路服务器,应该没有超开的。
    退出前会开启 20 到 30 多个线程。
    algas
        4
    algas  
    OP
       2016-10-09 12:49:47 +08:00
    @ryd994
    感觉上更像是 stack 之类的爆了,说实话我对这个不太了解,但是我已经完全手动申请释放内存了。
    异常退出的部分没有申请内存,多线程部分只是计算不同序列的平均值之类的东西,读写都是不同位置的内存。
    这个程序是同样使用 40 线程计算小一点的系统是完全正常的,不像存在内存泻露的样子。

    另外就是,小规模情况下没有问题,出现问题的计算规模都要占用一半以上的物理内存, valgrind 是不是没有办法对付这种情况?
    ryd994
        5
    ryd994  
       2016-10-09 13:04:31 +08:00 via Android
    @algas 估计会很慢
    能不能用更少线程计算大问题呢?
    试试至少跑一下,有些内存错误小规模可能神不知鬼不觉没有爆发,但 valgrind 查非法访问一看就知道了
    icylord
        6
    icylord  
       2016-10-09 13:13:33 +08:00
    排除代码 bug 了嘛
    reus
        7
    reus  
       2016-10-09 13:27:51 +08:00
    用 rust 写就没有 segfault 了,理论上
    wayslog
        8
    wayslog  
       2016-10-09 13:37:26 +08:00
    @reus 你试试 let mut v = [1u8; 1024*1024*1024*4+1];
    wayslog
        9
    wayslog  
       2016-10-09 13:38:55 +08:00
    @reus 我们不排除 segfault ,该爆栈还是要爆,但是 rust 的极小 runtime 会拦截这些 segfault ,改成 panic ,能更好的 debug 。

    另外上一个提问的安全解答是: let mut v = box [1u8; 1024*1024*1024*4+1];
    algas
        10
    algas  
    OP
       2016-10-09 14:03:58 +08:00
    @ryd994
    我尝试一下。


    @icylord
    之前都已经在用来出结果了,最近找了个配置很好的机器,才发现这种问题。


    编译参数:
    g++ -O3 -fopenmp -mavx -mfma -fPIC --share -g -I ./ testa.cpp -o libtesta.so

    gcc basic/*.c *.c -O3 -DSFMT_MEXP=19937 -lpthread -lm -Wall -fopenmp -mavx -mfma -L ./ -ltesta -Wl,-rpath=.

    C 和 C++混编,但是程序还没有进入到 c++的库就退出了。
    xiaozhaoz
        11
    xiaozhaoz  
       2016-10-09 14:06:19 +08:00
    确认系统 /proc/sys/vm/overcommit_* 设置。

    overcommit* 控制了进程占用虚拟空间 和 系统物理内存的关系。
    xiaozhaoz
        12
    xiaozhaoz  
       2016-10-09 14:09:26 +08:00
    cat /proc/meminfo , 贴出来看看
    algas
        13
    algas  
    OP
       2016-10-09 14:09:53 +08:00
    @xiaozhaoz

    这 overcommnit_*文件如下,这个完全不明白是什么意思。

    [14:08]:ls /proc/sys/vm/overcommit_*
    /proc/sys/vm/overcommit_kbytes /proc/sys/vm/overcommit_memory /proc/sys/vm/overcommit_ratio

    cat /proc/sys/vm/overcommit_*
    0
    0
    50
    algas
        14
    algas  
    OP
       2016-10-09 14:10:46 +08:00
    @xiaozhaoz

    [14:10]:cat /proc/meminfo
    MemTotal: 131915360 kB
    MemFree: 131296756 kB
    MemAvailable: 131385392 kB
    Buffers: 20536 kB
    Cached: 173024 kB
    SwapCached: 0 kB
    Active: 151716 kB
    Inactive: 104916 kB
    Active(anon): 63772 kB
    Inactive(anon): 2520 kB
    Active(file): 87944 kB
    Inactive(file): 102396 kB
    Unevictable: 68 kB
    Mlocked: 68 kB
    SwapTotal: 0 kB
    SwapFree: 0 kB
    Dirty: 0 kB
    Writeback: 0 kB
    AnonPages: 63200 kB
    Mapped: 53380 kB
    Shmem: 3200 kB
    Slab: 72120 kB
    SReclaimable: 26112 kB
    SUnreclaim: 46008 kB
    KernelStack: 10416 kB
    PageTables: 5484 kB
    NFS_Unstable: 0 kB
    Bounce: 0 kB
    WritebackTmp: 0 kB
    CommitLimit: 65957680 kB
    Committed_AS: 151684 kB
    VmallocTotal: 34359738367 kB
    VmallocUsed: 499908 kB
    VmallocChunk: 34291843068 kB
    HardwareCorrupted: 0 kB
    AnonHugePages: 10240 kB
    HugePages_Total: 0
    HugePages_Free: 0
    HugePages_Rsvd: 0
    HugePages_Surp: 0
    Hugepagesize: 2048 kB
    DirectMap4k: 891976 kB
    DirectMap2M: 103849984 kB
    DirectMap1G: 31457280 kB
    xiaozhaoz
        15
    xiaozhaoz  
       2016-10-09 14:19:46 +08:00
    CommitLimit: 65957680 kB
    Committed_AS: 151684 kB

    单一进程,最多只允许 malloc() 66G 虚拟内存, 超过就会 malloc 失败。

    echo 1 > /proc/sys/vm/overcommit_memory

    再试试看。
    ryd994
        16
    ryd994  
       2016-10-09 14:20:47 +08:00 via Android
    @xiaozhaoz 楼主说的 vsz 和你说的虚拟内存根本不是一个东西, windows 下的虚拟内存, Linux 下叫 swap

    @algas 我又查了一下,你这应就是 bug 导致的访问错误
    如果 oom 的话, kernal log 里会有记录,比如: http://askubuntu.com/questions/399458/out-of-memory-when-booting-ubuntu-or-any-linux-distro-from-live-usb
    alqaz
        17
    alqaz  
       2016-10-09 14:29:40 +08:00
    是不是应该还是程序有 bug, 既然跑大一点计算才出问题,可不可能是只有这种情况下某些代码才会执行,然后触发了某个 bug?
    xiaozhaoz
        18
    xiaozhaoz  
       2016-10-09 14:35:27 +08:00   ❤️ 1
    @ryd994
    如果楼主是 ps 命令看到的 VSZ , 那就是虚拟内存。
    VSZ virtual memory size of the process in KiB (1024-byte units). Device mappings are currently excluded

    楼主确实没说清楚。
    如果整个系统只占用 80G RSS ,一般不会 OOM ,但内存 zone 情况不好说。
    所以 dmesg 或者 /var/下面的 log 看一眼才能确定。
    xiaozhaoz
        19
    xiaozhaoz  
       2016-10-09 14:37:10 +08:00
    楼主的 overcommit_memory 设置是 0 , 所以该程序的虚拟内存 overcommit limit 是动态计算的, 不是 66G 。
    如果 overcommit_memory == 2, 超过 66G 会立即异常。
    xiaozhaoz
        20
    xiaozhaoz  
       2016-10-09 14:40:04 +08:00
    @ryd994 通过楼主提供的信息,收到是 signal 11 。 所以初步认为是 overcommit , 而不是 oom , oom 是 signal 9.
    ryd994
        21
    ryd994  
       2016-10-09 14:43:47 +08:00 via Android
    @xiaozhaoz 所以我还是倾向于程序有 bug 然后非法访问
    WKPlus
        22
    WKPlus  
       2016-10-09 14:47:51 +08:00
    CommitLimit 只有在 overcommit_memory 是 2 的时候才生效啊,现在 overcommit_memory 是 0 , CommitLimit 是多少没关系。
    可以 dmesg 看下有没有 OOM ,我觉得还是程序本身有问题的可能性比较大。
    xiaozhaoz
        23
    xiaozhaoz  
       2016-10-09 14:50:45 +08:00
    @WKPlus

    The Linux kernel supports the following overcommit handling modes

    0 - Heuristic overcommit handling. Obvious overcommits of
    address space are refused. Used for a typical system. It
    ensures a seriously wild allocation fails while allowing
    overcommit to reduce swap usage. root is allowed to
    allocate slightly more memory in this mode. This is the
    default.

    1 - Always overcommit. Appropriate for some scientific
    applications. Classic example is code using sparse arrays
    and just relying on the virtual memory consisting almost
    entirely of zero pages.

    2 - Don't overcommit. The total address space commit
    for the system is not permitted to exceed swap + a
    configurable amount (default is 50%) of physical RAM.
    Depending on the amount you use, in most situations
    this means a process will not be killed while accessing
    pages but will receive errors on memory allocation as
    appropriate.

    Useful for applications that want to guarantee their
    memory allocations will be available in the future
    without having to initialize every page.
    WKPlus
        24
    WKPlus  
       2016-10-09 14:55:24 +08:00
    @xiaozhaoz 你贴这段话什么意思呢?
    xiaozhaoz
        25
    xiaozhaoz  
       2016-10-09 14:59:19 +08:00
    @WKPlus ,

    overcommit 的意思是是否允许过量使用虚存。

    所以
    0 是不予许,也就是超过一定的虚存会分配失败,但是一个复杂的算法计算上限。
    1 是允许, 也就是虚存可以一直分配下去,知道进程的地址空间,或者 oom 发生。
    2 是老模式, 就是 ( 50% * 物理内存) + swap 计算虚存使用上限。
    WKPlus
        26
    WKPlus  
       2016-10-09 15:04:06 +08:00
    @xiaozhaoz 那么我说的 “ CommitLimit 只有在 overcommit_memory 是 2 的时候才生效啊,现在 overcommit_memory 是 0 , CommitLimit 是多少没关系” 没错啊。我是问你为啥突然贴那个给我
    stephenyin
        27
    stephenyin  
       2016-10-09 17:40:32 +08:00
    你这些线程是会频繁启停么? 查查是不是有分支没有 join 或 detach 这些线程!
    algas
        28
    algas  
    OP
       2016-10-09 21:13:03 +08:00
    抱歉一直在外面忙,不在电脑前面


    @ryd994
    貌似用 2 两个线程也算不了更大的情况,退出前 1s 左右记录到的结果如下, ps aux | grep -i pid
    username pid cpu mem vsz rss
    username 1991 99.9 57.4 105758172 75775140 pts/0 R+ 14:21 6:58 ./a.out 260 0.5
    algas
        29
    algas  
    OP
       2016-10-09 21:13:09 +08:00
    @xiaozhaoz
    虽然我看不太明白你的意思,大概是说程序自身有问题吧?下面是 dmesg 最后一部分的内容,希望有用。
    [10523563.169856] a.out[1948]: segfault at 5b9a92500 ip 00000000004034f3 sp 00002b7021521e60 error 6 in a.out[400000+5000]
    [10523563.169885] a.out[1947]: segfault at 56530c140 ip 00000000004034f3 sp 00002b7021320e60 error 6 in a.out[400000+5000]
    [10523563.170921] a.out[1954]: segfault at ffffffffb47b7b80 ip 00000000004034f3 sp 00002b7022127e60 error 7
    [10523563.171253] a.out[1951]: segfault at 6b7125040 ip 00000000004034f3 sp 00002b7021b24e60 error 6
    [10523563.171259] in a.out[400000+5000]
    [10523563.171267] in a.out[400000+5000]
    [10524098.407830] a.out[2857]: segfault at 20e363320 ip 00000000004034f3 sp 00002b84d78a7e60 error 6
    [10524098.407838] a.out[2858]: segfault at 27b29cd20 ip 00000000004034f3 sp 00002b84d7aa8e60 error 6
    [10524098.407840] in a.out[400000+5000]
    [10524098.407849] a.out[2862]: segfault at 42ef83520 ip 00000000004034f3 sp 00002b84d82ace60 error 6

    [10524098.407856] a.out[2856]: segfault at 1a1429920 ip 00000000004034f3 sp 00002b84d76a6e60 error 6
    [10524098.407859] in a.out[400000+5000]
    [10524098.407859] in a.out[400000+5000]


    [10524098.407867] a.out[2863]: segfault at 49bebcf20 ip 00000000004034f3 sp 00002b84d84ade60 error 6 in a.out[400000+5000]
    [10524098.407876] in a.out[400000+5000]
    algas
        30
    algas  
    OP
       2016-10-09 21:14:53 +08:00
    @stephenyin

    不会频繁启动终止,这是一个计算程序,在进入多线程的部分会程序停掉,主线程里是有 join 的。
    algas
        31
    algas  
    OP
       2016-10-09 21:17:21 +08:00
    @xiaozhaoz

    [10548380.539504] a.out[29535]: segfault at fffffffd08858920 ip 00000000004034f3 sp 00002bab3bb23e60 error 7 in a.out[400000+5000]

    这里面的 error 7 是指 signal=7 吗?
    algas
        32
    algas  
    OP
       2016-10-09 21:21:05 +08:00
    @WKPlus

    dmesg 的信息贴到上面的回复了,里面有 error 6 也有 error 7 ,这个看不懂....
    xiaozhaoz
        33
    xiaozhaoz  
       2016-10-09 23:08:23 +08:00 via Android
    error 6,7 指的是用户空间写物理内存异常。不是 signal x

    现在可以肯定,是不是 oom

    你这个问题好查,每次异常的 eip 都相同,都是 0x4034f3 ,所以反汇编,根据偏移可以推算到代码,才能确切定位是什么问题。
    algas
        34
    algas  
    OP
       2016-10-10 00:07:41 +08:00
    @xiaozhaoz
    谢谢你,我应该是找到问题,很低级,但是并不常有机会碰到,我放到 append 里了。
    ryd994
        35
    ryd994  
       2016-10-10 01:53:19 +08:00 via Android
    @algas 主要是我一般没做过这么大规模的。有 segfault 找 valgrind 是我的习惯反射了。
    其次就是, int 是 16 位………
    long 是 32
    long long 是 64
    你这个地方应该用 size_t
    size_t 的意思是当前环境用于表示地址范围大小,保证不溢出
    ryd994
        36
    ryd994  
       2016-10-10 01:55:21 +08:00 via Android
    correlationMatrix
    是做计算物理的前辈么?希望能交个朋友
    alqaz
        37
    alqaz  
       2016-10-10 09:36:36 +08:00
    N*N*8=18G 多,很好奇这是哪方面使用的程序。
    araraloren
        38
    araraloren  
       2016-10-10 09:53:49 +08:00
    @alqaz ~~楼主不是大量计算么,保存中间的计算结果什么的吧,不过一次性申请这么多内存也是醉了。。
    algas
        39
    algas  
    OP
       2016-10-10 12:10:58 +08:00
    @ryd994
    基本上就是这个问题, linux C int 和 long int 都是 32 位, long long int 是 64 位。

    malloc 的参数是 size_t 类型,其实就是 unsigned ,比 int 多一位可用。
    所以 malloc 单次申请内存大小受到 size_t 的位数限制,我这里把 size_t 益出了。

    如果需要申请连续的一大块内存,可能要研究以下 mmap 函数。

    我是学统计物理的,主要对付 toy model ,和真正计算实际问题的那种计算物理不太一样。
    algas
        40
    algas  
    OP
       2016-10-10 12:19:32 +08:00
    @alqaz
    @araraloren

    这个是用来保存矩阵的一个数组,是完全填充的。
    类似于计算晶格上的振动模式,如果考虑的系统是 200*200 个原子的正方形区域(很小对不对),
    关联矩阵就需要 4e4 * 4e4 个元素,基本溢出 int 类型了。

    分块计算的倒是可以避免一次申请这么大的内存,但是程序复杂性估计就要上天了 2333
    ryd994
        41
    ryd994  
       2016-10-10 12:25:47 +08:00 via Android
    @algas mmap 不是用来分配内存的啊……… map 文件………
    内存管理算法一般都不能很好的处理特大段内存分配,真的没有办法拆么?
    我是学材料工程和计算机双专业,所以计算物理这边多少沾得上
    ryd994
        42
    ryd994  
       2016-10-10 12:49:44 +08:00 via Android
    @algas 你这样写
    size_t N = 220 * 220;
    size_t 用于表示内存范围的时候不可能溢出,否则就违背定义了。如果我没有记错,在 64 位系统上是 unsigned long long

    你的问题是: int 只有 32767 的上限, 220*220=4e4 明显超了,后面 N * N * sizeof(double) 里 N*N 先计算得中间结果,中间结果 promote 为 size_t 与 double 大小相乘后作为 malloc 的参数。第一步时,因为双方都是 int ,所以没有范围调整,所以这里也超了。

    重申: size_t 不是 uint16 ,是 uint64 ,如果 size_t 溢出,则当前平台上根本不可能对该内存空间寻址,换言之根本不存在 size_t 溢出而其他方法能正常分配内存的情况。
    algas
        43
    algas  
    OP
       2016-10-15 09:45:44 +08:00
    @ryd994 你说的是对的,我之前看了以下 size_t 的类型,就没有管是不是 64 位的 unsigned 。
    不过我这里 int 是 32 位的。
    sizeof(int) = 4.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   994 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 19:11 · PVG 03:11 · LAX 11:11 · JFK 14:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.