source: doc/theses/mubeen_zulfiqar_MMath/allocator.tex @ a0bd9a2

Last change on this file since a0bd9a2 was fb6691a, checked in by Peter A. Buhr <pabuhr@…>, 2 years ago

final proofread of Mubeen's MMath thesis

  • Property mode set to 100644
File size: 61.5 KB
RevLine 
[50d8d4d]1\chapter{Allocator}
[47204d4]2\label{c:Allocator}
[dbfae7b]3
[23f1065]4This chapter presents a new stand-alone concurrent low-latency memory-allocator ($\approx$1,200 lines of code), called llheap (low-latency heap), for C/\CC programs using kernel threads (1:1 threading), and specialized versions of the allocator for the programming languages \uC and \CFA using user-level threads running over multiple kernel threads (M:N threading).
[db4a8cf]5The new allocator fulfills the GNU C Library allocator API~\cite{GNUallocAPI}.
[58d471f]6
[db4a8cf]7
8\section{llheap}
9
10The primary design objective for llheap is low-latency across all allocator calls independent of application access-patterns and/or number of threads, \ie very seldom does the allocator have a delay during an allocator call.
11(Large allocations requiring initialization, \eg zero fill, and/or copying are not covered by the low-latency objective.)
12A direct consequence of this objective is very simple or no storage coalescing;
13hence, llheap's design is willing to use more storage to lower latency.
14This objective is apropos because systems research and industrial applications are striving for low latency and computers have huge amounts of RAM memory.
[23f1065]15Finally, llheap's performance should be comparable with the current best allocators (see performance comparison in \VRef[Chapter]{c:Performance}).
[db4a8cf]16
17% The objective of llheap's new design was to fulfill following requirements:
18% \begin{itemize}
19% \item It should be concurrent and thread-safe for multi-threaded programs.
20% \item It should avoid global locks, on resources shared across all threads, as much as possible.
21% \item It's performance (FIX ME: cite performance benchmarks) should be comparable to the commonly used allocators (FIX ME: cite common allocators).
22% \item It should be a lightweight memory allocator.
23% \end{itemize}
[2b910f9]24
25%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
26
[db4a8cf]27\section{Design Choices}
[2b910f9]28
[db4a8cf]29llheap's design was reviewed and changed multiple times throughout the thesis.
30Some of the rejected designs are discussed because they show the path to the final design (see discussion in \VRef{s:MultipleHeaps}).
[29d8c02]31Note, a few simple tests for a design choice were compared with the current best allocators to determine the viability of a design.
[db4a8cf]32
33
34\subsection{Allocation Fastpath}
[23f1065]35\label{s:AllocationFastpath}
[db4a8cf]36
37These designs look at the allocation/free \newterm{fastpath}, \ie when an allocation can immediately return free storage or returned storage is not coalesced.
38\paragraph{T:1 model}
[29d8c02]39\VRef[Figure]{f:T1SharedBuckets} shows one heap accessed by multiple kernel threads (KTs) using a bucket array, where smaller bucket sizes are shared among N KTs.
40This design leverages the fact that usually the allocation requests are less than 1024 bytes and there are only a few different request sizes.
[db4a8cf]41When KTs $\le$ N, the common bucket sizes are uncontented;
42when KTs $>$ N, the free buckets are contented and latency increases significantly.
43In all cases, a KT must acquire/release a lock, contented or uncontented, along the fast allocation path because a bucket is shared.
44Therefore, while threads are contending for a small number of buckets sizes, the buckets are distributed among them to reduce contention, which lowers latency;
45however, picking N is workload specific.
46
47\begin{figure}
[dbfae7b]48\centering
49\input{AllocDS1}
[db4a8cf]50\caption{T:1 with Shared Buckets}
51\label{f:T1SharedBuckets}
52\end{figure}
53
54Problems:
[9c5aef9]55\begin{itemize}
56\item
[db4a8cf]57Need to know when a KT is created/destroyed to assign/unassign a shared bucket-number from the memory allocator.
[9c5aef9]58\item
[db4a8cf]59When no thread is assigned a bucket number, its free storage is unavailable.
[9c5aef9]60\item
[db4a8cf]61All KTs contend for the global-pool lock for initial allocations, before free-lists get populated.
[9c5aef9]62\end{itemize}
[db4a8cf]63Tests showed having locks along the allocation fast-path produced a significant increase in allocation costs and any contention among KTs produces a significant spike in latency.
[a953c2e3]64
[db4a8cf]65\paragraph{T:H model}
[29d8c02]66\VRef[Figure]{f:THSharedHeaps} shows a fixed number of heaps (N), each a local free pool, where the heaps are sharded (distributed) across the KTs.
[db4a8cf]67A KT can point directly to its assigned heap or indirectly through the corresponding heap bucket.
[29d8c02]68When KT $\le$ N, the heaps might be uncontented;
[db4a8cf]69when KTs $>$ N, the heaps are contented.
70In all cases, a KT must acquire/release a lock, contented or uncontented along the fast allocation path because a heap is shared.
[29d8c02]71By increasing N, this approach reduces contention but increases storage (time versus space);
[db4a8cf]72however, picking N is workload specific.
[a953c2e3]73
[db4a8cf]74\begin{figure}
75\centering
76\input{AllocDS2}
77\caption{T:H with Shared Heaps}
78\label{f:THSharedHeaps}
79\end{figure}
[a953c2e3]80
[db4a8cf]81Problems:
82\begin{itemize}
83\item
84Need to know when a KT is created/destroyed to assign/unassign a heap from the memory allocator.
85\item
86When no thread is assigned to a heap, its free storage is unavailable.
87\item
88Ownership issues arise (see \VRef{s:Ownership}).
89\item
90All KTs contend for the local/global-pool lock for initial allocations, before free-lists get populated.
91\end{itemize}
92Tests showed having locks along the allocation fast-path produced a significant increase in allocation costs and any contention among KTs produces a significant spike in latency.
[9c5aef9]93
[db4a8cf]94\paragraph{T:H model, H = number of CPUs}
95This design is the T:H model but H is set to the number of CPUs on the computer or the number restricted to an application, \eg via @taskset@.
96(See \VRef[Figure]{f:THSharedHeaps} but with a heap bucket per CPU.)
97Hence, each CPU logically has its own private heap and local pool.
98A memory operation is serviced from the heap associated with the CPU executing the operation.
99This approach removes fastpath locking and contention, regardless of the number of KTs mapped across the CPUs, because only one KT is running on each CPU at a time (modulo operations on the global pool and ownership).
100This approach is essentially an M:N approach where M is the number if KTs and N is the number of CPUs.
[a953c2e3]101
[db4a8cf]102Problems:
103\begin{itemize}
104\item
105Need to know when a CPU is added/removed from the @taskset@.
106\item
107Need a fast way to determine the CPU a KT is executing on to access the appropriate heap.
108\item
109Need to prevent preemption during a dynamic memory operation because of the \newterm{serially-reusable problem}.
110\begin{quote}
[fb6691a]111A sequence of code that is guaranteed to run to completion before being invoked to accept another input is called serially-reusable code.~\cite{SeriallyReusable}\label{p:SeriallyReusable}
[db4a8cf]112\end{quote}
113If a KT is preempted during an allocation operation, the operating system can schedule another KT on the same CPU, which can begin an allocation operation before the previous operation associated with this CPU has completed, invalidating heap correctness.
114Note, the serially-reusable problem can occur in sequential programs with preemption, if the signal handler calls the preempted function, unless the function is serially reusable.
115Essentially, the serially-reusable problem is a race condition on an unprotected critical section, where the operating system is providing the second thread via the signal handler.
[a953c2e3]116
[db4a8cf]117Library @librseq@~\cite{librseq} was used to perform a fast determination of the CPU and to ensure all memory operations complete on one CPU using @librseq@'s restartable sequences, which restart the critical section after undoing its writes, if the critical section is preempted.
118\end{itemize}
119Tests showed that @librseq@ can determine the particular CPU quickly but setting up the restartable critical-section along the allocation fast-path produced a significant increase in allocation costs.
120Also, the number of undoable writes in @librseq@ is limited and restartable sequences cannot deal with user-level thread (UT) migration across KTs.
121For example, UT$_1$ is executing a memory operation by KT$_1$ on CPU$_1$ and a time-slice preemption occurs.
122The signal handler context switches UT$_1$ onto the user-level ready-queue and starts running UT$_2$ on KT$_1$, which immediately calls a memory operation.
123Since KT$_1$ is still executing on CPU$_1$, @librseq@ takes no action because it assumes KT$_1$ is still executing the same critical section.
124Then UT$_1$ is scheduled onto KT$_2$ by the user-level scheduler, and its memory operation continues in parallel with UT$_2$ using references into the heap associated with CPU$_1$, which corrupts CPU$_1$'s heap.
[23f1065]125If @librseq@ had an @rseq_abort@ which:
126\begin{enumerate}
127\item
128Marked the current restartable critical-section as cancelled so it restarts when attempting to commit.
129\item
130Do nothing if there is no current restartable critical section in progress.
131\end{enumerate}
132Then @rseq_abort@ could be called on the backside of a  user-level context-switching.
133A feature similar to this idea might exist for hardware transactional-memory.
[db4a8cf]134A significant effort was made to make this approach work but its complexity, lack of robustness, and performance costs resulted in its rejection.
135
136\paragraph{1:1 model}
137This design is the T:H model with T = H, where there is one thread-local heap for each KT.
138(See \VRef[Figure]{f:THSharedHeaps} but with a heap bucket per KT and no bucket or local-pool lock.)
139Hence, immediately after a KT starts, its heap is created and just before a KT terminates, its heap is (logically) deleted.
[fb6691a]140Heaps are uncontended for a KTs memory operations as every KT has its own thread-local heap, modulo operations on the global pool and ownership.
[db4a8cf]141
142Problems:
[a953c2e3]143\begin{itemize}
144\item
[29d8c02]145Need to know when a KT starts/terminates to create/delete its heap.
[db4a8cf]146
147\noindent
148It is possible to leverage constructors/destructors for thread-local objects to get a general handle on when a KT starts/terminates.
[a953c2e3]149\item
[db4a8cf]150There is a classic \newterm{memory-reclamation} problem for ownership because storage passed to another thread can be returned to a terminated heap.
151
152\noindent
153The classic solution only deletes a heap after all referents are returned, which is complex.
154The cheap alternative is for heaps to persist for program duration to handle outstanding referent frees.
155If old referents return storage to a terminated heap, it is handled in the same way as an active heap.
156To prevent heap blowup, terminated heaps can be reused by new KTs, where a reused heap may be populated with free storage from a prior KT (external fragmentation).
157In most cases, heap blowup is not a problem because programs have a small allocation set-size, so the free storage from a prior KT is apropos for a new KT.
[a953c2e3]158\item
[db4a8cf]159There can be significant external fragmentation as the number of KTs increases.
160
161\noindent
162In many concurrent applications, good performance is achieved with the number of KTs proportional to the number of CPUs.
[29d8c02]163Since the number of CPUs is relatively small, and a heap is also relatively small, $\approx$10K bytes (not including any associated freed storage), the worst-case external fragmentation is still small compared to the RAM available on large servers with many CPUs.
[a953c2e3]164\item
[db4a8cf]165There is the same serially-reusable problem with UTs migrating across KTs.
[a953c2e3]166\end{itemize}
[db4a8cf]167Tests showed this design produced the closest performance match with the best current allocators, and code inspection showed most of these allocators use different variations of this approach.
168
169
170\vspace{5pt}
171\noindent
[23f1065]172The conclusion from this design exercise is: any atomic fence, atomic instruction (lock free), or lock along the allocation fastpath produces significant slowdown.
[29d8c02]173For the T:1 and T:H models, locking must exist along the allocation fastpath because the buckets or heaps might be shared by multiple threads, even when KTs $\le$ N.
[db4a8cf]174For the T:H=CPU and 1:1 models, locking is eliminated along the allocation fastpath.
175However, T:H=CPU has poor operating-system support to determine the CPU id (heap id) and prevent the serially-reusable problem for KTs.
176More operating system support is required to make this model viable, but there is still the serially-reusable problem with user-level threading.
[29d8c02]177So the 1:1 model had no atomic actions along the fastpath and no special operating-system support requirements.
[bb7c77d]178The 1:1 model still has the serially-reusable problem with user-level threading, which is addressed in \VRef{s:UserlevelThreadingSupport}, and the greatest potential for heap blowup for certain allocation patterns.
[db4a8cf]179
180
181% \begin{itemize}
182% \item
183% A decentralized design is better to centralized design because their concurrency is better across all bucket-sizes as design 1 shards a few buckets of selected sizes while other designs shards all the buckets. Decentralized designs shard the whole heap which has all the buckets with the addition of sharding @sbrk@ area. So Design 1 was eliminated.
184% \item
185% Design 2 was eliminated because it has a possibility of contention in-case of KT > N while Design 3 and 4 have no contention in any scenario.
186% \item
187% Design 3 was eliminated because it was slower than Design 4 and it provided no way to achieve user-threading safety using librseq. We had to use CFA interruption handling to achieve user-threading safety which has some cost to it.
188% that  because of 4 was already slower than Design 3, adding cost of interruption handling on top of that would have made it even slower.
189% \end{itemize}
190% Of the four designs for a low-latency memory allocator, the 1:1 model was chosen for the following reasons:
191
192% \subsection{Advantages of distributed design}
193%
194% The distributed design of llheap is concurrent to work in multi-threaded applications.
195% Some key benefits of the distributed design of llheap are as follows:
196% \begin{itemize}
197% \item
198% The bump allocation is concurrent as memory taken from @sbrk@ is sharded across all heaps as bump allocation reserve. The call to @sbrk@ will be protected using locks but bump allocation (on memory taken from @sbrk@) will not be contended once the @sbrk@ call has returned.
199% \item
200% Low or almost no contention on heap resources.
201% \item
202% It is possible to use sharing and stealing techniques to share/find unused storage, when a free list is unused or empty.
203% \item
204% Distributed design avoids unnecessary locks on resources shared across all KTs.
205% \end{itemize}
206
207\subsection{Allocation Latency}
208
209A primary goal of llheap is low latency.
210Two forms of latency are internal and external.
211Internal latency is the time to perform an allocation, while external latency is time to obtain/return storage from/to the operating system.
212Ideally latency is $O(1)$ with a small constant.
213
[29d8c02]214To obtain $O(1)$ internal latency means no searching on the allocation fastpath and largely prohibits coalescing, which leads to external fragmentation.
[db4a8cf]215The mitigating factor is that most programs have well behaved allocation patterns, where the majority of allocation operations can be $O(1)$, and heap blowup does not occur without coalescing (although the allocation footprint may be slightly larger).
216
217To obtain $O(1)$ external latency means obtaining one large storage area from the operating system and subdividing it across all program allocations, which requires a good guess at the program storage high-watermark and potential large external fragmentation.
218Excluding real-time operating-systems, operating-system operations are unbounded, and hence some external latency is unavoidable.
[bb7c77d]219The mitigating factor is that operating-system calls can often be reduced if a programmer has a sense of the storage high-watermark and the allocator is capable of using this information (see @malloc_expansion@ \VPageref{p:malloc_expansion}).
[db4a8cf]220Furthermore, while operating-system calls are unbounded, many are now reasonably fast, so their latency is tolerable and infrequent.
221
[a953c2e3]222
[9c5aef9]223%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
224
[db4a8cf]225\section{llheap Structure}
[9c5aef9]226
[db4a8cf]227\VRef[Figure]{f:llheapStructure} shows the design of llheap, which uses the following features:
[9c5aef9]228\begin{itemize}
229\item
[db4a8cf]2301:1 multiple-heap model to minimize the fastpath,
[9c5aef9]231\item
[db4a8cf]232can be built with or without heap ownership,
[9c5aef9]233\item
[db4a8cf]234headers per allocation versus containers,
[9c5aef9]235\item
[db4a8cf]236no coalescing to minimize latency,
[9c5aef9]237\item
[23f1065]238global heap memory (pool) obtained from the operating system using @mmap@ to create and reuse heaps needed by threads,
[9c5aef9]239\item
[23f1065]240local reserved memory (pool) per heap obtained from global pool,
241\item
242global reserved memory (pool) obtained from the operating system using @sbrk@ call,
243\item
244optional fast-lookup table for converting allocation requests into bucket sizes,
245\item
246optional statistic-counters table for accumulating counts of allocation operations.
[9c5aef9]247\end{itemize}
248
249\begin{figure}
250\centering
[db4a8cf]251% \includegraphics[width=0.65\textwidth]{figures/NewHeapStructure.eps}
252\input{llheap}
253\caption{llheap Structure}
254\label{f:llheapStructure}
[9c5aef9]255\end{figure}
256
[23f1065]257llheap starts by creating an array of $N$ global heaps from storage obtained using @mmap@, where $N$ is the number of computer cores, that persists for program duration.
[db4a8cf]258There is a global bump-pointer to the next free heap in the array.
[29d8c02]259When this array is exhausted, another array of heaps is allocated.
260There is a global top pointer for a intrusive linked-list to chain free heaps from terminated threads.
261When statistics are turned on, there is a global top pointer for a intrusive linked-list to chain \emph{all} the heaps, which is traversed to accumulate statistics counters across heaps using @malloc_stats@.
[db4a8cf]262
[23f1065]263When a KT starts, a heap is allocated from the current array for exclusive use by the KT.
[29d8c02]264When a KT terminates, its heap is chained onto the heap free-list for reuse by a new KT, which prevents unbounded growth of number of heaps.
265The free heaps are stored on stack so hot storage is reused first.
266Preserving all heaps, created during the program lifetime, solves the storage lifetime problem when ownership is used.
[db4a8cf]267This approach wastes storage if a large number of KTs are created/terminated at program start and then the program continues sequentially.
268llheap can be configured with object ownership, where an object is freed to the heap from which it is allocated, or object no-ownership, where an object is freed to the KT's current heap.
269
270Each heap uses segregated free-buckets that have free objects distributed across 91 different sizes from 16 to 4M.
[fb6691a]271All objects in a bucket are of the same size.
[23f1065]272The number of buckets used is determined dynamically depending on the crossover point from @sbrk@ to @mmap@ allocation using @mallopt( M_MMAP_THRESHOLD )@, \ie small objects managed by the program and large objects managed by the operating system.
273Each free bucket of a specific size has the following two lists:
[9c5aef9]274\begin{itemize}
275\item
[db4a8cf]276A free stack used solely by the KT heap-owner, so push/pop operations do not require locking.
[23f1065]277The free objects are a stack so hot storage is reused first.
[9c5aef9]278\item
[23f1065]279For ownership, a shared away-stack for KTs to return storage allocated by other KTs, so push/pop operations require locking.
280When the free stack is empty, the entire ownership stack is removed and becomes the head of the corresponding free stack.
[9c5aef9]281\end{itemize}
282
[db4a8cf]283Algorithm~\ref{alg:heapObjectAlloc} shows the allocation outline for an object of size $S$.
284First, the allocation is divided into small (@sbrk@) or large (@mmap@).
[23f1065]285For large allocations, the storage is mapped directly from the operating system.
[db4a8cf]286For small allocations, $S$ is quantized into a bucket size.
[23f1065]287Quantizing is performed using a binary search over the ordered bucket array.
[db4a8cf]288An optional optimization is fast lookup $O(1)$ for sizes < 64K from a 64K array of type @char@, where each element has an index to the corresponding bucket.
[29d8c02]289The @char@ type restricts the number of bucket sizes to 256.
[23f1065]290For $S$ > 64K, a binary search is used.
[db4a8cf]291Then, the allocation storage is obtained from the following locations (in order), with increasing latency.
292\begin{enumerate}[topsep=0pt,itemsep=0pt,parsep=0pt]
293\item
294bucket's free stack,
295\item
296bucket's away stack,
297\item
298heap's local pool
299\item
300global pool
301\item
302operating system (@sbrk@)
303\end{enumerate}
[9c5aef9]304
[23f1065]305\begin{figure}
306\vspace*{-10pt}
307\begin{algorithm}[H]
308\small
[db4a8cf]309\caption{Dynamic object allocation of size $S$}\label{alg:heapObjectAlloc}
[9c5aef9]310\begin{algorithmic}[1]
311\State $\textit{O} \gets \text{NULL}$
[23f1065]312\If {$S >= \textit{mmap-threshhold}$}
313        \State $\textit{O} \gets \text{allocate dynamic memory using system call mmap with size S}$
314\Else
[db4a8cf]315        \State $\textit{B} \gets \text{smallest free-bucket} \geq S$
[9c5aef9]316        \If {$\textit{B's free-list is empty}$}
317                \If {$\textit{B's away-list is empty}$}
318                        \If {$\textit{heap's allocation buffer} < S$}
[db4a8cf]319                                \State $\text{get allocation from global pool (which might call \lstinline{sbrk})}$
[9c5aef9]320                        \EndIf
321                        \State $\textit{O} \gets \text{bump allocate an object of size S from allocation buffer}$
322                \Else
323                        \State $\textit{merge B's away-list into free-list}$
324                        \State $\textit{O} \gets \text{pop an object from B's free-list}$
325                \EndIf
326        \Else
327                \State $\textit{O} \gets \text{pop an object from B's free-list}$
328        \EndIf
329        \State $\textit{O's owner} \gets \text{B}$
330\EndIf
331\State $\Return \textit{ O}$
332\end{algorithmic}
333\end{algorithm}
334
[23f1065]335\vspace*{-15pt}
336\begin{algorithm}[H]
337\small
338\caption{Dynamic object free at address $A$ with object ownership}\label{alg:heapObjectFreeOwn}
339\begin{algorithmic}[1]
340\If {$\textit{A mapped allocation}$}
341        \State $\text{return A's dynamic memory to system using system call \lstinline{munmap}}$
[ba897d21]342\Else
343        \State $\text{B} \gets \textit{O's owner}$
344        \If {$\textit{B is thread-local heap's bucket}$}
345                \State $\text{push A to B's free-list}$
346        \Else
347                \State $\text{push A to B's away-list}$
348        \EndIf
349\EndIf
350\end{algorithmic}
351\end{algorithm}
[a953c2e3]352
[23f1065]353\vspace*{-15pt}
354\begin{algorithm}[H]
355\small
356\caption{Dynamic object free at address $A$ without object ownership}\label{alg:heapObjectFreeNoOwn}
357\begin{algorithmic}[1]
358\If {$\textit{A mapped allocation}$}
359        \State $\text{return A's dynamic memory to system using system call \lstinline{munmap}}$
360\Else
361        \State $\text{B} \gets \textit{O's owner}$
362        \If {$\textit{B is thread-local heap's bucket}$}
363                \State $\text{push A to B's free-list}$
364        \Else
365                \State $\text{C} \gets \textit{thread local heap's bucket with same size as B}$
366                \State $\text{push A to C's free-list}$
367        \EndIf
368\EndIf
369\end{algorithmic}
[db4a8cf]370\end{algorithm}
[23f1065]371\end{figure}
[a953c2e3]372
[23f1065]373Algorithm~\ref{alg:heapObjectFreeOwn} shows the de-allocation (free) outline for an object at address $A$ with ownership.
374First, the address is divided into small (@sbrk@) or large (@mmap@).
375For large allocations, the storage is unmapped back to the operating system.
376For small allocations, the bucket associated with the request size is retrieved.
377If the bucket is local to the thread, the allocation is pushed onto the thread's associated bucket.
378If the bucket is not local to the thread, the allocation is pushed onto the owning thread's associated away stack.
379
380Algorithm~\ref{alg:heapObjectFreeNoOwn} shows the de-allocation (free) outline for an object at address $A$ without ownership.
381The algorithm is the same as for ownership except if the bucket is not local to the thread.
382Then the corresponding bucket of the owner thread is computed for the deallocating thread, and the allocation is pushed onto the deallocating thread's bucket.
383
[29d8c02]384Finally, the llheap design funnels \label{p:FunnelRoutine} all allocation/deallocation operations through the @malloc@ and @free@ routines, which are the only routines to directly access and manage the internal data structures of the heap.
[23f1065]385Other allocation operations, \eg @calloc@, @memalign@, and @realloc@, are composed of calls to @malloc@ and possibly @free@, and may manipulate header information after storage is allocated.
386This design simplifies heap-management code during development and maintenance.
387
388
389\subsection{Alignment}
390
[29d8c02]391Most dynamic memory allocations have a minimum storage alignment for the contained object(s).
[23f1065]392Often the minimum memory alignment, M, is the bus width (32 or 64-bit) or the largest register (double, long double) or largest atomic instruction (DCAS) or vector data (MMMX).
393In general, the minimum storage alignment is 8/16-byte boundary on 32/64-bit computers.
394For consistency, the object header is normally aligned at this same boundary.
[29d8c02]395Larger alignments must be a power of 2, such as page alignment (4/8K).
[23f1065]396Any alignment request, N, $\le$ the minimum alignment is handled as a normal allocation with minimal alignment.
397
398For alignments greater than the minimum, the obvious approach for aligning to address @A@ is: compute the next address that is a multiple of @N@ after the current end of the heap, @E@, plus room for the header before @A@ and the size of the allocation after @A@, moving the end of the heap to @E'@.
399\begin{center}
400\input{Alignment1}
401\end{center}
402The storage between @E@ and @H@ is chained onto the appropriate free list for future allocations.
[fb6691a]403The same approach is used for sufficiently large free blocks, where @E@ is the start of the free block, and any unused storage before @H@ or after the allocated object becomes free storage.
[23f1065]404In this approach, the aligned address @A@ is the same as the allocated storage address @P@, \ie @P@ $=$ @A@ for all allocation routines, which simplifies deallocation.
405However, if there are a large number of aligned requests, this approach leads to memory fragmentation from the small free areas around the aligned object.
406As well, it does not work for large allocations, where many memory allocators switch from program @sbrk@ to operating-system @mmap@.
407The reason is that @mmap@ only starts on a page boundary, and it is difficult to reuse the storage before the alignment boundary for other requests.
408Finally, this approach is incompatible with allocator designs that funnel allocation requests through @malloc@ as it directly manipulates management information within the allocator to optimize the space/time of a request.
409
[29d8c02]410Instead, llheap alignment is accomplished by making a \emph{pessimistic} allocation request for sufficient storage to ensure that \emph{both} the alignment and size request are satisfied, \eg:
[23f1065]411\begin{center}
412\input{Alignment2}
413\end{center}
414The amount of storage necessary is @alignment - M + size@, which ensures there is an address, @A@, after the storage returned from @malloc@, @P@, that is a multiple of @alignment@ followed by sufficient storage for the data object.
415The approach is pessimistic because if @P@ already has the correct alignment @N@, the initial allocation has already requested sufficient space to move to the next multiple of @N@.
416For this special case, there is @alignment - M@ bytes of unused storage after the data object, which subsequently can be used by @realloc@.
417
418Note, the address returned is @A@, which is subsequently returned to @free@.
419However, to correctly free the allocated object, the value @P@ must be computable, since that is the value generated by @malloc@ and returned within @memalign@.
420Hence, there must be a mechanism to detect when @P@ $\neq$ @A@ and how to compute @P@ from @A@.
421
422The llheap approach uses two headers:
423the \emph{original} header associated with a memory allocation from @malloc@, and a \emph{fake} header within this storage before the alignment boundary @A@, which is returned from @memalign@, e.g.:
424\begin{center}
425\input{Alignment2Impl}
426\end{center}
[29d8c02]427Since @malloc@ has a minimum alignment of @M@, @P@ $\neq$ @A@ only holds for alignments greater than @M@.
[23f1065]428When @P@ $\neq$ @A@, the minimum distance between @P@ and @A@ is @M@ bytes, due to the pessimistic storage-allocation.
429Therefore, there is always room for an @M@-byte fake header before @A@.
430
431The fake header must supply an indicator to distinguish it from a normal header and the location of address @P@ generated by @malloc@.
432This information is encoded as an offset from A to P and the initialize alignment (discussed in \VRef{s:ReallocStickyProperties}).
433To distinguish a fake header from a normal header, the least-significant bit of the alignment is used because the offset participates in multiple calculations, while the alignment is just remembered data.
434\begin{center}
435\input{FakeHeader}
436\end{center}
437
438
439\subsection{\lstinline{realloc} and Sticky Properties}
440\label{s:ReallocStickyProperties}
441
[29d8c02]442The allocation routine @realloc@ provides a memory-management pattern for shrinking/enlarging an existing allocation, while maintaining some or all of the object data, rather than performing the following steps manually.
[23f1065]443\begin{flushleft}
444\begin{tabular}{ll}
445\multicolumn{1}{c}{\textbf{realloc pattern}} & \multicolumn{1}{c}{\textbf{manually}} \\
446\begin{lstlisting}
447T * naddr = realloc( oaddr, newSize );
448
449
450
451\end{lstlisting}
452&
453\begin{lstlisting}
454T * naddr = (T *)malloc( newSize ); $\C[2.4in]{// new storage}$
455memcpy( naddr, addr, oldSize );  $\C{// copy old bytes}$
456free( addr );                           $\C{// free old storage}$
457addr = naddr;                           $\C{// change pointer}\CRT$
458\end{lstlisting}
459\end{tabular}
460\end{flushleft}
461The realloc pattern leverages available storage at the end of an allocation due to bucket sizes, possibly eliminating a new allocation and copying.
462This pattern is not used enough to reduce storage management costs.
[29d8c02]463In fact, if @oaddr@ is @nullptr@, @realloc@ does a @malloc@, so even the initial @malloc@ can be a @realloc@ for consistency in the allocation pattern.
[23f1065]464
465The hidden problem for this pattern is the effect of zero fill and alignment with respect to reallocation.
466Are these properties transient or persistent (``sticky'')?
[29d8c02]467For example, when memory is initially allocated by @calloc@ or @memalign@ with zero fill or alignment properties, respectively, what happens when those allocations are given to @realloc@ to change size?
468That is, if @realloc@ logically extends storage into unused bucket space or allocates new storage to satisfy a size change, are initial allocation properties preserved?
[23f1065]469Currently, allocation properties are not preserved, so subsequent use of @realloc@ storage may cause inefficient execution or errors due to lack of zero fill or alignment.
470This silent problem is unintuitive to programmers and difficult to locate because it is transient.
471To prevent these problems, llheap preserves initial allocation properties for the lifetime of an allocation and the semantics of @realloc@ are augmented to preserve these properties, with additional query routines.
472This change makes the realloc pattern efficient and safe.
473
474
475\subsection{Header}
476
477To preserve allocation properties requires storing additional information with an allocation,
[29d8c02]478The best available option is the header, where \VRef[Figure]{f:llheapNormalHeader} shows the llheap storage layout.
[23f1065]479The header has two data field sized appropriately for 32/64-bit alignment requirements.
480The first field is a union of three values:
481\begin{description}
482\item[bucket pointer]
483is for allocated storage and points back to the bucket associated with this storage requests (see \VRef[Figure]{f:llheapStructure} for the fields accessible in a bucket).
484\item[mapped size]
485is for mapped storage and is the storage size for use in unmapping.
486\item[next free block]
487is for free storage and is an intrusive pointer chaining same-size free blocks onto a bucket's free stack.
488\end{description}
489The second field remembers the request size versus the allocation (bucket) size, \eg request 42 bytes which is rounded up to 64 bytes.
[fb6691a]490Since programmers think in request sizes rather than allocation sizes, the request size allows better generation of statistics or errors and also helps in memory management.
[a953c2e3]491
[23f1065]492\begin{figure}
493\centering
494\input{Header}
495\caption{llheap Normal Header}
496\label{f:llheapNormalHeader}
497\end{figure}
[a953c2e3]498
[fb6691a]499The low-order 3-bits of the first field are \emph{unused} for any stored values as these values are 16-byte aligned by default, whereas the second field may use all of its bits.
[23f1065]500The 3 unused bits are used to represent mapped allocation, zero filled, and alignment, respectively.
501Note, the alignment bit is not used in the normal header and the zero-filled/mapped bits are not used in the fake header.
502This implementation allows a fast test if any of the lower 3-bits are on (@&@ and compare).
503If no bits are on, it implies a basic allocation, which is handled quickly;
504otherwise, the bits are analysed and appropriate actions are taken for the complex cases.
[29d8c02]505Since most allocations are basic, they will take significantly less time as the memory operations will be done along the allocation and free fastpath.
[3a038fa]506
507
[bb7c77d]508\section{Statistics and Debugging}
[3a038fa]509
[23f1065]510llheap can be built to accumulate fast and largely contention-free allocation statistics to help understand allocation behaviour.
511Incrementing statistic counters must appear on the allocation fastpath.
512As noted, any atomic operation along the fastpath produces a significant increase in allocation costs.
513To make statistics performant enough for use on running systems, each heap has its own set of statistic counters, so heap operations do not require atomic operations.
[15885de9]514
[23f1065]515To locate all statistic counters, heaps are linked together in statistics mode, and this list is locked and traversed to sum all counters across heaps.
516Note, the list is locked to prevent errors traversing an active list;
[fb6691a]517the statistics counters are not locked and can flicker during accumulation.
[23f1065]518\VRef[Figure]{f:StatiticsOutput} shows an example of statistics output, which covers all allocation operations and information about deallocating storage not owned by a thread.
519No other memory allocator studied provides as comprehensive statistical information.
[29d8c02]520Finally, these statistics were invaluable during the development of this thesis for debugging and verifying correctness and should be equally valuable to application developers.
[1f8dbfe]521
[23f1065]522\begin{figure}
523\begin{lstlisting}
524Heap statistics: (storage request / allocation)
525  malloc >0 calls 2,766; 0 calls 2,064; storage 12,715 / 13,367 bytes
526  aalloc >0 calls 0; 0 calls 0; storage 0 / 0 bytes
527  calloc >0 calls 6; 0 calls 0; storage 1,008 / 1,104 bytes
528  memalign >0 calls 0; 0 calls 0; storage 0 / 0 bytes
529  amemalign >0 calls 0; 0 calls 0; storage 0 / 0 bytes
530  cmemalign >0 calls 0; 0 calls 0; storage 0 / 0 bytes
531  resize >0 calls 0; 0 calls 0; storage 0 / 0 bytes
532  realloc >0 calls 0; 0 calls 0; storage 0 / 0 bytes
533  free !null calls 2,766; null calls 4,064; storage 12,715 / 13,367 bytes
534  away pulls 0; pushes 0; storage 0 / 0 bytes
535  sbrk calls 1; storage 10,485,760 bytes
536  mmap calls 10,000; storage 10,000 / 10,035 bytes
537  munmap calls 10,000; storage 10,000 / 10,035 bytes
538  threads started 4; exited 3
539  heaps new 4; reused 0
540\end{lstlisting}
541\caption{Statistics Output}
542\label{f:StatiticsOutput}
543\end{figure}
[15885de9]544
[23f1065]545llheap can also be built with debug checking, which inserts many asserts along all allocation paths.
546These assertions detect incorrect allocation usage, like double frees, unfreed storage, or memory corruptions because internal values (like header fields) are overwritten.
547These checks are best effort as opposed to complete allocation checking as in @valgrind@.
548Nevertheless, the checks detect many allocation problems.
549There is an unfortunate problem in detecting unfreed storage because some library routines assume their allocations have life-time duration, and hence, do not free their storage.
[29d8c02]550For example, @printf@ allocates a 1024-byte buffer on the first call and never deletes this buffer.
[bb7c77d]551To prevent a false positive for unfreed storage, it is possible to specify an amount of storage that is never freed (see @malloc_unfreed@ \VPageref{p:malloc_unfreed}), and it is subtracted from the total allocate/free difference.
[23f1065]552Determining the amount of never-freed storage is annoying, but once done, any warnings of unfreed storage are application related.
[1f8dbfe]553
[29d8c02]554Tests indicate only a 30\% performance decrease when statistics \emph{and} debugging are enabled, and the latency cost for accumulating statistic is mitigated by limited calls, often only one at the end of the program.
[15885de9]555
[1f8dbfe]556
[23f1065]557\section{User-level Threading Support}
[bb7c77d]558\label{s:UserlevelThreadingSupport}
[15885de9]559
[fb6691a]560The serially-reusable problem (see \VPageref{p:SeriallyReusable}) occurs for kernel threads in the ``T:H model, H = number of CPUs'' model and for user threads in the ``1:1'' model, where llheap uses the ``1:1'' model.
561The solution is to prevent interrupts that can result in a CPU or KT change during operations that are logically critical sections such as starting a memory operation on one KT and completing it on another.
[23f1065]562Locking these critical sections negates any attempt for a quick fastpath and results in high contention.
563For user-level threading, the serially-reusable problem appears with time slicing for preemptable scheduling, as the signal handler context switches to another user-level thread.
[29d8c02]564Without time slicing, a user thread performing a long computation can prevent the execution of (starve) other threads.
[fb6691a]565To prevent starvation for a memory-allocation-intensive thread, \ie the time slice always triggers in an allocation critical-section for one thread so the thread never gets time sliced, a thread-local \newterm{rollforward} flag is set in the signal handler when it aborts a time slice.
[23f1065]566The rollforward flag is tested at the end of each allocation funnel routine (see \VPageref{p:FunnelRoutine}), and if set, it is reset and a volunteer yield (context switch) is performed to allow other threads to execute.
567
[29d8c02]568llheap uses two techniques to detect when execution is in an allocation operation or routine called from allocation operation, to abort any time slice during this period.
[fb6691a]569On the slowpath when executing expensive operations, like @sbrk@ or @mmap@, interrupts are disabled/enabled by setting kernel-thread-local flags so the signal handler aborts immediately.
570On the fastpath, disabling/enabling interrupts is too expensive as accessing kernel-thread-local storage can be expensive and not user-thread-safe.
[23f1065]571For example, the ARM processor stores the thread-local pointer in a coprocessor register that cannot perform atomic base-displacement addressing.
[fb6691a]572Hence, there is a window between loading the kernel-thread-local pointer from the coprocessor register into a normal register and adding the displacement when a time slice can move a thread.
[23f1065]573
[29d8c02]574The fast technique (with lower run time cost) is to define a special code section and places all non-interruptible routines in this section.
[23f1065]575The linker places all code in this section into a contiguous block of memory, but the order of routines within the block is unspecified.
576Then, the signal handler compares the program counter at the point of interrupt with the the start and end address of the non-interruptible section, and aborts if executing within this section and sets the rollforward flag.
577This technique is fragile because any calls in the non-interruptible code outside of the non-interruptible section (like @sbrk@) must be bracketed with disable/enable interrupts and these calls must be along the slowpath.
578Hence, for correctness, this approach requires inspection of generated assembler code for routines placed in the non-interruptible section.
579This issue is mitigated by the llheap funnel design so only funnel routines and a few statistics routines are placed in the non-interruptible section and their assembler code examined.
[29d8c02]580These techniques are used in both the \uC and \CFA versions of llheap as both of these systems have user-level threading.
[23f1065]581
582
583\section{Bootstrapping}
584
585There are problems bootstrapping a memory allocator.
586\begin{enumerate}
[1f8dbfe]587\item
[23f1065]588Programs can be statically or dynamically linked.
[1f8dbfe]589\item
[fb6691a]590The order in which the linker schedules startup code is poorly supported so it cannot be controlled entirely.
[1f8dbfe]591\item
[23f1065]592Knowing a KT's start and end independently from the KT code is difficult.
593\end{enumerate}
594
595For static linking, the allocator is loaded with the program.
596Hence, allocation calls immediately invoke the allocator operation defined by the loaded allocation library and there is only one memory allocator used in the program.
597This approach allows allocator substitution by placing an allocation library before any other in the linked/load path.
598
599Allocator substitution is similar for dynamic linking, but the problem is that the dynamic loader starts first and needs to perform dynamic allocations \emph{before} the substitution allocator is loaded.
600As a result, the dynamic loader uses a default allocator until the substitution allocator is loaded, after which all allocation operations are handled by the substitution allocator, including from the dynamic loader.
601Hence, some part of the @sbrk@ area may be used by the default allocator and statistics about allocation operations cannot be correct.
602Furthermore, dynamic linking goes through trampolines, so there is an additional cost along the allocator fastpath for all allocation operations.
[29d8c02]603Testing showed up to a 5\% performance decrease with dynamic linking as compared to static linking, even when using @tls_model("initial-exec")@ so the dynamic loader can obtain tighter binding.
[23f1065]604
605All allocator libraries need to perform startup code to initialize data structures, such as the heap array for llheap.
[29d8c02]606The problem is getting initialization done before the first allocator call.
[23f1065]607However, there does not seem to be mechanism to tell either the static or dynamic loader to first perform initialization code before any calls to a loaded library.
[fb6691a]608Also, initialization code of other libraries and the run-time environment may call memory allocation routines such as \lstinline{malloc}.
609This compounds the situation as there is no mechanism to tell either the static or dynamic loader to first perform the initialization code of the memory allocator before any other initialization that may involve a dynamic memory allocation call.
[23f1065]610As a result, calls to allocation routines occur without initialization.
611To deal with this problem, it is necessary to put a conditional initialization check along the allocation fastpath to trigger initialization (singleton pattern).
612
613Two other important execution points are program startup and termination, which include prologue or epilogue code to bootstrap a program, which programmers are unaware of.
614For example, dynamic-memory allocations before/after the application starts should not be considered in statistics because the application does not make these calls.
615llheap establishes these two points using routines:
616\begin{lstlisting}
617__attribute__(( constructor( 100 ) )) static void startup( void ) {
618        // clear statistic counters
619        // reset allocUnfreed counter
620}
621__attribute__(( destructor( 100 ) )) static void shutdown( void ) {
622        // sum allocUnfreed for all heaps
623        // subtract global unfreed storage
624        // if allocUnfreed > 0 then print warning message
625}
626\end{lstlisting}
627which use global constructor/destructor priority 100, where the linker calls these routines at program prologue/epilogue in increasing/decreasing order of priority.
628Application programs may only use global constructor/destructor priorities greater than 100.
629Hence, @startup@ is called after the program prologue but before the application starts, and @shutdown@ is called after the program terminates but before the program epilogue.
630By resetting counters in @startup@, prologue allocations are ignored, and checking unfreed storage in @shutdown@ checks only application memory management, ignoring the program epilogue.
631
632While @startup@/@shutdown@ apply to the program KT, a concurrent program creates additional KTs that do not trigger these routines.
633However, it is essential for the allocator to know when each KT is started/terminated.
634One approach is to create a thread-local object with a construct/destructor, which is triggered after a new KT starts and before it terminates, respectively.
635\begin{lstlisting}
636struct ThreadManager {
637        volatile bool pgm_thread;
638        ThreadManager() {} // unusable
639        ~ThreadManager() { if ( pgm_thread ) heapManagerDtor(); }
640};
641static thread_local ThreadManager threadManager;
642\end{lstlisting}
643Unfortunately, thread-local variables are created lazily, \ie on the first dereference of @threadManager@, which then triggers its constructor.
644Therefore, the constructor is useless for knowing when a KT starts because the KT must reference it, and the allocator does not control the application KT.
645Fortunately, the singleton pattern needed for initializing the program KT also triggers KT allocator initialization, which can then reference @pgm_thread@ to call @threadManager@'s constructor, otherwise its destructor is not called.
[29d8c02]646Now when a KT terminates, @~ThreadManager@ is called to chain it onto the global-heap free-stack, where @pgm_thread@ is set to true only for the program KT.
[23f1065]647The conditional destructor call prevents closing down the program heap, which must remain available because epilogue code may free more storage.
648
649Finally, there is a recursive problem when the singleton pattern dereferences @pgm_thread@ to initialize the thread-local object, because its initialization calls @atExit@, which immediately calls @malloc@ to obtain storage.
650This recursion is handled with another thread-local flag to prevent double initialization.
651A similar problem exists when the KT terminates and calls member @~ThreadManager@, because immediately afterwards, the terminating KT calls @free@ to deallocate the storage obtained from the @atExit@.
652In the meantime, the terminated heap has been put on the global-heap free-stack, and may be active by a new KT, so the @atExit@ free is handled as a free to another heap and put onto the away list using locking.
653
654For user threading systems, the KTs are controlled by the runtime, and hence, start/end pointers are known and interact directly with the llheap allocator for \uC and \CFA, which eliminates or simplifies several of these problems.
655The following API was created to provide interaction between the language runtime and the allocator.
656\begin{lstlisting}
[16d397a]657void startThread();                     $\C{// KT starts}$
658void finishThread();                    $\C{// KT ends}$
[23f1065]659void startup();                         $\C{// when application code starts}$
660void shutdown();                        $\C{// when application code ends}$
661bool traceHeap();                       $\C{// enable allocation/free printing for debugging}$
662bool traceHeapOn();                     $\C{// start printing allocation/free calls}$
663bool traceHeapOff();                    $\C{// stop printing allocation/free calls}$
664\end{lstlisting}
[29d8c02]665This kind of API is necessary to allow concurrent runtime systems to interact with different memory allocators in a consistent way.
[a953c2e3]666
667%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[1f8dbfe]668
669\section{Added Features and Methods}
[15885de9]670
[23f1065]671The C dynamic-allocation API (see \VRef[Figure]{f:CDynamicAllocationAPI}) is neither orthogonal nor complete.
672For example,
[1f8dbfe]673\begin{itemize}
674\item
[23f1065]675It is possible to zero fill or align an allocation but not both.
676\item
[bb7c77d]677It is \emph{only} possible to zero fill an array allocation.
[1f8dbfe]678\item
[23f1065]679It is not possible to resize a memory allocation without data copying.
[1f8dbfe]680\item
[23f1065]681@realloc@ does not preserve initial allocation properties.
[1f8dbfe]682\end{itemize}
[23f1065]683As a result, programmers must provide these options, which is error prone, resulting in blaming the entire programming language for a poor dynamic-allocation API.
684Furthermore, newer programming languages have better type systems that can provide safer and more powerful APIs for memory allocation.
[1f8dbfe]685
[23f1065]686\begin{figure}
687\begin{lstlisting}
688void * malloc( size_t size );
689void * calloc( size_t nmemb, size_t size );
690void * realloc( void * ptr, size_t size );
691void * reallocarray( void * ptr, size_t nmemb, size_t size );
692void free( void * ptr );
693void * memalign( size_t alignment, size_t size );
[bb7c77d]694void * aligned_alloc( size_t alignment, size_t size );
695int posix_memalign( void ** memptr, size_t alignment, size_t size );
[23f1065]696void * valloc( size_t size );
697void * pvalloc( size_t size );
[bb7c77d]698
[23f1065]699struct mallinfo mallinfo( void );
700int mallopt( int param, int val );
701int malloc_trim( size_t pad );
702size_t malloc_usable_size( void * ptr );
703void malloc_stats( void );
704int malloc_info( int options, FILE * fp );
705\end{lstlisting}
706\caption{C Dynamic-Allocation API}
707\label{f:CDynamicAllocationAPI}
708\end{figure}
[15885de9]709
[23f1065]710The following presents design and API changes for C, \CC (\uC), and \CFA, all of which are implemented in llheap.
[1f8dbfe]711
[15885de9]712
[3a038fa]713\subsection{Out of Memory}
[1f8dbfe]714
[3a038fa]715Most allocators use @nullptr@ to indicate an allocation failure, specifically out of memory;
716hence the need to return an alternate value for a zero-sized allocation.
[29d8c02]717A different approach allowed by @C API@ is to abort a program when out of memory and return @nullptr@ for a zero-sized allocation.
[bb7c77d]718In theory, notifying the programmer of memory failure allows recovery;
719in practice, it is almost impossible to gracefully recover when out of memory.
720Hence, the cheaper approach of returning @nullptr@ for a zero-sized allocation is chosen because no pseudo allocation is necessary.
[15885de9]721
[1f8dbfe]722
[23f1065]723\subsection{C Interface}
724
[bb7c77d]725For C, it is possible to increase functionality and orthogonality of the dynamic-memory API to make allocation better for programmers.
[15885de9]726
[bb7c77d]727For existing C allocation routines:
[1f8dbfe]728\begin{itemize}
729\item
[bb7c77d]730@calloc@ sets the sticky zero-fill property.
[1f8dbfe]731\item
[bb7c77d]732@memalign@, @aligned_alloc@, @posix_memalign@, @valloc@ and @pvalloc@ set the sticky alignment property.
[1f8dbfe]733\item
[bb7c77d]734@realloc@ and @reallocarray@ preserve sticky properties.
[1f8dbfe]735\end{itemize}
736
[bb7c77d]737The C dynamic-memory API is extended with the following routines:
[1f8dbfe]738
[bb7c77d]739\paragraph{\lstinline{void * aalloc( size_t dim, size_t elemSize )}}
740extends @calloc@ for allocating a dynamic array of objects without calculating the total size of array explicitly but \emph{without} zero-filling the memory.
[fb6691a]741@aalloc@ is significantly faster than @calloc@, which is the only alternative given by the standard memory-allocation routines.
[15885de9]742
[bb7c77d]743\noindent\textbf{Usage}
744@aalloc@ takes two parameters.
[1f8dbfe]745\begin{itemize}
746\item
[bb7c77d]747@dim@: number of array objects
[1f8dbfe]748\item
[bb7c77d]749@elemSize@: size of array object
[1f8dbfe]750\end{itemize}
[bb7c77d]751It returns the address of the dynamic array or @NULL@ if either @dim@ or @elemSize@ are zero.
[1f8dbfe]752
[bb7c77d]753\paragraph{\lstinline{void * resize( void * oaddr, size_t size )}}
754extends @realloc@ for resizing an existing allocation \emph{without} copying previous data into the new allocation or preserving sticky properties.
755@resize@ is significantly faster than @realloc@, which is the only alternative.
[15885de9]756
[bb7c77d]757\noindent\textbf{Usage}
758@resize@ takes two parameters.
[1f8dbfe]759\begin{itemize}
760\item
[bb7c77d]761@oaddr@: address to be resized
[1f8dbfe]762\item
[bb7c77d]763@size@: new allocation size (smaller or larger than previous)
[1f8dbfe]764\end{itemize}
[bb7c77d]765It returns the address of the old or new storage with the specified new size or @NULL@ if @size@ is zero.
[1f8dbfe]766
[23f1065]767\paragraph{\lstinline{void * amemalign( size_t alignment, size_t dim, size_t elemSize )}}
[bb7c77d]768extends @aalloc@ and @memalign@ for allocating an aligned dynamic array of objects.
769Sets sticky alignment property.
[15885de9]770
[bb7c77d]771\noindent\textbf{Usage}
772@amemalign@ takes three parameters.
[1f8dbfe]773\begin{itemize}
774\item
[bb7c77d]775@alignment@: alignment requirement
[1f8dbfe]776\item
[bb7c77d]777@dim@: number of array objects
[1f8dbfe]778\item
[bb7c77d]779@elemSize@: size of array object
[1f8dbfe]780\end{itemize}
[bb7c77d]781It returns the address of the aligned dynamic-array or @NULL@ if either @dim@ or @elemSize@ are zero.
[15885de9]782
[23f1065]783\paragraph{\lstinline{void * cmemalign( size_t alignment, size_t dim, size_t elemSize )}}
[bb7c77d]784extends @amemalign@ with zero fill and has the same usage as @amemalign@.
785Sets sticky zero-fill and alignment property.
786It returns the address of the aligned, zero-filled dynamic-array or @NULL@ if either @dim@ or @elemSize@ are zero.
[1f8dbfe]787
[23f1065]788\paragraph{\lstinline{size_t malloc_alignment( void * addr )}}
[bb7c77d]789returns the alignment of the dynamic object for use in aligning similar allocations.
[15885de9]790
[bb7c77d]791\noindent\textbf{Usage}
792@malloc_alignment@ takes one parameter.
[1f8dbfe]793\begin{itemize}
794\item
[bb7c77d]795@addr@: address of an allocated object.
[1f8dbfe]796\end{itemize}
[bb7c77d]797It returns the alignment of the given object, where objects not allocated with alignment return the minimal allocation alignment.
[1f8dbfe]798
[23f1065]799\paragraph{\lstinline{bool malloc_zero_fill( void * addr )}}
[bb7c77d]800returns true if the object has the zero-fill sticky property for use in zero filling similar allocations.
801
802\noindent\textbf{Usage}
[1eec0b0]803@malloc_zero_fill@ takes one parameters.
[15885de9]804
[1f8dbfe]805\begin{itemize}
806\item
[bb7c77d]807@addr@: address of an allocated object.
[1f8dbfe]808\end{itemize}
[bb7c77d]809It returns true if the zero-fill sticky property is set and false otherwise.
[1f8dbfe]810
[23f1065]811\paragraph{\lstinline{size_t malloc_size( void * addr )}}
[bb7c77d]812returns the request size of the dynamic object (updated when an object is resized) for use in similar allocations.
813See also @malloc_usable_size@.
[15885de9]814
[bb7c77d]815\noindent\textbf{Usage}
816@malloc_size@ takes one parameters.
[1f8dbfe]817\begin{itemize}
818\item
[bb7c77d]819@addr@: address of an allocated object.
[1f8dbfe]820\end{itemize}
[bb7c77d]821It returns the request size or zero if @addr@ is @NULL@.
[15885de9]822
[bb7c77d]823\paragraph{\lstinline{int malloc_stats_fd( int fd )}}
824changes the file descriptor where @malloc_stats@ writes statistics (default @stdout@).
[15885de9]825
[bb7c77d]826\noindent\textbf{Usage}
827@malloc_stats_fd@ takes one parameters.
[3d7d407]828\begin{itemize}
829\item
[29d8c02]830@fd@: file descriptor.
[3d7d407]831\end{itemize}
[bb7c77d]832It returns the previous file descriptor.
[3d7d407]833
[bb7c77d]834\paragraph{\lstinline{size_t malloc_expansion()}}
835\label{p:malloc_expansion}
836set the amount (bytes) to extend the heap when there is insufficient free storage to service an allocation request.
[29d8c02]837It returns the heap extension size used throughout a program when requesting more memory from the system using @sbrk@ system-call, \ie called once at heap initialization.
[3d7d407]838
[bb7c77d]839\paragraph{\lstinline{size_t malloc_mmap_start()}}
840set the crossover between allocations occurring in the @sbrk@ area or separately mapped.
841It returns the crossover point used throughout a program, \ie called once at heap initialization.
[3d7d407]842
[bb7c77d]843\paragraph{\lstinline{size_t malloc_unfreed()}}
844\label{p:malloc_unfreed}
845amount subtracted to adjust for unfreed program storage (debug only).
846It returns the new subtraction amount and called by @malloc_stats@.
[3d7d407]847
848
[bb7c77d]849\subsection{\CC Interface}
[3d7d407]850
[bb7c77d]851The following extensions take advantage of overload polymorphism in the \CC type-system.
[3d7d407]852
[bb7c77d]853\paragraph{\lstinline{void * resize( void * oaddr, size_t nalign, size_t size )}}
854extends @resize@ with an alignment re\-quirement.
[15885de9]855
[bb7c77d]856\noindent\textbf{Usage}
857takes three parameters.
[3d7d407]858\begin{itemize}
859\item
[bb7c77d]860@oaddr@: address to be resized
[3d7d407]861\item
[bb7c77d]862@nalign@: alignment requirement
[3d7d407]863\item
[bb7c77d]864@size@: new allocation size (smaller or larger than previous)
[3d7d407]865\end{itemize}
[bb7c77d]866It returns the address of the old or new storage with the specified new size and alignment, or @NULL@ if @size@ is zero.
[3d7d407]867
[bb7c77d]868\paragraph{\lstinline{void * realloc( void * oaddr, size_t nalign, size_t size )}}
869extends @realloc@ with an alignment re\-quirement and has the same usage as aligned @resize@.
[15885de9]870
[3d7d407]871
[bb7c77d]872\subsection{\CFA Interface}
[3d7d407]873
[bb7c77d]874The following extensions take advantage of overload polymorphism in the \CFA type-system.
875The key safety advantage of the \CFA type system is using the return type to select overloads;
876hence, a polymorphic routine knows the returned type and its size.
877This capability is used to remove the object size parameter and correctly cast the return storage to match the result type.
878For example, the following is the \CFA wrapper for C @malloc@:
879\begin{cfa}
880forall( T & | sized(T) ) {
881        T * malloc( void ) {
882                if ( _Alignof(T) <= libAlign() ) return @(T *)@malloc( @sizeof(T)@ ); // C allocation
883                else return @(T *)@memalign( @_Alignof(T)@, @sizeof(T)@ ); // C allocation
884        } // malloc
885\end{cfa}
886and is used as follows:
887\begin{lstlisting}
888int * i = malloc();
889double * d = malloc();
890struct Spinlock { ... } __attribute__(( aligned(128) ));
891Spinlock * sl = malloc();
892\end{lstlisting}
893where each @malloc@ call provides the return type as @T@, which is used with @sizeof@, @_Alignof@, and casting the storage to the correct type.
894This interface removes many of the common allocation errors in C programs.
895\VRef[Figure]{f:CFADynamicAllocationAPI} show the \CFA wrappers for the equivalent C/\CC allocation routines with same semantic behaviour.
[3d7d407]896
[bb7c77d]897\begin{figure}
898\begin{lstlisting}
899T * malloc( void );
900T * aalloc( size_t dim );
901T * calloc( size_t dim );
902T * resize( T * ptr, size_t size );
903T * realloc( T * ptr, size_t size );
904T * memalign( size_t align );
905T * amemalign( size_t align, size_t dim );
906T * cmemalign( size_t align, size_t dim  );
907T * aligned_alloc( size_t align );
908int posix_memalign( T ** ptr, size_t align );
909T * valloc( void );
910T * pvalloc( void );
911\end{lstlisting}
912\caption{\CFA C-Style Dynamic-Allocation API}
913\label{f:CFADynamicAllocationAPI}
914\end{figure}
[3d7d407]915
[bb7c77d]916In addition to the \CFA C-style allocator interface, a new allocator interface is provided to further increase orthogonality and usability of dynamic-memory allocation.
917This interface helps programmers in three ways.
[3d7d407]918\begin{itemize}
919\item
[29d8c02]920naming: \CFA regular and @ttype@ polymorphism (@ttype@ polymorphism in \CFA is similar to \CC variadic templates) is used to encapsulate a wide range of allocation functionality into a single routine name, so programmers do not have to remember multiple routine names for different kinds of dynamic allocations.
[3d7d407]921\item
[29d8c02]922named arguments: individual allocation properties are specified using postfix function call, so the programmers do not have to remember parameter positions in allocation calls.
[3d7d407]923\item
[29d8c02]924object size: like the \CFA's C-interface, programmers do not have to specify object size or cast allocation results.
[3d7d407]925\end{itemize}
[bb7c77d]926Note, postfix function call is an alternative call syntax, using backtick @`@, where the argument appears before the function name, \eg
927\begin{cfa}
928duration ?@`@h( int h );                // ? denote the position of the function operand
929duration ?@`@m( int m );
930duration ?@`@s( int s );
931duration dur = 3@`@h + 42@`@m + 17@`@s;
932\end{cfa}
933
934\paragraph{\lstinline{T * alloc( ... )} or \lstinline{T * alloc( size_t dim, ... )}}
[29d8c02]935is overloaded with a variable number of specific allocation operations, or an integer dimension parameter followed by a variable number of specific allocation operations.
[fb6691a]936These allocation operations can be passed as named arguments when calling the \lstinline{alloc} routine.
[bb7c77d]937A call without parameters returns a dynamically allocated object of type @T@ (@malloc@).
938A call with only the dimension (dim) parameter returns a dynamically allocated array of objects of type @T@ (@aalloc@).
939The variable number of arguments consist of allocation properties, which can be combined to produce different kinds of allocations.
940The only restriction is for properties @realloc@ and @resize@, which cannot be combined.
941
942The allocation property functions are:
943\subparagraph{\lstinline{T_align ?`align( size_t alignment )}}
944to align the allocation.
945The alignment parameter must be $\ge$ the default alignment (@libAlign()@ in \CFA) and a power of two, \eg:
946\begin{cfa}
947int * i0 = alloc( @4096`align@ );  sout | i0 | nl;
948int * i1 = alloc( 3, @4096`align@ );  sout | i1; for (i; 3 ) sout | &i1[i]; sout | nl;
949
9500x555555572000
9510x555555574000 0x555555574000 0x555555574004 0x555555574008
952\end{cfa}
953returns a dynamic object and object array aligned on a 4096-byte boundary.
954
955\subparagraph{\lstinline{S_fill(T) ?`fill ( /* various types */ )}}
956to initialize storage.
957There are three ways to fill storage:
958\begin{enumerate}
[3d7d407]959\item
[bb7c77d]960A char fills each byte of each object.
[3d7d407]961\item
[bb7c77d]962An object of the returned type fills each object.
[3d7d407]963\item
[bb7c77d]964An object array pointer fills some or all of the corresponding object array.
965\end{enumerate}
966For example:
967\begin{cfa}[numbers=left]
968int * i0 = alloc( @0n`fill@ );  sout | *i0 | nl;  // disambiguate 0
969int * i1 = alloc( @5`fill@ );  sout | *i1 | nl;
970int * i2 = alloc( @'\xfe'`fill@ ); sout | hex( *i2 ) | nl;
971int * i3 = alloc( 5, @5`fill@ );  for ( i; 5 ) sout | i3[i]; sout | nl;
972int * i4 = alloc( 5, @0xdeadbeefN`fill@ );  for ( i; 5 ) sout | hex( i4[i] ); sout | nl;
973int * i5 = alloc( 5, @i3`fill@ );  for ( i; 5 ) sout | i5[i]; sout | nl;
974int * i6 = alloc( 5, @[i3, 3]`fill@ );  for ( i; 5 ) sout | i6[i]; sout | nl;
975\end{cfa}
976\begin{lstlisting}[numbers=left]
9770
9785
9790xfefefefe
9805 5 5 5 5
9810xdeadbeef 0xdeadbeef 0xdeadbeef 0xdeadbeef 0xdeadbeef
9825 5 5 5 5
9835 5 5 -555819298 -555819298  // two undefined values
984\end{lstlisting}
[29d8c02]985Examples 1 to 3 fill an object with a value or characters.
986Examples 4 to 7 fill an array of objects with values, another array, or part of an array.
[bb7c77d]987
988\subparagraph{\lstinline{S_resize(T) ?`resize( void * oaddr )}}
989used to resize, realign, and fill, where the old object data is not copied to the new object.
990The old object type may be different from the new object type, since the values are not used.
991For example:
992\begin{cfa}[numbers=left]
993int * i = alloc( @5`fill@ );  sout | i | *i;
994i = alloc( @i`resize@, @256`align@, @7`fill@ );  sout | i | *i;
995double * d = alloc( @i`resize@, @4096`align@, @13.5`fill@ );  sout | d | *d;
996\end{cfa}
997\begin{lstlisting}[numbers=left]
9980x55555556d5c0 5
9990x555555570000 7
10000x555555571000 13.5
1001\end{lstlisting}
1002Examples 2 to 3 change the alignment, fill, and size for the initial storage of @i@.
1003
1004\begin{cfa}[numbers=left]
1005int * ia = alloc( 5, @5`fill@ );  for ( i; 5 ) sout | ia[i]; sout | nl;
1006ia = alloc( 10, @ia`resize@, @7`fill@ ); for ( i; 10 ) sout | ia[i]; sout | nl;
1007sout | ia; ia = alloc( 5, @ia`resize@, @512`align@, @13`fill@ ); sout | ia; for ( i; 5 ) sout | ia[i]; sout | nl;;
1008ia = alloc( 3, @ia`resize@, @4096`align@, @2`fill@ );  sout | ia; for ( i; 3 ) sout | &ia[i] | ia[i]; sout | nl;
1009\end{cfa}
1010\begin{lstlisting}[numbers=left]
10115 5 5 5 5
10127 7 7 7 7 7 7 7 7 7
10130x55555556d560 0x555555571a00 13 13 13 13 13
10140x555555572000 0x555555572000 2 0x555555572004 2 0x555555572008 2
1015\end{lstlisting}
1016Examples 2 to 4 change the array size, alignment and fill for the initial storage of @ia@.
1017
1018\subparagraph{\lstinline{S_realloc(T) ?`realloc( T * a ))}}
1019used to resize, realign, and fill, where the old object data is copied to the new object.
[29d8c02]1020The old object type must be the same as the new object type, since the value is used.
[bb7c77d]1021Note, for @fill@, only the extra space after copying the data from the old object is filled with the given parameter.
1022For example:
1023\begin{cfa}[numbers=left]
1024int * i = alloc( @5`fill@ );  sout | i | *i;
1025i = alloc( @i`realloc@, @256`align@ );  sout | i | *i;
1026i = alloc( @i`realloc@, @4096`align@, @13`fill@ );  sout | i | *i;
1027\end{cfa}
1028\begin{lstlisting}[numbers=left]
10290x55555556d5c0 5
10300x555555570000 5
10310x555555571000 5
1032\end{lstlisting}
1033Examples 2 to 3 change the alignment for the initial storage of @i@.
[29d8c02]1034The @13`fill@ in example 3 does nothing because no extra space is added.
[bb7c77d]1035
1036\begin{cfa}[numbers=left]
1037int * ia = alloc( 5, @5`fill@ );  for ( i; 5 ) sout | ia[i]; sout | nl;
1038ia = alloc( 10, @ia`realloc@, @7`fill@ ); for ( i; 10 ) sout | ia[i]; sout | nl;
1039sout | ia; ia = alloc( 1, @ia`realloc@, @512`align@, @13`fill@ ); sout | ia; for ( i; 1 ) sout | ia[i]; sout | nl;;
1040ia = alloc( 3, @ia`realloc@, @4096`align@, @2`fill@ );  sout | ia; for ( i; 3 ) sout | &ia[i] | ia[i]; sout | nl;
1041\end{cfa}
1042\begin{lstlisting}[numbers=left]
10435 5 5 5 5
10445 5 5 5 5 7 7 7 7 7
10450x55555556c560 0x555555570a00 5
10460x555555571000 0x555555571000 5 0x555555571004 2 0x555555571008 2
1047\end{lstlisting}
1048Examples 2 to 4 change the array size, alignment and fill for the initial storage of @ia@.
[29d8c02]1049The @13`fill@ in example 3 does nothing because no extra space is added.
[3d7d407]1050
[bb7c77d]1051These \CFA allocation features are used extensively in the development of the \CFA runtime.
Note: See TracBrowser for help on using the repository browser.