Malte Cornils wrote:
> I've done some further research and found out a few things.

I apologize that I have not read your results in detail.  But I did
not want to hold off adding this information until I had and so this
may be overlapping or incomplete with regard to your complete post.

> So, malloc appears to only use contiguos areas of address space with
> a single invocation, but allocating more memory in a different
> "address space hole" the second time around works fine.

I read through the glibc source and here are some of the interesting
comments from the glibc malloc.c implementation.  Just the Reader's
Digest version.  If you skim, make sure you read the section marked
"M_MMAP_THRESHOLD".

/*
  This is not the fastest, most space-conserving, most portable, or
  most tunable malloc ever written. However it is among the fastest
  while also being among the most space-conserving, portable and tunable.
  Consistent balance across these factors results in a good general-purpose
  allocator for malloc-intensive programs.

  The main properties of the algorithms are:
  * For large (>= 512 bytes) requests, it is a pure best-fit allocator,
    with ties normally decided via FIFO (i.e. least recently used).
  * For small (<= 64 bytes by default) requests, it is a caching
    allocator, that maintains pools of quickly recycled chunks.
  * In between, and for combinations of large and small requests, it does
    the best it can trying to meet both goals at once.
  * For very large requests (>= 128KB by default), it relies on system
    memory mapping facilities, if supported.

  For a longer but slightly out of date high-level description, see
     http://gee.cs.oswego.edu/dl/html/malloc.html
*/

Note that 128KB section about using the system mmap.

/*
  Define HAVE_MMAP as true to optionally make malloc() use mmap() to
  allocate very large blocks.  These will be returned to the
  operating system immediately after a free(). Also, if mmap
  is available, it is used as a backup strategy in cases where
  MORECORE fails to provide space from system.

  This malloc is best tuned to work with mmap for large requests.
  If you do not have mmap, operations involving very large chunks (1MB
  or so) may be slower than you'd like.
*/

#ifndef HAVE_MMAP
#define HAVE_MMAP 1

So it definitely wants to use mmap() and it wants to use it for big
chunks.

/*
   MMAP_AS_MORECORE_SIZE is the minimum mmap size argument to use if
   sbrk fails, and mmap is used as a backup (which is done only if
   HAVE_MMAP).  The value must be a multiple of page size.  This
   backup strategy generally applies only when systems have "holes" in
   address space, so sbrk cannot perform contiguous expansion, but
   there is still space available on system.  On systems for which
   this is known to be useful (i.e. most linux kernels), this occurs
   only when programs allocate huge amounts of memory.  Between this,
   and the fact that mmap regions tend to be limited, the size should
   be large, to avoid too many mmap calls and thus avoid running out
   of kernel resources.
*/

#ifndef MMAP_AS_MORECORE_SIZE
#define MMAP_AS_MORECORE_SIZE (1024 * 1024)
#endif

So if sbrk() fails it chunks out of mmap in 1MB chunks.

/* Define USE_ARENAS to enable support for multiple `arenas'.  These
   are allocated using mmap(), are necessary for threads and
   occasionally useful to overcome address space limitations affecting
   sbrk(). */

#ifndef USE_ARENAS
#define USE_ARENAS HAVE_MMAP
#endif

Not really interesting unless we are using threads.  But it explains
the arena values from malloc_stats().

Here is a very interesting section.  Don't miss this.

/*
  M_MMAP_THRESHOLD is the request size threshold for using mmap()
  to service a request. Requests of at least this size that cannot
  be allocated using already-existing space will be serviced via mmap.
  (If enough normal freed space already exists it is used instead.)

  Using mmap segregates relatively large chunks of memory so that
  they can be individually obtained and released from the host
  system. A request serviced through mmap is never reused by any
  other request (at least not directly; the system may just so
  happen to remap successive requests to the same locations).

  Segregating space in this way has the benefits that:

   1. Mmapped space can ALWAYS be individually released back
      to the system, which helps keep the system level memory
      demands of a long-lived program low.
   2. Mapped memory can never become `locked' between
      other chunks, as can happen with normally allocated chunks, which
      means that even trimming via malloc_trim would not release them.
   3. On some systems with "holes" in address spaces, mmap can obtain
      memory that sbrk cannot.

  However, it has the disadvantages that:

   1. The space cannot be reclaimed, consolidated, and then
      used to service later requests, as happens with normal chunks.
   2. It can lead to more wastage because of mmap page alignment
      requirements
   3. It causes malloc performance to be more dependent on host
      system memory management support routines which may vary in
      implementation quality and may impose arbitrary
      limitations. Generally, servicing a request via normal
      malloc steps is faster than going through a system's mmap.

  The advantages of mmap nearly always outweigh disadvantages for
  "large" chunks, but the value of "large" varies across systems.  The
  default is an empirically derived value that works well in most
  systems.
*/

#define M_MMAP_THRESHOLD      -3

#ifndef DEFAULT_MMAP_THRESHOLD
#define DEFAULT_MMAP_THRESHOLD (128 * 1024)
#endif

This means that big requests are those bigger than 128kb and those are
serviced through mmap().  This is a mallopt() configurable parameter.
Smaller requests are serviced through sbrk() until that runs out.
After sbrk() runs out then mmap() is fallen back upon to provide more
memory until that runs out.  This explains the behavior we have seen
with addresses being allocated first in one place and then in the
other.

I added a call to malloc_stats(), basically this in the programs:

          printf("Out of memory at %ld MB\n",count / (1024 / 64));
          fflush(stdout);
          malloc_stats();
          sleep(30);
          exit(0);

For the 1MB malloc() case:

  Arena 0:
  system bytes     =  938486264
  in use bytes     =  938482680
  Total (incl. mmap):
  system bytes     = 3083831800
  in use bytes     = 3083828216
  max mmap regions =       2038
  max mmap bytes   = 2145345536

I modified this for a 64kb malloc() case:

  Arena 0:
  system bytes     = 3085863300
  in use bytes     = 2952052412
  Total (incl. mmap):
  system bytes     = 3085863300
  in use bytes     = 2952052412
  max mmap regions =          0
  max mmap bytes   =          0

This I don't understand.  I thought I did until this point.  This
shows that it never mmap'd any segments.  I was able to get the same
amount of memory (without limits of overhead) from the system by using
smaller chunks.  I expected it to run out of sbrk() at 1GB and fall
back to mmap and allocate the remaining 2GB.  Perhaps this is an
accounting strangeness in malloc_stats() only.  Because it did
allocate the full 3GB of memory regardless and we now know sbrk()
fails after the first 1GB.

> The question remains why the address space is split like it is. That is, the 
> program starts at 0x08000000, while the shared libraries are at 0x4100000. 
> So, approx. 900 MiB can be allocated here. Then, there is free address space 
> again after the shared libraries, up to approx. 0xb7000000. This is the 
> largest contiguous amount of free address space (ca. 2 GiB) and is what 
> limits my malloc test program, I suppose.

I believe this is done for the reasons above in the code comments.  As
a rough heuristic so that memory can be grouped into small chunks and
big chunks and coalesced more readily.

> Anyone still following me? And should I try asking at glibc-devel or at the 
> Linux kernel mailing list?

You are in pretty deep into the glibc malloc implementation.  At a
guess I would expect the glibc list would be more interested.  (But I
am interested and I am not subscribed there. :-)

By the way, thanks for the good literature search and providing all of
those references!

Bob

Attachment: signature.asc
Description: Digital signature

Reply via email to