hphp/runtime/base/memory-manager.cpp
/*
 * smart_malloc & friends implementation notes
 *
 * There are three kinds of smart mallocation:
 *
 *  a) Large allocations.  (size >= kMaxSmartSize)
 *
 *     In this case we behave as a wrapper around the normal libc
 *     malloc/free.  We insert a SweepNode header at the front of the
 *     allocation in order to find these at sweep time (end of
 *     request) so we can give them back to libc.
 *
 *  b) Size-tracked small allocations.
 *
 *     This is used for the generic case, for callers who can't tell
 *     us the size of the allocation at free time.
 *
 *     In this situation, we put a SmallNode header at the front of
 *     the block that tells us the size for when we need to free it
 *     later.  We differentiate this from a SweepNode (for a big
 *     allocation) by assuming that no SweepNode::prev will point to
 *     an address in the first kMaxSmartSize bytes of virtual address
 *     space.
 *
 *  c) Size-untracked small allocation
 *
 *     Many callers have an easy time telling you how big the object
 *     was when they need to free it.  In this case we can avoid the
 *     SmallNode, which saves us some memory and also let's us give
 *     out 16-byte aligned pointers easily.
 *
 *     We know when we have one of these because it has to be freed
 *     through a different entry point.  (E.g. MM().smartFreeSize or
 *     MM().smartFreeSizeBig.)
 *
 * When small blocks are freed (case b and c), they're placed the
 * appropriate size-segregated freelist.  Large blocks are immediately
 * passed back to libc via free.
 *
 * There are currently two kinds of freelist entries: entries where
 * there is already a valid SmallNode on the list (case b), and
 * entries where there isn't (case c).  The reason for this is that
 * that way, when allocating for case b, you don't need to store the
 * SmallNode size again.  Much of the heap is going through case b at
 * the time of this writing, so it is a measurable regression to try
 * to just combine the free lists, but presumably we can move more to
 * case c and combine the lists eventually.
 */
smart_malloc & friends implementation notes
There are three kinds of smart mallocation.

a) Large allocations.

In this case we behave as a wrapper around the normal libc
malloc/free.  We insert a SweepNode header at the front of the
allocation in order to find these at sweep time (end of
request) so we can give them back to libc.

b) Size-tracked small allocations.

This is used for the generic case, for callers who can't tell
us the size of the allocation at free time.

In this situation, we put a SmallNode header at the front of
the block that tells us the size for when we need to free it
later.  We differentiate this from a SweepNode (for a big
allocation) by assuming that no SweepNode::prev will point to
an address in the first kMaxSmartSize bytes of virtual address
space.

c) Size-untracked small allocation

Many callers have an easy time telling you how big the object
was when they need to free it.  In this case we can avoid the
SmallNode, which saves us some memory and also let's us give
out 16-byte aligned pointers easily.

We know when we have one of these because it has to be freed
through a different entry point.

When small blocks are freed (case b and c), they're placed the
appropriate size-segregated freelist.  Large blocks are immediately
passed back to libc via free.

There are currently two kinds of freelist entries: entries where
there is already a valid SmallNode on the list (case b), and
entries where there isn't (case c).  The reason for this is that
that way, when allocating for case b, you don't need to store the
SmallNode size again.  Much of the heap is going through case b at
the time of this writing, so it is a measurable regression to try
to just combine the free lists, but presumably we can move more to
case c and combine the lists eventually.
smart_malloc & friends implementation notes There are three kinds of smart mallocation . a ) Large allocations . In this case we behave as a wrapper around the normal libc malloc / free . We insert a SweepNode header at the front of the allocation in order to find these at sweep time ( end of request ) so we can give them back to libc . b ) Size - tracked small allocations . This is used for the generic case for callers who can ' t tell us the size of the allocation at free time . In this situation we put a SmallNode header at the front of the block that tells us the size for when we need to free it later . We differentiate this from a SweepNode ( for a big allocation ) by assuming that no SweepNode :: prev will point to an address in the first kMaxSmartSize bytes of virtual address space . c ) Size - untracked small allocation Many callers have an easy time telling you how big the object was when they need to free it . In this case we can avoid the SmallNode which saves us some memory and also let ' s us give out 16 - byte aligned pointers easily . We know when we have one of these because it has to be freed through a different entry point . When small blocks are freed ( case b and c ) they ' re placed the appropriate size - segregated freelist . Large blocks are immediately passed back to libc via free . There are currently two kinds of freelist entries : entries where there is already a valid SmallNode on the list ( case b ) and entries where there isn ' t ( case c ) . The reason for this is that that way when allocating for case b you don ' t need to store the SmallNode size again . Much of the heap is going through case b at the time of this writing so it is a measurable regression to try to just combine the free lists but presumably we can move more to case c and combine the lists eventually .
inline void* MemoryManager::smartMalloc(size_t nbytes) {
  nbytes += sizeof(SmallNode);
  if (UNLIKELY(nbytes > kMaxSmartSize)) {
    return smartMallocBig(nbytes);
  }
  auto const ptr = static_cast<SmallNode*>(smartMallocSize(nbytes));
  ptr->padbytes = nbytes;
  return ptr + 1;
}
inline void * MemoryManager :: smartMalloc ( size_t nbytes ) { nbytes += sizeof ( SmallNode ) ; if ( UNLIKELY ( nbytes > kMaxSmartSize ) ) { return smartMallocBig ( nbytes ) ; } auto const ptr = static_cast < SmallNode * > ( smartMallocSize ( nbytes ) ) ; ptr -> padbytes = nbytes ; return ptr + 1 ; }
smart_malloc & friends implementation notes
 There are three kinds of smart mallocation:
smart_malloc & friends implementation notes There are three kinds of smart mallocation :