1. The cost of free(), which recursively includes the cost of free()ing all "substructures" (ex: linked list free(head) includes the cost of free()ing the entire linked list)
2. The cost of malloc()
Manual memory management has both malloc() and free(). While garbage-collection memory management removes free() entirely, and does everything with malloc() alone (with "malloc" having some kind of mechanism to automatically detect which bits of RAM are safe to re-allocate without free() to guide it).
These costs can be incredibly complicated to figure out (!!!). free()ing a 4MB linked-list for example, would flush out your L1 cache and L2 cache, as well as take up a significant chunk of your L3 cache.
There is also the issue of fragmentation: malloc() and free() are cheap when the program starts, but as the heap gets more and more confounded by malloc()/free() calls, future calls to malloc() and free() can get slower-and-slower.
So there's a degree of:
* "Housekeeping" -- anti-fragmentation mostly, sorting the free()'d pointers and regions into groups more easily malloc'd() in the future.
* Synchronization -- Having all your threads pull from the same pool of memory is easier to think about, but requires multi-threaded synchronization. For long-lasting free() (ex: freeing a 4MB linked list), this could have one thread slowing down other threads when they try to malloc or free themselves.
-----------
Finally, the application code grossly determines what is faster or slower. Many people assume manual memory management is faster, but consider the following example:
Lets say our application-data consists of 1MB of misc. data + 3999MBs of linked-list data (including the internal pointers). Lets also assume 4000MBs is when the garbage collector runs.
Manual memory management would malloc() the linked list until its of 3999MB size, then the free() call would traverse the entire 3999MB structure every time to find the "deletion" pointers.
In contrast, Garbage collection of malloc() the linked list to 3999MB. The free() doesn't exist, but the linked-list will be detected as "dead data" upon the next malloc() call (only the 1MB of misc. data is traversed).
As such: the garbage-collector will never "Traverse" the entire linked list. The garbage collector only traverses the "live data" whenever it runs (which might be the 1MB of misc. data... but if this misc. data is long lived, then a generational garbage collector won't even traverse that!!).
So really, garbage collectors are best when you have small amounts of live data, and large amounts of malloc() calls. In contrast, manual memory management is best when you have lots of live-data (especially "quickly moving" live data, such that the generational-heuristic doesn't help), and relatively little dead-data.