精华内容
下载资源
问答
  • Concur 是 malloc() 和其他标准内存分配调用的可扩展性极强的多线程透明替代品。 线性扩展到任意数量的 CPU 内核。 比 mtmalloc、ptmalloc、Hoard 等更具可扩展性和内存效率。分配速度非常快,没有锁争用,并且比...
  • 原因:为什么malloc本身是线程安全函数(man手册也说明了),但是signal中使用却会产生错误与陷阱,线程是调用malloc并执行,signal也是执行malloc,可是为什么是不安全的? 背景知识:这4个部分留给初学者自己查...

    前言:为什么写这篇博客?

    原因:为什么malloc本身是线程安全函数(man手册也说明了),但是signal中使用却会产生错误与陷阱,线程是调用malloc并执行,signal也是执行malloc,可是为什么是不安全的?

    背景知识:这4个部分留给初学者自己查资料,内容太多,这里简单叙述下,这5块搞清楚就能理解了,而且对于linux学习这几部分是必不可少的,现在可能感觉用不上,等真正做项目你才会发现这些概念有多重要。

    (1)内核态与用户态

    (2)系统调用与C库调用

    (3)可重入与不可重入

    (4)内核对信号的处理方法

    (5)系统调用函数内部的锁

    (6)同步与异步

    1、系统调用函数的线程安全与可重入性

    (1)在man手册中,与系统调用有关的函数都会说明该函数是否线程安全,所以这也是我们写代码需要关注的,而线程安全与函数是否可重入有很大关系,函数可重入一定是线程安全的,线程安全不一定是可重入函数,比如maloc使用递归锁实现了线程安全,但它是不可重入函数,所以不可重入函数可以通过内核锁实现线程安全(锁是系统调用,所以工作在内核态,也叫内核锁),还有很多函数也是这样实现线程安全的。

    2、内核处理信号内函数机制与线程处理函数机制的区别

    2.1 信号

    信号详细介绍该博客https://blog.csdn.net/edonlii/article/details/8472382

    1、基本概念

    软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

    收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。


    2、内核对信号的基本处理方法(重点理解这部分,不懂的去百度,直到理解)

    (1)内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理,比如线程中系统调用malloc申请内存,所以从用户态进入了内核态,现在信号发生,这时操作系统会从内核态跳转到用户态执行signal函数(signal函数本身是应用层的一行代码需要被运行,属于用户态,当执行到绑定的函数时,函数内部有系统调用函数,然后进入内核态)。进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数,而且执行用户定义的函数的方法很巧妙(这个函数时signal函数绑定的那个处理信号函数,比如里面出现malloc系统调用),内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方(线程中调用malloc所在内核执行处)。这样做的原因是用户定义的处理函数不应该在内核态下执行,所以一般在信号处理函数中,最好仅仅用来打印一条信息,然后使用longjmp或者exit退出。

    综上所述,如果信号被捕获,执行点从内核态返回用户态,在返回时,如果发现待执行进程存在被触发的signal,那么在离开内核态之后(也就是将CPU切换到用户模式),执行用户进程为该signal绑定的signal处理函数,从这一点上看,signal处理函数是在用户进程上下文中执行的。当执行完signal处理函数之后,再返回到用户进程被中断或者system call(软中断或者指令陷阱)打断的地方。

    (2)如果signal处理函数使用系统调用,比如malloc,free

    信号处理函数中只能调用可重入函数,而不能调用不可重入函数。进程捕捉到信号并对其进行处理时,正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理函数中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时正在执行的正常指令序列(这类似于发生硬件中断时所做的)。但在信号处理函数中,不能判断捕捉到信号时线程执行到何处。

      信号处理函数默认情况下是在进程的主线程调用的,这种情况下使用不可重入函数,有可能会造成不可预知的错误。比如调用了malloc函数,为了保证malloc是线程安全的,所以内部使用了锁,根据malloc中锁的不同处理方式,分别可能会导致以下情况的发生:

    1)  如果是普通锁,在主线程中malloc函数获取锁之后被signal中断,在signal处理函数中继续调用malloc,因为主线程中的malloc已经获取到了锁,signal处理函数只能等待锁释放,而主线程中的malloc函数正在等待signal处理函数返回后继续执行,这样就造成了锁死;

    2) 如果是递归锁,那么signal处理函数中的malloc函数获取锁后进行内存分配,因为上次的malloc操作还没完,可能成会造成内存数据混乱。

      就定时而言,可不直接使用singal alarm,而使用posix定时器,通过通知线程的方式,将定时处理函数放到单独的线程中来处理。

    3、内核线程调度系统对malloc的处理方法

    多线程之前使用malloc是安全的,虽然它不可重入,但是用锁实现了

    1)  如果是普通锁,A线程获取堆栈锁后,B线程必须等待A线程执行完成后,释放锁,然后B线程malloc才能申请内存。

    2)  如果是递归锁,内核调度系统在线程之间调度时,如果线程A的malloc函数一旦开始了申请,它就不会交出CPU,而是等malloc完成后,才会根据情况是否交出CPU,如果没有特别重要的处理,调度器就会跳转到线程B中执行,如果B线程执行到malloc,同理。(调度器如何工作等我搞明白再修正这里的介绍,这里第二点只是猜测

    3、同步与异步(Sync与Async)

    同步:简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。

    异步:异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。

    在许多C语言实现中,信号是真正意义上的“异步”。从理论上说,一个信号可能在C程序执行期间的任何时刻上发生。

    展开全文
  • 让Linux使用malloc申请更的内存

    千次阅读 2015-08-04 23:41:13
    我们的程序有个进程,但主进程只有一个,里面包括几十个线程,有的线程使用了如opencv的模块,占用内存有几百兆。而之前在文章提到的H.264转AVI,也必须将转码后的AVI格式内容放在内存,由于某些原因,系统的...

    项目遇到一个问题,程序跑着跑着就会挂掉,从多方信息分析来看,发现在设备的linux系统中,一个进程申请的内存最大只能达到1GB,而设备所用的物理内存是2GB的。我们的程序有多个进程,但主进程只有一个,里面包括几十个线程,有的线程使用了如opencv的模块,占用内存有几百兆。而之前在文章提到的H.264转AVI,也必须将转码后的AVI格式内容放在内存,由于某些原因,系统中的内存使用峰值会达到1GB。但由于我正在搞其它的bug,这个实际是同事研究出来的,我也只是再次多方面地验证了一下。还是在这里记录一下吧。

    测试所用的代码很简单,就是不断地申请内存。

    #define BUF_LEN (8*1024*1024)
    
    int main(void)
    {
        int cnt = 0;
        char* p = NULL;
        unsigned int total = 0;
        while (1)
        {
            cnt++;
            p = (char*)malloc(BUF_LEN);
            if (p==NULL)
            {
                printf("[%d]malloc for %d failed.\n", cnt, BUF_LEN);
                break;
            }
            total += BUF_LEN;
            printf("[%d]malloc %p ok total: %u(%.1fKB %.1fMB)\n", cnt, p, total, total/1024.0, total/1024.0/1024.0);
        }
        
        return 0;
    }

    在系统未做配置前,执行上述程序,只能申请900MB的内存。运行结果如下:

    [112]malloc 0x7f575008 ok total: 939524096(917504.0KB 896.0MB)
    [113]malloc 0x7ed74008 ok total: 947912704(925696.0KB 904.0MB)
    [114]malloc 0x7e573008 ok total: 956301312(933888.0KB 912.0MB)
    [115]malloc for buff failed.

    查找一些资料发现,内存的使用与/proc/sys/vm/overcommit_memory和/proc/sys/vm/overcommit_ratio似乎有关。经过我的测试,似乎和overcommit_memory关系更密切。对于overcommit_memory,我们的设备代码一直都是使用系统的默认值2。在2GB内存系统上只能申请900MB。但是,将它设置为1时,就可以申请最大的内存,执行如下命令临时生效:
    echo 1 > /proc/sys/vm/overcommit_memory

    再次运行上述程序,结果如下:

    [380]malloc 0xbdfd7008 ok total: 3187671040(3112960.0KB 3040.0MB)
    [381]malloc 0xbe7d8008 ok total: 3196059648(3121152.0KB 3048.0MB)
    [382]malloc 0xbefd9008 ok total: 3204448256(3129344.0KB 3056.0MB)

    注:测试中,申请内存的限制与overcommit_ratio的值无关,但修改overcommit_ratio会引起/proc/meminfo的中的值的变化。当overcommit_ratio为10时,信息如下:
    cat /proc/meminfo | grep Commit       
    CommitLimit:      679932 kB
    Committed_AS:     495736 kB

    当overcommit_ratio为50时,信息如下:
    # cat /proc/meminfo | grep Commit       
    CommitLimit:     1465500 kB
    Committed_AS:     495736 kB

    2015.8.4 晚 李迟

    展开全文
  • 让我们看看在CRuby程序,导致内存碎片的一个根源: 每个线程的内存动态分配(malloc’s per-thread memory arenas) 不是每次一个简单的配置修改就能彻底解决一个问题。 我曾有一个客户端,它的Sidekiq进程使用...

    总结: 内存碎片是一个难以测量和诊断的问题,但是解决这个问题有时也很容易。让我们看看在CRuby程序中,导致内存碎片的一个根源: 每个线程的内存动态分配(malloc’s per-thread memory arenas)

    不是每次一个简单的配置修改就能彻底解决一个问题。

    我曾有一个客户端,它的Sidekiq进程使用了非常多的内存,大概每个进程都占用了1G。它们刚开始启动时,大概每个只占用300M,然后就在几个小时内缓慢增长到了一个G,然后内存的占用才开始平稳。

    我修改了一个单一的环境变量: MALLOC_ARENA_MAX, 将其值设为2。

    进程重启后,上面说的内存占用缓慢增长现象消失,内存的最终稳定在之前的一半: 大约每个进程512M。

    现在,在你拷贝这个魔法般的环境变量到你的应用环境之前,你要知道: 它也会带来问题。 你可能不会被它所解决的问题所困扰。 这里没有银弹。

    Ruby并不是一个在内存使用上很轻的语言。很多Rails应用在每个进程内存消耗1G的情况下煎熬着。这接近Java的水平了。Sidekiq,这个流行的Ruby异步任务处理框架,它的进程可以变得越来越大。其中有很多原因,其中有一个很难去诊断和调试的原因: 内存碎片。

    Ruby内存消耗呈对数增长

    这个问题(内存消耗)在Ruby进程中呈现出缓慢,爬行般地增长。也常被误认为内存泄漏。然而,不像内存泄漏时内存呈线性增长,它是呈对数增长的。

    内存泄漏在Ruby中常常是由C拓展程序bug导致的。 举个例子,如果你的Markdown解析器每次泄漏10kb,你的内存消耗将会永久地呈线性增长,因为你一般会以一个常规的频率调用markdown解析器。

    内存碎片会造成内存呈对数增长。 它看起来像一个长长的曲线,会到达某个不可见的限制点。所有Ruby进程都有一些内存碎片问题。Ruby管理内存方式必然会导致这个问题。

    尤其是Ruby不会在内存中移动对象。这样可能会破坏持有Ruby对象指针的C扩展程序。如果不能在内存中移动这些对象,碎片就是一个必然的结果。这是一个在C项目中相当普遍的问题,不仅仅是Ruby。

    这是内存碎片的图表,注意看在MALLOC_ARENA_MAX为2后,碎片数量有一个巨大的下掉

    碎片有时会造成Ruby占用超过它实际需要两倍的内存,有时还可能会是4倍之多!

    Ruby程序员不会常去思考内存问题,尤其是动态内存分配(malloc)的级别。这也没有问题,这门语言就是为让程序员避开内存问题而设计的。它在手册页是正确的。但是尽管Ruby能保证内存安全,但它不能提供完美的内存抽象化。这在内存使用中不能被完全忽略。因为Ruby程序员太幼稚和不熟悉计算机如何管理内存,以至于当问题发生时,他们常常束手无策,不知道如何去调试,可能还会将问题丢锅给动态解释型语言,比如Ruby。

    Rubyists和内存之间隔了四个抽象层,这使得问题更加糟糕。第一层是Ruby虚拟机本身,它有自己追踪管理内存的方式(有时被叫做ObjectSpace)。第二层是动态内存分配器(allocator), 它依赖于你具体使用了哪种实现方式,不同的方式之间会有非常大差异。第三层是操作系统,它将实际的物理内存地址抽象为虚拟内存地址。它会因内核的不同而产生显著的差异,例如Mach和Linux就有很大不同。最后一层是硬件,它使用了一些策略,使得频繁存取的数据处在特别的位置,使得它们能被更快地获取到。这里面有很多CPU参与的地方,就像translation lookaside buffer

    这些隔离使得Rubyist很难去处理内存碎片。这个问题常常发生在Ruby虚拟机和动态内存分配器的级别,这也是超过95%的Rubyist不熟悉的地方。

    有些碎片是必然的,但是它也可能让你的内存使用翻倍,使情况变得很糟糕。你怎么才能知道你所遇到的情况是前者还是后者呢? 是什么造成了严重的内存碎片呢? 我对此有一个观点,它导致了Ruby多线程应用的内存碎片,像Puma,Passenger企业版,还有多线程任务处理器Sidekiq或Sucker Punch。

    glibc内存分配器中每个线程的内存场所(Per-Thread Memory Arenas in glibc Malloc)

    这一切归结为标准glibc malloc实现的一个特殊功能,称为per-thread memory arenas

    为了理解为什么,我需要解释一下在CRuby中GC如何快速运行。

    (Aaron Patterson做的ObjectSpace可视化。每个像素是一个RVALUE。绿色的是新的,红色的是老的。更多)

    所有对象都会在ObjectSpace拥有一个条目(entry)。ObjectSpace是一个很大的链表,它包含了每个在进程中活跃Ruby对象的条目。链表中的条目采用了RVALUE的形式,RVALUE是一个有40个字节的C结构体,这个结构体包含了Ruby对象部分基础数据。 结构体内的详细内容取决于Ruby对象的类。举个例子,如果有一个很短的字符串”hello”,那么字符串的字节数据就会直接嵌入到RVALUE中。但是,这里只有40字节,如果这个字符串有23字节或者更长,那么RVALUE中只会保存实际数据的原始指针。实际数据存放在RVALUE外面。

    RVALUE进一步在ObjectSpace中被组织成16KB“页”。每个页包含大约408个RVALUE

    这些数据可以在任意Ruby进程中的GC::INTERNAL_CONSTANTS变量中找到:

    GC::INTERNAL_CONSTANTS
    => {
    :RVALUE_SIZE=>40,
    :HEAP_PAGE_OBJ_LIMIT=>408,
    # ...
    }

    创建一个长的字符串(例如有1000个字符的HTTP响应),像这样:

      1. 增加一个 RVALUEObjectSpace链表。如果在ObjectSpace没有空闲插槽( free slots)了,那么,我们会调用malloc(16384)在这个链表中增加一个堆页。(译者: 16384bytes等于16kb。一个插槽也就是一个RVALUE结构体占的位置)
      1. 调用malloc(1000),返回一个1000个字节大小的内存地址(实际上,Ruby将会获取的空间比它所需要的更大,用来适应字符串大小的调整)。 这个内存块存放上面说的HTTP响应。

    内存分配器在这里所调用的,正是我想提醒你的地方。它做的所有事情都是去某个地方寻找一个特定大小的内存空间。实际上,malloc的连续性是不确定的,正是这样,它不能保证分配的内存空间的实际位置在哪里。这意味着,从Ruby虚拟机的视角来看,碎片(这基本上是关于内存在哪里的问题)是动态内存分配器(allocator)导致的问题。 (但是,分配模式和大小肯定会让分配器变得更难)

    Ruby能做的事情是测量自己ObjectSpace中的碎片。GC模块中有一个方法GC.stat,它提供了当前内存和GC的健康信息。它是压倒性的,并没有记录在案,输出是一个哈希:

    GC.stat
    => {
    :count=>12,
    :heap_allocated_pages=>91,
    :heap_sorted_length=>91,
    # ... way more keys ...
    }

    我想让你注意哈希中的这两个key: GC.stat[:heap_live_slots]GC.stat[:heap_eden_pages]

    heap_live_slots代表ObjectSpace中被活跃(没有被标记为冻结)RVALUE结构体占据的插槽(slots)数量。大致可以认为是当前活跃的Ruby对象。

    heap_eden_pagesObjectSpace中至少有一个活跃插槽(slot)的页(page)的数量。ObjectSpace页中,有至少一个活跃插槽(slot)的页,被称为伊甸页(eden pages)。ObjectSpace中,没有包含活跃对象的页,被称作墓穴页(tomb pages)。从GC的视角来看,这有非常重要的差别,因为墓穴页的空间可以还给操作系统。这样,GC会将新对象先放到伊甸页中,然后所有伊甸页填满后再坟墓页。这减少了内存碎片。

    如果你用活跃的插槽数除以伊甸页中所有插槽数,你可以测量出ObjectSpace当前的碎片。下面的例子是我在一个新的irb进程中得到的:

    5.times { GC.start }
    GC.stat[:heap_live_slots] # 24508
    GC.stat[:heap_eden_pages] # 83
    GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] # 408
    
    # live_slots / (eden_pages * slots_per_page)
    # 24508 / (83 * 408) = 72.3%

    当前伊甸页中大约有28%的插槽没有被使用。空闲插槽的比例很高,表明它们分散在更多的堆页中,如果我们能移动它们,则不会这样。这是一种内存的内存碎片。

    另一种测量内部碎片的方式来自于Ruby虚拟机中的GC.stat[:heap_sorted_length]。这个key是堆的”实际长度”。如果我们有三个ObjectSpace 页,假设我释放了中间的那页,那么我只有两个堆页保留。但是,我不能在内存中移动堆页,所以这个堆(基本上是堆页面的最高索引)的”长度”还是3。(译者: 关于这个点可以进一步参考这篇文章)

    GC.stat[:heap_sorted_length]除以 GC.stat[:heap_eden_pages]可以测量ObjectSpace 级别的内存碎片。如果得到低比例的结果,则表明在ObjectSpace中有大量的堆页般大小的”漏洞”。(译者:这些空闲的堆页没有被实际使用,但是它还是占用了系统的内存)

    这些测量很有趣,大部分的内存碎片(和大量的分配)并没有发生在ObjectSpace 中。它们发生在进程为那些不能嵌入单个RVALUE中的对象而分配内存时。 根据Aaron Patterson 和 Sam Saffron的实验,大部分情况下就是这样。一个典型的Rails应用中50%-80%的内存占用是通过malloc调用产生,为体积大于几个字节的对象分配空间。

    Aaron说的”managed by the GC”的意思是管理ObjectSpace链表。

    好吧,让我们来谈谈每个线程的内存使用。

    per-thread memory arena是在glibc 2.10中引入的优化,
    现在已经在 arena.c里面了。它旨在减少访问内存时,线程之间的竞争。

    在一个简单的基本分配器设计中,分配器确保一次只有一个线程可以从主体请求一个内存块。这个锁保证了两个线程不会意外的获得同一个内存块。如果真的发生了这种情况,那会造成相当讨厌的多线程bug。但是,如果应用程序有多个线程,那运行会变得缓慢,因为会有很多锁的争抢。所有的内存获取都需要通过这个锁,所以你可以看到,这会是一个瓶颈。

    由于锁对性能的影响,移除它已成为分配器设计中的主要工作。已经有一些无锁的分配器了。

    per-thread memory arena 的实现缓解了以下过程中锁的竞争(转述自这篇文章):

      1. 在一个线程中调用malloc,线程尝试获取锁,去访问之前访问过的内存场所(arena) (或者主内存区)。
      1. 如果内存场所(arena)不可用,则尝试下一个内存场所(arena) ,如果有其他内存arena。
      1. 如果所有内存场所(arenas)都不可用,创建一个新的场所(arena)并使用它。这个新的场所会被追加到一个链表的最后。

    通过这种方式,主区域基本上被扩展进了一个堆的链表。mallopt会限制能创建的区域(arenas)的数量,特别是M_ARENA_MAX参数(文档在这里)。默认情况下,per-thread memory arenas对这个数量限制是内核数量的8倍。大多数Ruby Web应用每个核跑了5个线程,Sidekiq跑的线程数量可能更多。这意味着在实际情况下,会有很多很多arenas被Ruby应用创建。

    让我们来看看这将如何在多线程Ruby应用程序中发挥作用。

      1. 你运行了一个Sidekiq进程,它有25个线程。
      1. Sidekiq运行5个新job。这些job和一个外部的服务通讯。它们发送一个HTTPS请求出去,然后在3秒后收到回复。
    • 3 每一个job发送HTTP请求,通过IO模块等待回复。通常来说,CRuby中所有的IO操作会释放GIL,这意味着线程可以并行执行,然后就会争抢获取主内存arena的锁,这会导致新的内存arena的创建。

    如果CRuby的多线程运行时不会有IO操作,那么它们不会去争抢主内存区域的锁,因为GIL避免了两个线程在同一时刻执行。这样,per-thread-memory arenas只会影响有IO操作的多线程应用。

    这是如何导致内存碎片的呢?

    内存碎片,本质上是一个装箱问题。我们如何高效的在多个装箱之间分配大小奇怪的条目,使得消耗的内存最小? 对于分配器来说,分包打包要困难得多,因为Ruby从不会移动内存位置(一旦分配了一个位置,这份数据会保持不动,直到被回收)。per-thread memory arenas实质上会创建大量不同的箱子(bins),它们不能被合并或打包在一起。装箱(Bin-packing)已经是NP-hard,而且这些限制会使获得最佳方案变得更难。

    Per-thread memory arenas导致了大量的RSS(实际使用物理内存)使用,这是glibc malloc跟踪器的一个已知问题。实际上, MallocInternals wiki 明确地说:

    因为线程的冲突压力增加,通过mmap创建额外的arenas,来缓解这个压力。能被创建的arenas的总数不超过CPU核数的8倍(除非用户另有指定,否则参阅mallopt),这意味着线程数多的应用依然会遇到锁竞争,但是会换来更少的碎片。

    你可以做的是: 降低可以使用的arenas数量,来减少碎片。这儿有一个明确地交换: 更少的arenas数量会减低内存碎片,但是应用会因为锁竞争的增加而变慢。

    Heroku在创建Cedar-14堆栈时发现了per-thread memory arenas的这种副作用,它将glibc升级到版本2.19。

    Heroku 用户在升级到这个新栈的时候发现内存消费增加了
    Terrence Hone 的测试发现了一些有趣的结果:

    Configuration Memory Use
    Base (unlimited arenas) 1.73x
    Base (before arenas introduced) 1x
    MALLOC_ARENA_MAX=1 0.86
    MALLOC_ARENA_MAX=2 0.87

    基本上, libc 2.19默认的内存arenas行为减少了10%的执行时间,但是增加了75%的内存消耗。减少arenas 的最大数量到2会消除这个速度上的提升,但是比旧Cedar-10堆栈减少了10%的内存使用量(与默认的内存竞技场行为相比内存使用量减少了约2倍!)。

    Configuration Response Times
    Base (unlimited arenas) 0.9x
    Base (before arenas introduced) 1x
    MALLOC_ARENA_MAX=1 1.15x
    MALLOC_ARENA_MAX=2 1.03x

    对于几乎所有Ruby应用,75%的内存增加来换10%的速度提升不是一个划算的交易。但是让我们在这里得到一些更加真实的结果。

    复制程序

    我写了一个Demo应用,这是一个Sidekiq任务,用来生成一些随机地数据,然后写入数据库。

    在我调整MALLOC_ARENA_MAX 的值为2之后,内存的使用在24小时后减少了15%。我注意到现实世界的工作负载大大放大了这种效果,这意味着我还没有完全理解可能导致这种碎片的分配模式(allocation pattern)。
    我在 Complete Guide to Rails Performance 看到了大量的内存图表,它们显示在MALLOC_ARENA_MAX = 2的情况下有2-3倍的内存节省。

    解决这个问题

    这儿有两个主要的解决途径,以及未来可能的解决方案。

    方案一: 降低内存arenas

    一个相当明显的解决方案是减少可用的最大内存区域(arenas)数量。我们可以通过修改MALLOC_ARENA_MAX 环境变量来实现。如前面所说的,这会增加分配器中的锁争用,导致应用的性能下降。

    这里不能推荐一个通用的设置,但是2到4的的范围似乎适合大多数Ruby应用。修改MALLOC_ARENA_MAX 到1似乎会对性能有较高的负面影响,也只会带来很少的内存消耗下降(1% - 2%)。在你的应用做相应的取舍之前,要去实验一下上面的设置,看看它们带来的内存和性能上的减少情况。

    方案二: jemalloc

    另一个可能的途径是简单的使用一个不同的内存分配器。jemalloc也实现了per-thread arenas。但是它的设计似乎避免了malloc中的碎片问题。

    上面的推文来自于我从CodeTriage的后台任务处理器中删除了jemalloc。你可以看到,影响是很显著的。我也实验了将MALLOC_ARENA_MAX设为2,但是内存使用依然比jemalloc多4倍。如果你可以在Ruby中切换到jemalloc,那就干吧。 相较于malloc,它似乎有一样或更好的性能,但会使用更少的内存。

    这不是一个关于jemalloc的博客,但是有一些在Ruby中使用jemalloc的优越点。

    方案三: 压缩GC

    如果可以移动内存的位置,碎片通常可以被减少。我们不能在CRuby做到这点,因为C扩展会使用指向Ruby内存的原生指针。移动了位置将会造成段错误,或读取到不正确的数据。

    Aaron Patterson正在致力于创建压缩GC。这项工作看起来有希望,但也许将来还有一段路要走。

    总结 (TL;DR):

    由于malloc在per-thread memory arenas导致的碎片,Ruby多线程程序或许会消费它们实际需要内存的2到4倍。为了解决这个问题,你可以设置MALLOC_ARENA_MAX 降低最大arenas数量,或者使用其他更好的内存分配器,像jemalloc。

    这里潜在的内存节省是如此之大,代价如此微小,以至于我建议如果您在生产环境中使用Ruby和Puma或Sidekiq,则应始终使用jemalloc。

    虽然这种效应在CRuby中最为明显,但它也可能会影响JVM和JRuby

    翻译自: https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html
    也发布在这里

    展开全文
  • 由于malloc_hook在多线程中不合用,因此转用__wrap_symbol方法支持多线程调用malloc。 缺点:所有source code都链接重编译。 ld中有一个选项–wrap,当查找某个符号时,它优先先解析__wrap_symbol, 解析不到才...

    由于malloc_hook在多线程中不合用,因此转用__wrap_symbol方法支持多线程调用malloc。

    缺点:所有source code都链接重编译。

    ld中有一个选项–wrap,当查找某个符号时,它优先先解析__wrap_symbol, 解析不到才去解析symbol

    当其它文件与你实现__wrap_malloc函数的文件链接时使用--wrap,malloc,则所有到malloc的调用都是会链接到__wrap_malloc,我们只需要在__wrap_malloc里面调用libc中的malloc函数。

    如:

    -Wl,-wrap,malloc -Wl,-wrap,free -Wl,-wrap,calloc -Wl,-wrap,realloc

    Ex:

    CCheckMemoryLeak.cpp:

    #include <stdlib.h>
    #include <execinfo.h>
    #include <map>
    #include <utility>
    #include <string.h>
    #include <sys/syscall.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <cxxabi.h>
    #include <dirent.h>
    
    #include "CCheckMemoryLeak.h"
    
    //
    // declare
    //
    #define THREAD_AUTOLOCK automutex _lock_(&_mutex, __FUNCTION__, __LINE__);
    #define TRUE (1)
    #define FALSE (0)
    #define PERR printf
    #define PWRN printf
    
    #define __HOOK__ (0)
    
    class automutex
    {
    public:
    	automutex(pthread_mutex_t *mux, const char *func, const int line);
    	~automutex();
    
    private:
        pthread_mutex_t *m_mux;
    };
    
    automutex::automutex(pthread_mutex_t *mux, const char *func, const int line)
    {
    	m_mux = mux;
    	pthread_mutex_lock(m_mux);
    }
    
    automutex::~automutex()
    {
    	pthread_mutex_unlock(m_mux);
    }
    
    typedef struct BtInfo_s
    {
        size_t size;
        char **str_bt;
        size_t mem_size;
        pthread_t tid;
        pid_t pid;
    } BtInfo_t;
    //
    // internel params
    //
    static void (*old_free)(void *ptr, const void *caller);
    static void *(*old_malloc)(size_t size, const void *caller);
    static void *(*old_realloc)(void *ptr, size_t size, const void *caller);
    static void *(*old_memalign)(size_t alignment, size_t size, const void *caller);
    
    static void my_free(void *ptr, const void *caller);
    static void *my_malloc(size_t size, const void *caller);
    static void *my_realloc(void *ptr, size_t size, const void *caller);
    static void *my_memalign(size_t alignment, size_t size, const void *caller);
    
    static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
    static std::map<const void *,  BtInfo_t> _malloc_info_map;
    static char _dym_symbol_dir[256] = "";
    static size_t _malloc_size_check_point = 1024;
    //
    // internel function
    //
    static int get_backtrace_info(char ** &str_bt, size_t *bt_sz)
    {
        if (NULL == bt_sz)
        {
            PERR("[%s][%d] error bt is null\n", __FUNCTION__, __LINE__);
            return FALSE;
        }
    #define _BT_SIZE_ (10)
        void *array[_BT_SIZE_] = { NULL };
        size_t size = backtrace (array, _BT_SIZE_);
    #undef _BT_SIZE_
        if (0 == size)
        {
            PERR("[%s][%d] bt sz is 0\n", __FUNCTION__, __LINE__);
            return FALSE;
        }
    
        str_bt = backtrace_symbols (array, size);
        if (NULL == str_bt)
        {
            PERR("[%s][%d] error str_bt is null\n", __FUNCTION__, __LINE__);
            return FALSE;
        }
    
        *bt_sz = size;
        return TRUE;
    }
    
    static pid_t gettid(void)
    {
        return syscall(SYS_gettid);
    }
    
    static int save_malloc_pointer_info(const void *ptr, const size_t size)
    {
        if (NULL == ptr)
        {
            PWRN("ptr is null\n");
            return TRUE;
        }
    
        size_t bt_sz = 0;
        BtInfo_t stBtInfo;
        if (FALSE == get_backtrace_info(stBtInfo.str_bt, &bt_sz))
        {
            PWRN("[%s][%d]get bt info error\n", __FUNCTION__, __LINE__);
            stBtInfo.str_bt = NULL;
        }
    
        stBtInfo.size = bt_sz;
        stBtInfo.mem_size = size;
        stBtInfo.tid = pthread_self();
        stBtInfo.pid = gettid();
        _malloc_info_map.insert(std::make_pair<const void *,  BtInfo_t>(ptr, stBtInfo));
        return TRUE;
    }
    
    static int remove_free_pointer_info(const void *ptr)
    {
        if (NULL == ptr)
        {
            PWRN("[%s][%d]ptr is null\n", __FUNCTION__, __LINE__);
            return TRUE;
        }
        std::map<const void *,  BtInfo_t>::iterator _it;
        _it = _malloc_info_map.find(ptr);
        if (_it == _malloc_info_map.end())
        {
            PERR("[%s][%d]point(%p) not found, must somethine error\n", __FUNCTION__, __LINE__, ptr);
            return FALSE;
        }
    
        BtInfo_t *pstBtInfo = &(_it->second);
        if (NULL != pstBtInfo)
        {
            if (NULL != pstBtInfo->str_bt)
            {
                free(pstBtInfo->str_bt);
                pstBtInfo->str_bt = NULL;
            }
        }
    
        _malloc_info_map.erase(_it);
        return TRUE;
    }
    
    /// format string start
    static int _frame_pos = 0;
    static void format_output_info(const char *funcname, const char *file, int line)
    {
        if (NULL == funcname)
        {
            printf("#%d  (Unkonw Function Name) at (Unknow Source File):0\n", _frame_pos);
        }
        else if (NULL == file)
        {
            printf("#%d  %s() at (Unknow Source File):0\n", _frame_pos, funcname);
        }
        else
        {
            printf("#%d  %s() at %s:%d\n", _frame_pos, funcname, file, line);
        }
        _frame_pos++;
    }
    
    static int get_libname(const char *symbol, char *libname)
    {
        if ((NULL == symbol) || (NULL == libname))
        {
            return FALSE;
        }
    
        const char *end = strstr(symbol, ".so");
        if (NULL == end)
        {
            return FALSE;
        }
    
        char *sta = (char *)rindex(symbol, '/');
        int len = 0;
        if (NULL == sta)
        {
            sta = (char *)symbol;
        }
        else
        {
            sta = sta + 1;
        }
    
        len = end + 3 - sta;
        if (0 >= len)
        {
            return FALSE;
        }
    
        strncpy(libname, sta, len);
        return TRUE;
    }
    
    static int get_funcname(const char *symbol, char *funcname)
    {
        if ((NULL == symbol) || (NULL == funcname))
        {
            return FALSE;
        }
    
        const char *sta = strchr(symbol, '(');
        if (NULL == sta)
        {
            return FALSE;
        }
    
        const char *end = strchr(symbol, '+');
        if (NULL == end)
        {
            return FALSE;
        }
    
        sta = sta + 1;
        int len = end - sta;
        if (1 >= len)
        {
            return FALSE;
        }
    
        strncpy(funcname, sta, len);
        return TRUE;
    }
    
    static int trans_symbol_name(char *symbol_name)
    {
        if (NULL == symbol_name)
        {
            return FALSE;
        }
    
        int status = 0;
        size_t size = 0;
        char *realname = abi::__cxa_demangle(symbol_name, 0, &size, &status);
        if (NULL != realname)
        {
            memset(symbol_name, 0x0, 256);
            strncpy(symbol_name, realname, 256);
            free(realname);
            realname = NULL;
            return TRUE;
        }
        return FALSE;
    }
    
    static void parse_func_info(const char *symbol)
    {
        if (NULL == symbol)
        {
            return;
        }
    
        char funcname[256];
        memset(funcname, 0x0, sizeof(funcname));
        if (TRUE == get_funcname(symbol, funcname))
        {
            trans_symbol_name(funcname);
            format_output_info(funcname, NULL, 0);
        }
        else
        {
            format_output_info(symbol, NULL, 0);
        }
    }
    
    static int get_lib_symbol_path(const char *libname, char *libpath)
    {
        if ((NULL == libname) || (NULL == libpath))
        {
            return FALSE;
        }
    
        struct dirent    *dp;
        DIR              *dfd = NULL;
    
        if(NULL == (dfd = opendir(_dym_symbol_dir)))
        {
            printf("open root dir failed! dir: %s", _dym_symbol_dir);
            return FALSE;
        }
    
        for(dp = readdir(dfd); NULL != dp; dp = readdir(dfd))
        {
            if(strstr(dp->d_name, libname) != NULL)
            {
                strncpy(libpath, dp->d_name, 256);
                closedir(dfd);
                return TRUE;
            }
        }
    
        closedir(dfd);
        return FALSE;
    }
    
    static void parse_lib_symbol_info(const char *symbol, const char *libname)
    {
        if ('\0' == _dym_symbol_dir[0])
        {
            parse_func_info(symbol);
            return;
        }
    
        char funcname[256];
        if (FALSE == get_funcname(symbol, funcname))
        {
            format_output_info(symbol, NULL, 0);
            return;
        }
    
        char libpath[256];
        memset(libpath, 0x0, sizeof(libpath));
        if (FALSE == get_lib_symbol_path(libname, libpath))
        {
            format_output_info(funcname, NULL, 0);
            return;
        }
    
        ///parse symbol lib info
    }
    /// format string end
    
    static int malloc_info_map_status(void)
    {
        THREAD_AUTOLOCK;
        size_t size = _malloc_info_map.size();
        printf("== unfree pointer count:%ld ==\n", size);
        printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
        if (0 != size)
        {
            _frame_pos = 0;
            printf("all alloc memory info:\n");
            std::map<const void *,  BtInfo_t>::iterator _it;
            size_t count = 0;
            size_t memsize = 0;
            for (_it = _malloc_info_map.begin(); _it != _malloc_info_map.end(); ++_it)
            {
                BtInfo_t *pstBtInfo = &(_it->second);
                if (NULL == pstBtInfo)
                {
                    printf("<< memory address:\t\t%p, size: unknow >>\n\n", _it->first);
                    continue;
                }
                printf("<< \t(%ld) memory address:\t%p, size: %ld, thread_id(0x%lx), p_id(0x%x)\t>>\n", ++count, _it->first, pstBtInfo->mem_size, pstBtInfo->tid, pstBtInfo->pid);
                printf("\t(%ld) backtrace information(%ld):\n", count, pstBtInfo->size);
                memsize += pstBtInfo->mem_size;
                size_t i = 0;
                char libname[256];
                for (i = 0; i < pstBtInfo->size; ++i)
                {
                    if (NULL == pstBtInfo->str_bt[i])
                    {
                        continue;
                    }
    
                    memset(libname, 0x0, sizeof(libname));
                    if (TRUE == get_libname(pstBtInfo->str_bt[i], libname))
                    {
                        ///trans lib info
                        parse_lib_symbol_info(pstBtInfo->str_bt[i], libname);
                    }
                    else
                    {
                        parse_func_info(pstBtInfo->str_bt[i]);
                    }
                }
    
                printf("== all unfree memory time(0x%lx) size(0x%lx) ==\n", count, memsize);
            }
        }
        return TRUE;
    }
    
    static void _private_hook_back()
    {
    #if __HOOK__
        old_malloc   = __malloc_hook;
        old_free     = __free_hook;
    	old_realloc  = __realloc_hook;
    	old_memalign = __memalign_hook;
    #endif
    }
    
    static void _private_hook_init()
    {
    #if __HOOK__
        __malloc_hook   = my_malloc;
        __free_hook     = my_free;
    	__realloc_hook  = my_realloc;
    	__memalign_hook = my_memalign;
    #endif
    }
    
    static void _private_hook_restore()
    {
    #if __HOOK__
        __malloc_hook   = old_malloc;
        __free_hook     = old_free;
    	__realloc_hook  = old_realloc;
    	__memalign_hook = old_memalign;
    #endif
    }
    
    #if __HOOK__
    static void *my_malloc(size_t size, const void *caller)
    #else
    void *__wrap_malloc(size_t size)
    #endif
    {
        static int count = 0;
        THREAD_AUTOLOCK;
        void *p = NULL;
        _private_hook_restore();
        p = malloc(size);
        //printf("xx malloc time(%d) xx\n", ++count);
        save_malloc_pointer_info(p, size);
        _private_hook_init();
        return p;
    }
    
    #if __HOOK__
    static void my_free(void *ptr, const void *caller)
    #else
    void __wrap_free(void *ptr)
    #endif
    {
        static int count = 0;
        THREAD_AUTOLOCK;
        _private_hook_restore();
        remove_free_pointer_info(ptr);
        free(ptr);
        //printf("xx free time(%d) xx\n", ++count);
        _private_hook_init();
    }
    
    /*
    如果是将分配的内存扩大,则有以下情况:
    1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
    2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
    3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。
    */
    static void *my_memalign(size_t boundary, size_t size, const void *caller)
    {
        static int count = 0;
        THREAD_AUTOLOCK;
        void *p = NULL;
        _private_hook_restore();
        p = malloc(size);
        //printf("xx memalign time(%d) xx\n", ++count);
        save_malloc_pointer_info(p, size);
        _private_hook_init();
        return p;
    }
    
    /*
    如果是将分配的内存扩大,则有以下情况:
    1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
    2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
    3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。
    */
    #if __HOOK__
    static void *my_realloc(void *__ptr, size_t size, const void *caller)
    #else
    void *__wrap_realloc(void *__ptr, size_t size)
    #endif
    {
        THREAD_AUTOLOCK;
        void *p = NULL;
        _private_hook_restore();
        //printf("realloc size: %ld\n", size);
        void *ptr = __ptr;
        p = realloc(__ptr, size);
        if (NULL != p)
        {
            remove_free_pointer_info(ptr);
            save_malloc_pointer_info(p, size);
        }
        _private_hook_init();
        return p;
    }
    
    void *__wrap_calloc(size_t numElements, size_t sizeOfElement)
    {
        static int count = 0;
        THREAD_AUTOLOCK;
        void *p = NULL;
        _private_hook_restore();
        p = calloc(numElements, sizeOfElement);
        //printf("xx calloc time(%d) xx\n", ++count);
        save_malloc_pointer_info(p, numElements * sizeOfElement);
        _private_hook_init();
        return p;
    }
    
    static void my_mempool_destroy()
    {
        THREAD_AUTOLOCK;
        _private_hook_restore();
    }
    
    static void my_mempool_init()
    {
        THREAD_AUTOLOCK;
        _private_hook_back();
        _private_hook_init();
        //atexit(my_mempool_destroy);
    }
    
    pthread_t monitor_id = -1;
    int thread_exit = FALSE;
    
    void *malloc_size_monitor(void *args)
    {
        while (FALSE == thread_exit)
        {
            if (_malloc_size_check_point <= _malloc_info_map.size())
            {
                printf("== to becarefully, check point detect, malloc size(%ld) ==\n", _malloc_info_map.size());
                malloc_info_map_status();
            }
    
            usleep(1000 * 1000);
        }
    
        return NULL;
    }
    
    static void malloc_size_monitor_thread_create(void)
    {
        THREAD_AUTOLOCK;
        _private_hook_restore();
        if (0 != pthread_create(&monitor_id, NULL, malloc_size_monitor, NULL))
        {
            monitor_id = -1;
        }
        _private_hook_init();
    }
    
    static void malloc_size_monitor_thread_destroy(void)
    {
        THREAD_AUTOLOCK;
        _private_hook_restore();
        thread_exit = TRUE;
        if (-1 != monitor_id)
        {
            pthread_join(monitor_id, NULL);
        }
        _private_hook_init();
    }
    //
    // extern function
    //
    #if (__HOOK__ == 1)
    void (*__MALLOC_HOOK_VOLATILE __malloc_initialize_hook) (void) = my_mempool_init;
    #endif
    __attribute((constructor)) void CCheckMemoryLeakInit(void)
    {
        printf("== Check Memory Leak Init ==\n");
        memset(_dym_symbol_dir, 0x0, sizeof(_dym_symbol_dir));
        malloc_size_monitor_thread_create();
    }
    
    __attribute((destructor)) void CCheckMemoryLeakDeInit(void)
    {
        malloc_size_monitor_thread_destroy();
        my_mempool_destroy();
        malloc_info_map_status();
        printf("== Check Memory Leak Exit ==\n");
    }
    
    void CCheckMemoryLeakInfo(void)
    {
        malloc_info_map_status();
    }
    
    void CCheckMemoryLeakSetDymSymbolDir(const char *dir)
    {
        if (NULL != dir)
        {
            memcpy(_dym_symbol_dir, dir, sizeof(_dym_symbol_dir));
        }
    }
    
    void CCheckMemoryLeakSetMallocSizeCheckPoint(size_t size)
    {
        _malloc_size_check_point = size;
    }
    
    size_t CCheckMemoryLeakGetMallocSizeInfo(void)
    {
        return _malloc_info_map.size();
    }
    

    CCheckMemoryLeak.h

    #include <stdio.h>
    #include <malloc.h>
    #include <pthread.h>
    
    #ifdef __cplusplus
    extern "C" {
    #endif
        ///private
        void *__wrap_malloc(size_t size);
        void *__wrap_realloc(void *__ptr, size_t size);
        void *__wrap_calloc(size_t numElements, size_t sizeOfElement);
        void __wrap_free(void *ptr);
        ///pulibc
        void CCheckMemoryLeakInfo(void);
        void CCheckMemoryLeakSetDymSymbolDir(const char *dir);
        void CCheckMemoryLeakSetMallocSizeCheckPoint(size_t size);
        size_t CCheckMemoryLeakGetMallocSizeInfo(void);
    #ifdef __cplusplus
    }
    #endif

    TestChkMemLeak.cpp

    #include <stdio.h>
    #include <string.h>
    #include <execinfo.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <memory.h>
    
    #include "CCheckMemoryLeak.h"
    
    pthread_t monitor_id = -1;
    int thread_exit = 0;
    pthread_t monitor_id2 = -1;
    int thread_exit2 = 0;
    
    void *monitor(void *args)
    {
        int i = 0;
        while (0 == thread_exit)
        {
            if (10 > i)
            {
                char *p = (char *)malloc(10);
                printf("(%p)thread 1 exit(%d)\n", p, i);
                free(p);
            }
            else{
                printf("thread 1 exit(%d)\n", i);
                break;
            }
            ++i;
        }
    
        return NULL;
    }
    
    void *monitor2(void *args)
    {
        int i = 0;
        while (0 == thread_exit2)
        {
            if (10 > i)
            {
                char *p = (char *)malloc(10);
                printf("(%p)thread 2 exit(%d)\n", p, i);
                free(p);
            }
            else{
                printf("thread 2 exit(%d)\n", i);
                break;
            }
            i++;
        }
    
        return NULL;
    }
    
    static void malloc_size_monitor_init(void)
    {
        if (0 != pthread_create(&monitor_id, NULL, monitor, NULL))
        {
            monitor_id = -1;
        }
    }
    
    static void malloc_size_monitor_exit(void)
    {
        thread_exit = 1;
        if (-1 != monitor_id)
        {
            pthread_join(monitor_id, NULL);
        }
    }
    
    static void malloc_size_monitor_init2(void)
    {
        if (0 != pthread_create(&monitor_id2, NULL, monitor2, NULL))
        {
            monitor_id2 = -1;
        }
    }
    
    static void malloc_size_monitor_exit2(void)
    {
        thread_exit2 = 1;
        if (-1 != monitor_id2)
        {
            pthread_join(monitor_id2, NULL);
        }
    }
    
    void next()
    {
        printf("\npause now, press 'n' to contine\n");
        char c = 'x';
        while (1)
        {
            scanf("%c", &c);
            if ('n' == c)
            {
                return;
            }
        }
    }
    int main(int argc, char *argv[])
    {
        int len = 100;
        char *p = (char *)malloc(len);
        if (NULL != p)
        {
            memset(p, 0x1F, len);
            CCheckMemoryLeakInfo();
            char *p2 = (char *)realloc(p, 200);
            CCheckMemoryLeakInfo();
            free(p2);
            p = NULL;
            CCheckMemoryLeakInfo();
        }
    
        p = (char *)calloc(2, len);
        free(p);
        p = (char *)calloc(2, 200);
        free(p);
    #if 1
        malloc_size_monitor_init();
        malloc_size_monitor_init2();
        malloc_size_monitor_exit();
        malloc_size_monitor_exit2();
        next();
    #endif
        return 0;
    }
    

    Makefile

    GCC := gcc
    GXX := g++
    SRC := CCheckMemoryLeak.cpp
    INC := ./
    LIBDIR :=
    BINLIBDIR := ./
    LIB := -lpthread
    BINLIB := -lChkMemLeak
    CFLAGS += -Wno-deprecated-declarations -fPIC -Wl,--rpath=./ -rdynamic
    CPPFLAGS += -Wno-deprecated-declarations -fPIC -Wl,--rpath=./ -rdynamic
    TARGETLIB := libChkMemLeak.so
    TARGETBIN := TestChkMemLeak
    WRAPFUNC := -Wl,-wrap,malloc -Wl,-wrap,free -Wl,-wrap,calloc -Wl,-wrap,realloc
    .PHONY : all libChkMemLeakCreate clean
    
    all: libChkMemLeakCreate
    	$(GXX) $(CPPFLAGS) $(WRAPFUNC)  -g -o  $(TARGETBIN) TestChkMemLeak.cpp -L$(BINLIBDIR) $(BINLIB) -I$(INC) $(LIB)
    
    libChkMemLeakCreate:
    	$(GXX) $(CPPFLAGS) -shared -g $(SRC) -o $(TARGETLIB) -L$(LIBDIR) -I$(INC) $(LIB)
    
    clean:
    	rm -rf ./$(TARGETLIB)
    	rm -rf ./$(TARGETBIN)

    Test Result:

    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    all alloc memory info:
    <<     (1) memory address:    0x2490430, size: 200, thread_id(0x7f25f2caa780), p_id(0x3602)    >>
        (1) backtrace information(6):
    #0  ./libChkMemLeak.so(+0x49fd) [0x7f25f28a19fd]() at (Unknow Source File):0
    #1  ./libChkMemLeak.so(+0x4b01) [0x7f25f28a1b01]() at (Unknow Source File):0
    #2  __wrap_realloc() at (Unknow Source File):0
    #3  main() at (Unknow Source File):0
    #4  __libc_start_main() at (Unknow Source File):0
    #5  ./TestChkMemLeak() [0x400b79]() at (Unknow Source File):0
    == all unfree memory time(0x1) size(0xc8) ==
    == unfree pointer count:0 ==



    展开全文
  • 1.使用锁的方式来支持多线程 我们之前介绍过,库函数的实现方式是通过一个主分配区和多个非主分配区来组织的,每一个分配区又有一个bins.我们先说对于大的分配区是如何保证线程安全的。 首先,每一个分配区均...
  • 程序员自我修养-CRT多线程

    千次阅读 2012-07-20 10:58:21
    线程的访问权限:   多线程运行库: 1. c语言必须提供多线程的API 2. 有些函数之前设计并不适合多线程,需要改进 ...1. 使用TLS,比如errno在单线程版本中直接返回全局的errno,但是在多线程中
  • 主动终止(终止自己) ...这两个函数返回值都为指针类型,在线程中使用时所指向的地址必须为:malloc分配的或者全局变量;因为当线程退出是 其作用域的变量都将消失。 这两个函数的返回值都能通过 int pthrea...
  • 《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...
  • GarbageCollectorC GC.h文件公开了一些可以在每个单线程C程序中使用的功能。 /* --------------------------------------------------------------------- * GC_init * -----------------------------------------...
  • WINAPI多线程

    2018-08-10 12:45:58
    《windows 核心编程》: CreateThread函数是用来创建线程的Windows函数,不过,如果你正在编写C/C++,绝不应该调用...1.在CreateThread API创建的线程中使用sprintf,malloc,strcat等涉及CRT存储堆操作的CRT库...
  • 多线程环境存在问题的C/C++运行期库变量和函数包括:error, _doserrno, strtok, strerror, asctime, gmtime,malloc等等。这些函数中都存在全局变量或者要访问全局变量(对于malloc来说,进程的堆也算是全全局的...
  • 线程的合并与分离 pthread_join() ...众所周知的一个问题就是C或C++编程如果要通过malloc()或new分配了一块内存,就必须使用free()或delete来回收这块内存,否则就会产生内存泄漏问题。既然线程和内...
  • 1)在写函数时候尽量使用局部变量(例如寄存器、堆栈的变量); 2)对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。 满足下列条件的函数多数是不...
  • 线程使用注意事项: 1.主线程退出其他线程不退出,主线程应调用pthread_exit 2.避免僵尸线程 pthread_join pthread_detach pthread_create指定分离属性 被join线程可能在join...4.应避免在多线程模型调用fork,...
  • 用户使用malloc或者是new申请内存的时候,找到第一个满足大小的空间,即从链表删除这个节点 之后就分配给用户使用,没有栈内存使用快,但是很灵活 new和malloc的区别 1.首先是属性上的区别,new是编译器支持的,...
  • 当前项目,需要查看C++的目标进程的内存分配情况,最直接的方式就是跟踪new/delete的使用情况;通过查看libstd++的源码发现,new的底层调用的还是malloc(有的版本可能不是,需要注意,c++的标准里没有强制要求new...
  • 首先先明白什么样的函数不是线程安全的:使用多个thread共享的全局或静态变量;函数调用了非thread_safe的子函数。 C库函数线程安全性 在 ARM 库,函数可能是线程安全的,如下所示: 某些函数从来都...
  • 文章目录多线程:主分配区和非主分配区结构chunkbinfast binsunsorted binsmall binslarge binsmmaped chunktop chunkLast remainder流程分配初始化malloc流程内存回收流程如何避免内存暴增? glibc中malloc采用的是...
  • 强大的多线程内存分配器jemalloc

    千次阅读 2011-03-25 16:32:00
    目前使用malloc() 库,调用的是 Poul-Henning Kamp 的 phkmalloc,... 由于在多处理器系统运行多线程应用时,它的缺乏效率,一个新的userland内存分配器被创建出来,并被它的创建者Jason Evans命名为jem
  • 程序说明:使用了c++11的std线程,锁,信号量等东西,替换了pthread多线程。主要之前用windows下移植的linux发现多线程始终有问题,所以决定用原生的试试。不过现在想来,应该问题还是我队列的设计问题。主要这里有...
  • 线程的访问权限: 多线程运行库: 1. c语言必须提供多线程的API 2. 有些函数之前设计并不适合多线程,需要改进 ...1. 使用TLS,比如errno在单线程版本中直接返回全局的errno,但是在多线程中返回
  • 在开发服务端时,由于是使用C语言,所以经常使用malloc,free等C语言函数;在开发起初阶段,由于只是开发基础模块和基本的功能组件,所以不牵涉到多线程的开发。 在开发过程,团队的每个成员都就自己的模块编写...
  • 模拟malloc和free 作业

    2013-07-09 21:18:51
    定义了一个10M大小的数组,每次分配空间都从这10M分配,原理是分配的时候空间足够的话先存储一个指定结构体,结构体有...自己的作业,当然也有很多欠缺的地方,比如没有考虑多线程同时调用这类的问题。仅供参考。
  • 要想成为reentrant式的函数,该函数不能含有(或使用)静态(或全局)数据(来存储函数调用过程的状态信息),也不能返回指向静态数据的指针,它只能使用由调用者提供的数据,当然也不能调用non-reentrant函数. <br />...
  • 快速检测内存泄露C/C++内存泄漏及检测 1、win下的内存泄露检测方法:_CrtDumpMemoryLeaks通过包括 crtdbg.h,将 malloc 和 free 函数映射到它们的调试版本,即 _... 发布版本使用普通的 malloc 和 free 函数。 在程
  • 多线程分配内存的情况下,可以减少锁竞争。 tcmalloc官方文档见: https://gperftools.github.io/gperftools/tcmalloc.html tcmalloc可以替换大多数系统、不同编译器环境下的内存分配函数(malloc/free/new/delete...
  • 【Linux】---线程安全

    2020-07-22 18:41:21
    (2)在多线程中使用线程安全的函数(可重入函数),所以线程安全的函数指的是如果一个函数能够被多个线程同时调用,且不发生竞态事件,则我们认为是线程安全的。 常见可重入的方法: 1、不使用全局变量和静态变量...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 180
精华内容 72
关键字:

多线程中使用malloc