source: doc/theses/mike_brooks_MMath/string.tex @ 5e8d75bb

Last change on this file since 5e8d75bb was 5e8d75bb, checked in by Michael Brooks <mlbrooks@…>, 6 weeks ago

Thesis, string, flesh out introductory comparison

  • Property mode set to 100644
File size: 43.9 KB
Line 
1\chapter{String}
2
3This chapter presents my work on designing and building a modern string type in \CFA.
4The discussion starts with examples of interesting string problems, followed by examples of how these issues are solved in my design.
5
6
7\section{String Operations}
8
9To prepare for the following discussion, comparisons among C, \CC, Java and \CFA strings are presented, beginning in \VRef[Figure]{f:StrApiCompare}.
10It provides a classic ``cheat sheet'' presentation, summarizing the names of the typical operations.
11
12\begin{figure}
13\begin{cquote}
14\begin{tabular}{@{}l|l|l|l@{}}
15C @char [ ]@                    &  \CC @string@                 & Java @String@     & \CFA @string@     \\
16\hline
17@strcpy@, @strncpy@             & @=@                                   & @=@               & @=@       \\
18@strcat@, @strncat@             & @+@                                   & @+@               & @+@       \\
19@strcmp@, @strncmp@             & @==@, @!=@, @<@, @<=@, @>@, @>=@
20                                                & @equals@, @compareTo@
21                                                                                                                    & @==@, @!=@, @<@, @<=@, @>@, @>=@ \\
22@strlen@                                & @length@, @size@              & @length@                      & @size@        \\
23@[ ]@                                   & @[ ]@                                 & @charAt@          & @[ ]@     \\
24@strncpy@                               & @substr@                              & @substring@       & @( )@     \\
25@strncpy@                               & @replace@                             & @replace@         & @=@ \emph{(on a substring)}\\
26@strstr@                                & @find@                                & @indexOf@         & @find@ \\
27@strcspn@                               & @find_first_of@               & @matches@         & @include@ \\
28@strspn@                                & @find_first_not_of@   & @matches@         & @exclude@ \\
29n/a                                             & @c_str@, @data@               & n/a               & @strcpy@, @strncpy@ \\
30\end{tabular}
31\end{cquote}
32\caption{Comparison of languages' strings, API/``cheat-sheet'' perspective.}
33\label{f:StrApiCompare}
34\end{figure}
35
36The key commonality is that operations work on groups of characters for assigning, copying, scanning, and updating.
37Because a C string is null terminated and requires explicit storage management \see{\VRef{s:String}}, most of its group operations are error prone and expensive.
38Most high-level string libraries use a separate length field and specialized storage management to support group operations.
39\CC strings retain null termination to interface with library functions requiring C strings.
40\begin{cfa}
41int open( const char * pathname, int flags );
42string fname{ "test.cc" );
43open( fname.@c_str()@, O_RDONLY );
44\end{cfa}
45The function @c_str@ does not create a new null-terminated C string from the \CC string, as that requires passing ownership of the C string to the caller for eventual deletion.\footnote{
46C functions like \lstinline{strdup} do return allocated storage that must be freed by the caller.}
47Instead, each \CC string is null terminated just in case it might be needed for this purpose.
48Providing this backwards compatibility with C has a ubiquitous performance and storage cost.
49
50While \VRef[Figure]{f:StrApiCompare} emphasizes cross-language similarities, it elides differences in how a certain function is used.
51For example, the @replace@ function in \CC performs a modification on its @this@ parameter, while the Java one allocates and returns a new string with the result, leaving @this@ unmodified.
52Generally, Java's @String@ type is immutable.
53Java provides a @StringBuffer@ near-analog that is mutable, but the differences are significant; for example, this class's @substring@ functions still return copies rather than mutable selections.
54
55These more significant differences are summarized in \VRef[Figure]{f:StrSemanticCompare}.  It calls out the consequences of each language taking a different approach on the ``internal'' issues, like storage management and null-terminator interoperability.  The discussion following justifies the figure's yes/no entries.
56
57\begin{figure}
58\begin{tabular}{@{}p{0.5in}p{2in}p{2in}>{\centering\arraybackslash}p{0.2in}>{\centering\arraybackslash}>{\centering\arraybackslash}p{0.2in}>{\centering\arraybackslash}p{0.2in}>{\centering\arraybackslash}p{0.2in}@{}}
59                                        &                       &                       & \multicolumn{4}{c}{\underline{Supports Helpful?}} \\
60                                        & Required      & Helpful       & C                     & \CC           & Java          & \CFA \\
61\hline
62Type abst'n
63                                        & Low-level: A ``string'' type represents a varying amount of text that is communicated with a function as a parameter/return.
64                                                                & High-level: Using a string-typed variable relieves the user of managing a distinct allocation for the text.
65                                                                                        & \xmark        & \cmark        & \cmark        & \cmark \\
66\hline
67\multirow{2}{0.5in}
68{State}
69                                        & \multirow{2}{2in}
70                                        {Fast Initialize: The target receives the characters of the original, but no time is spent copying characters.  The result is one of Alias or Snapshot.}
71                                                                & Alias: The target is a further name for the text in the original; changes made in either variable are visible in both.
72                                                                                        & \cmark        & \cmark        & \xmark        & \cmark \\
73\cline{3-7}
74                                        &
75                                                                & Snapshot: The target (resp.\ source) contains the value of the source at the time of the initialization until the target (resp.\ source) is explicitly changed.
76                                                                                        & \xmark        & \xmark        & \cmark        & \cmark \\
77\hline
78Symmetry
79                                        & Laxed: The target’s type is anything string-like; it may have a different status concerning ownership.
80                                                                & Strict: The target’s type is the same as the original; both strings are equivalent peers concerning ownership.
81                                                                                        & --            & \xmark        & \cmark        & \cmark \\
82\hline
83Referent
84                                        & Variable-Constrained: The target can accept the entire text of the original.
85                                                                & Fragment: The target can accept an arbitrary substring of the original.
86                                                                                        & \xmark        & \xmark        & \cmark        & \cmark
87\end{tabular}
88
89\noindent
90Notes
91\begin{itemize}
92\item
93        All languages support Required in all criteria.
94\item
95        A language gets ``Supports Helpful'' in one criterion if it can do so without sacrificing the Required achievement on all other criteria.
96\item
97        The C ``string'' is @char *@, under the conventions that @<string.h>@ requires.  Symmetry is not applicable to C.
98\item
99        The Java @String@ class is analyzed; its @StringBuffer@ class behaves similarly to @C++@.
100\end{itemize}
101\caption{Comparison of languages' strings, assignment-semantics perspective.}
102\label{f:StrSemanticCompare}
103\end{figure}
104
105In C:
106\begin{cfa}
107char * s1 = ...; // assumed
108char * s2 = s1;  // alias state, variable-constrained referent
109char * s3 = s1 + 2;  // alias state, variable-constrained referent
110\end{cfa}
111The issue of symmetry is trivial for a low-level type, and so, scored as not applicable to C.
112With the type not managing the text buffer, there is no ownership question, \ie nothing done with the @s1@ or @s2@ variables leads to the memory that their text currently occupies becoming reusable.
113While @s3@ is a valid C-string that contains a proper substring of @s1@, the @s3@ technique does not constitue having a fragment referent because null termination implies the substring cannot be chosen arbitrarily; the technique works only for suffixes.
114
115In \CC:
116\begin{cfa}
117string s1 = ...; // assumed
118string & s2 = s1;  // alias state, lax symmetry, variable-constrained referent
119string s3 = s1;  // NOT fast-initialize (strict symmetry, variable-constrained referent)
120string s4 = s1.substr(2,4);  // NOT fast-initialize (strict symmetry, fragment referent)
121string & s5 = s1.substr(2,4);  // memory-use error
122\end{cfa}
123The lax symmetry of the @s2@ technique reflects how the validity of @s2@ depends on the lifetime of @s1@.
124It is common practice in \CC to use the @s2@ technique for parameter passing, but the safest-bet advice has to be that the callee can only use the referenced string for the duration of the call.
125So, when the called function is a constructor, it is typical that the implementation is doing an @s3@-style initialization of a string-object-typed member.
126Exceptions of this pattern are possible, of course, but they represent the programmer taking responsiblity to assure safety where the type system does not.
127The @s4@ initialization is constrained by specification to copy the substring because of @c_str@ being specified to be a null-terminated character run that is not its own allocation.
128TODO: address caveat that @s3@ could be done fast by reference counting in the text area.
129
130
131In Java:
132\begin{cfa}
133String s1 = ...;  // assumed
134String s2 = s1;  // snapshot state, strict symmetry, variable-constrained referent
135String s3 = s1.substring(2,4);  // snapshot state (possible), strict symmetry, fragment referent
136\end{cfa}
137Here, facts about Java's implicit pointers and pointer equality can overcomplicate the picture.
138The further fact of Java's string immutability means that string variables behave as simple values.
139The result in @s2@ is the value of @s1@, and their pointer equality certainly assures that no time is spent copying characters.
140With @s3@, the case for fast-copy is more subtle.
141Certainly, its value is not pointer-equal to @s1@, implying at least a further allocation.
142TODO: finish the fast-copy case.
143Java strings lacking mutation means that aliasing is not possible with the @String@ type.
144Java's @StringBuffer@ provides aliasing, though without supporting symmetric treatment of a fragment referent; as a result, @StringBuffer@ scores as \CC.
145The easy symmetry that the Java string enjoys is aided by Java's garbage collection; Java's @s2@ is doing effectively the operation of \CC's @s2@, though without the consequence to of complicating memory management.
146
147Finally, in \CFA,
148\begin{cfa}
149string s1 = ...; // assumed
150string s2 = s1; // snapshot state, strict symmetry, variable-constrained referent
151string s3 = s1`shareEdits; // alias state, strict symmetry, variable-constrained referent
152string s4 = s1(2,4); // snapshot state, strict symmetry, fragment referent
153string s5 = s1(2,4)`shareEdits; // alias state, strict symmetry, fragment referent
154\end{cfa}
155all helpful criteria of \VRef[Figure]{f:StrSemanticCompare} are satisfied.
156The \CFA string manages storage, handles all assignments, including those of fragment referents, with fast initialization, provides the choice between snapshot and alias semantics, does so symmetrically with one type (which assures text validity according to the lifecycles of the string variables).
157With aliasing, the intuition is that each string is an editor on an open shared document.
158With fragment aliasing, the intuition is that these editor views have been scolled or zoomed to overlapping, but potentially different, ranges.
159
160The remainder of this chapter explains how the \CFA string achieves this usage style.
161
162
163
164\section{Storage Management}
165
166This section discusses issues related to storage management of strings.
167Specifically, it is common for strings to logically overlap completely or partially.
168\begin{cfa}
169string s1 = "abcdef";
170string s2 = s1; $\C{// complete overlap, s2 == "abcdef"}$
171string s3 = s1.substr( 0, 3 ); $\C{// partial overlap, s3 == "abc"}$
172\end{cfa}
173This raises the question of how strings behave when an overlapping component is changed,
174\begin{cfa}
175s3[1] = 'w'; $\C{// what happens to s1 and s2?}$
176\end{cfa}
177This question is the notion of mutable or immutable strings.
178For example, Java has immutable strings that are copied when any overlapping string changes.
179Note, the notion of underlying string mutability is not specified by @const@, \eg:
180\begin{cfa}
181const string s1 = "abc";
182\end{cfa}
183Here, @const@ applies to the @s1@ pointer to @"abc"@, and @"abc"@ is an immutable constant that is \emph{copied} into the string's storage.
184Hence, @s1@ is not pointing at an immutable constant, meaning its underlying string is always mutable, unless some other designation is specified, such as Java's global rule.
185
186
187\subsection{Logical overlap}
188
189\CFA provides a dynamic mechanism to indicate mutable or immutable as an assignment attribute: @`shareEdits@.
190
191Consider two strings @s1@ and @s1a@ that are in an aliasing relationship, and a third, @s2@, made by a simple copy from @s1@.
192Aliasing (@`shareEdits@) means that changes flow in both directions; with a simple copy, they do not.
193\input{sharing1.tex}
194
195Aliasing (@`shareEdits@) means that changes flow in both directions; with a simple copy, they do not.
196\input{sharing2.tex}
197
198Assignment of a value is just a modification.
199The aliasing relationship is established at construction and is unaffected by assignment of a value.
200\input{sharing3.tex}
201
202Assignment from a string is just assignment of a value.
203Whether of not the RHS participates in aliasing is irrelevant.  Any aliasing of the LHS is unaffected.
204\input{sharing4.tex}
205
206Consider new strings @s1_mid@ being an alias for a run in the middle of @s1@, along with @s2@, made by a simple copy from the middle of @s1@.
207\input{sharing5.tex}
208
209Again, @`shareEdits@ passes changes in both directions; copy does not.
210Note the difference in index values, with the \emph{b} position being 1 in the longer string and 0 in the shorter strings.
211In the case of s1 aliasing with @s1_mid@, the very same character is being accessed by different positions.
212\input{sharing6.tex}
213
214Once again, assignment of a value is a modification that flows through the aliasing relationship, without affecting its structure.
215\input{sharing7.tex}
216
217In the \emph{ff} step, which is a positive example of flow across an aliasing relationship, the result is straightforward to accept because the flow direction is from contained (small) to containing (large).
218The following rules for edits through aliasing substrings will guide how to flow in the opposite direction.
219
220Growth and shrinkage are natural extensions.
221An empty substring is a real thing, at a well-defined location, whose meaning is extrapolated from the examples so far.
222The intended metaphor is to operating a GUI text editor.
223Having an aliasing substring is like using the mouse to select a few words.
224Assigning onto an aliasing substring is like typing from having a few words selected: depending how much you type, the file being edited can get shorter or longer.
225\input{sharing8.tex}
226
227Multiple portions can be aliased.
228When there are several aliasing substrings at once, the text editor analogy becomes an online multi-user editor.
229I should be able to edit a paragraph in one place (changing the document's length), without my edits affecting which letters are within a mouse-selection that you had made previously, somewhere else.
230\input{sharing9.tex}
231
232When an edit happens on an aliasing substring that overlaps another, an effect is unavoidable.
233Here, the passive party sees its selection shortened, to exclude the characters that were not part of the original selection.
234\input{sharing10.tex}
235
236TODO: finish typesetting the demo
237
238%\input{sharing-demo.tex}
239
240
241\subsection{RAII limitations}
242
243Earlier work on \CFA~\cite[ch.~2]{Schluntz17} implemented object constructors and destructors for all types (basic and user defined).
244A constructor is a user-defined function run implicitly \emph{after} an object's declaration-storage is created, and a destructor is a user-defined function run \emph{before} an object's declaration-storage is deleted.
245This feature, called RAII~\cite[p.~389]{Stroustrup94}, guarantees pre invariants for users before accessing an object and post invariants for the programming environment after an object terminates.
246
247The purposes of these invariants goes beyond ensuring authentic values inside an object.
248Invariants can also track occurrences of managed objects in other data structures.
249For example, reference counting is a typical application of an invariant outside of the data values.
250With a reference-counting smart-pointer, the constructor and destructor \emph{of a pointer type} tracks the life cycle of the object it points to.
251Both \CC and \CFA RAII systems are powerful enough to achieve reference counting.
252
253In general, a lifecycle function has access to an object by location, \ie constructors and destructors receive a @this@ parameter providing an object's memory address.
254The lifecycle implementation can then add this object to a collection at creation and remove it at destruction.
255A module providing lifecycle semantics can traverse the collection at relevant times to keep the objects ``good.''
256Hence, declaring such an object not only ensures ``good'' authentic values, but also an implicit subscription to a service that keeps the value ``good'' across its lifetime.
257
258In many cases, the relationship between memory location and lifecycle is straightforward.
259For example, stack-allocated objects being used as parameters and returns, with a sender version in one stack frame and a receiver version in another, as opposed to assignment where sender and receiver are in the same stack frame.
260What is crucial for lifecycle management is knowing if the receiver is initialized or uninitialized, \ie an object is or is not currently associated with management.
261To provide this knowledge, languages differentiate between initialization and assignment to a left-hand side.
262\begin{cfa}
263Obj obj2 = obj1;  // initialization, obj2 is uninitialized
264obj2 = obj1;        // assignment, obj2 must be initialized for management to work
265\end{cfa}
266Initialization occurs at declaration by value, parameter by argument, return temporary by function call.
267Hence, it is necessary to have two kinds of constructors: by value or object.
268\begin{cfa}
269Obj obj1{ 1, 2, 3 };  // by value, management is initialized
270Obj obj2 = obj1;     // by obj, management is updated
271\end{cfa}
272When no object management is required, initialization copies the right-hand value.
273Hence, the calling convention remains uniform, where the unmanaged case uses @memcpy@ as the initialization constructor and managed uses the specified initialization constructor.
274
275The \CFA RAII system supports lifecycle functions, except for returning a value from a function to a temporary.
276For example, in \CC:
277\begin{cfa}
278struct S {...};
279S identity( S s ) { return s; }
280S s;
281s = identity( s ); // S temp = identity( s ); s = temp;
282\end{cfa}
283the generated code for the function call created a temporary with initialization from the function call, and then assigns the temporary to the receiver.
284This two step approach means extra storage for the temporary and two copies to get the result into the receiver variable.
285\CC{17} introduced return value-optimization (RVO)~\cite{RVO20} to ``avoid copying an object that a function returns as its value, including avoiding creation of a temporary object''.
286\CFA uses C semantics for function return giving direct value-assignment, which eliminates unnecessary code, but skips an essential feature needed by lifetime management.
287The following discusses the consequences of this semantics with respect to lifetime management of \CFA strings.
288
289The present string-API contribution provides lifetime management with initialization semantics on function return.
290The workaround to achieve the full lifetime semantics does have a runtime performance penalty.
291An alternative API sacrifices return initialization semantics to recover full runtime performance.
292These APIs are layered, with the slower, friendlier High Level API (HL) wrapping the faster, more primitive Low Level API (LL).
293Both API present the same features, up to lifecycle management, with return initialization being disabled in LL and implemented with the workaround in HL.
294The intention is for most future code to target HL.
295When \CFA becomes a full compiler, it can provide return initialization with RVO optimizations.
296Then, programs written with the HL API will simply run faster.
297In the meantime, performance-critical sections of applications use LL.
298Subsequent performance experiments~\VRef{s:PerformanceAssessment} with other string libraries has \CFA strings using the LL API.
299These measurement gives a fair estimate of the goal state for \CFA.
300
301
302\subsection{Memory management}
303
304A centrepiece of the string module is its memory manager.
305The management scheme defines a shared buffer for string text.
306Allocation in this buffer is via a bump-pointer;
307the buffer is compacted and/or relocated with growth when it fills.
308A string is a smart pointer into this buffer.
309
310This cycle of frequent cheap allocations, interspersed with infrequent expensive compactions, has obvious similarities to a general-purpose memory manager based on garbage collection (GC).
311A few differences are noteworthy.
312First, in a general purpose manager, the objects of allocation contain pointers to other such objects, making the transitive reachability of these objects be a critical property.
313Here, the allocations are text, so one allocation never keeps another alive.
314Second, in a general purpose manager, the handle that keeps an allocation alive is just a lean pointer.
315For strings, a fatter representation is acceptable because there are fewer string head pointers versus chained pointers within nodes as for linked containers.
316
317\begin{figure}
318\includegraphics{memmgr-basic}
319\caption{String memory-management data structures}
320\label{f:memmgr-basic}
321\end{figure}
322
323\VRef[Figure]{f:memmgr-basic} shows the representation.
324A heap header and its text buffer, defines a sharing context.
325Normally, one global sharing context is appropriate for an entire program;
326exceptions are discussed in [xref TBD].
327A string is a handle into the buffer and linked into a list.
328The list is doubly linked for $O(1)$ insertion and removal at any location.
329Strings are orders n the list by text-buffer address, where there is no overlapping, and approximately, where there is.
330The header maintains a next-allocation pointer, @alloc@, pointing to the last live allocation of the buffer.
331No external references point into the buffer and the management procedure relocates the text allocations as needed.
332A string handle contains an explicit string, while its string is contiguous and not null terminated.
333The length sets an upper limit on the string size, but is large (4 or 8 bytes).
334String handles can be allocated in the stack or heap, while the text buffer is large enough with good management so that only one dynamic allocation is necessary for it during program execution.
335During this period strings can vary in size dynamically.
336
337When the text buffer fills, \ie the next new string allocation causes @alloc@ to point beyond the end of the buffer, the strings are compacted.
338The linked handles define all live strings in the buffer, which indirectly defines the allocated and free space in the buffer.
339Since the string handles are in (roughly) sorted order, the handle list can be traversed copying the first text to the start of the buffer and subsequent strings after each over.
340After compaction, if the amount of free storage is still less than the new string allocation, a larger text buffer is heap allocated, the current buffer is copies into the new buffer, and the original buffer is freed.
341Note, the list of string handles is unaffected during a compaction;
342only the string pointers are modified to new buffer locations.
343
344Object lifecycle events are the subscription-management triggers in such a service.
345There are two fundamental string-creation routines: importing external text like a C-string or reading a string, and initialization from an existing \CFA string.
346When importing, storage comes from the end of the buffer, into which the text is copied.
347The resultant handle is inserted at the end of the handle list to maintain ordering.
348When initializing from text already in the text buffer, the new handle is a second reference into the original run of characters.
349In this case, the new handle's linked-list position is after the original handle.
350Both string initialization styles preserve the string module's internal invariant that the linked-list order matches the buffer order.
351For string destruction, handles are removed from the list.
352
353Certain string operations can results in a subset (substring) of another string.
354The resulting handle is then place in the correct sorted position in the list, possible with a short linear search to locate the position.
355For string operations resulting in a new string, that string is allocated at the end of the buffer.
356For shared-edit strings, handles that originally referenced containing locations need to see the new value at the new buffer location.
357These strings are moved to appropriate locations at the end of the list (addressed further in [xref: TBD].
358For nonshared-edit strings, a containing string can be moved and the nonshared strings can remain in the same position.
359String assignment words similarly to string initialization, maintain the invariant of linked list order matching buffer order.
360
361At the level of the memory manager, these modifications can always be explained as assignments; for example, an append is an assignment into the empty substring at the end.
362
363While favourable conditions allow for in-place editing, the general case requires a fresh buffer run.
364For example, if the new value does not fit in the old place, or if other handles are still using the old value, then the new value will use a fresh buffer run.
365
366where there is room for the resulting value in the original buffer location, and where all handles referring to the original buffer location should see the new value,
367
368always boiled down to assignment and appendment.
369Assignment has special cases that happen in-place, but in the general case, it is implemented as a sequence of appends onto a fresh allocation at the end of the buffer.
370(The sequence has multiple steps when the assignment target is a substring: old before, new middle, old after.)
371Similarly, an append request can be serviced in-place when there is room, or as a pair of appends.
372
373
374\subsection{Sharing implementation}
375
376The \CFA string module has two manners in which several string handles can share an underlying run of characters. 
377
378The first type of sharing is user-requested, following the [xref Logical Overlap].  Here, the user requests, explicitly, that both handles be views of the same logical, modifiable string.  This state is typically produced by the substring operation.  In a typical substring call, the source string-handle is referencing an entire string, and the resulting, newly made, string handle is referencing a portion of the original.  In this state, a subsequent modification made by either is visible in both.
379
380The second type of sharing happens when the system implicitly delays the physical execution of a logical \emph{copy} operation, as part of its copy-on-write optimization.  This state is typically produced by constructing a new string, using an original string as its initialization source.  In this state, a subsequent modification done on one handle triggers the deferred copy action, leaving the handles referencing different runs within the buffer, holding distinct values.
381
382A further abstraction, in the string module's implementation, helps distinguish the two senses of sharing.  A share-edit set (SES) is an equivalence class over string handles, being the reflexive, symmetric and transitive closure of the relationship of one being constructed from the other, with the ``share edits'' opt-in given.  It is represented by a second linked list among the handles.  A string that shares edits with no other is in a SES by itself.  Inside a SES, a logical modification of one substring portion may change the logical value in another, depending on whether the two actually overlap.  Conversely, no logical value change can flow outside of a SES.  Even if a modification on one string handle does not reveal itself \emph{logically} to anther handle in the same SES (because they don't overlap), if the modification is length-changing, completing the modification requires visiting the second handle to adjust its location in the sliding text.
383
384
385\subsection{Avoiding implicit sharing}
386
387There are tradeoffs associated with the copy-on-write mechanism.  Several qualitative matters are detailed in the [xref: Performance Assessment] section and the qualitative issue of multi-threaded support is introduced here.  The \CFA sting library provides a switch to disable the sharing mechanism for situations where it is inappropriate.
388
389Because of the inter-linked string handles, any participant managing one string is also managing, directly, the neighbouring strings, and from there, a data structure of the ``set of all strings.''  This data structure is intended for sequential access.  A negative consequence of this decision is that multiple threads using strings need to be set up so that they avoid attempting to modify (concurrently) an instance of this structure.  A positive consequence is that a single-threaded program, or a program with several independent threads, can use the sharing context without an overhead from locking.
390
391The \CFA sting library provides the @string_sharectx@ type to control an ambient sharing context for the current thread.  It allows two adjustments: to opt out of sharing entirely, or to begin sharing within a private context.  Either way, the chosen mode applies to the current thread, for the duration of the lifetime of the created  @string_sharectx@ object, up to being suspended by child lifetimes of different contexts.  The indented use is with stack-managed lifetimes, in which the established context lasts until the current function returns, and affects all functions called that don't create their own contexts.
392\lstinputlisting[language=CFA, firstline=20, lastline=34]{sharectx.run.cfa}
393In this example, the single-letter functions are called in alphabetic order.  The functions @a@ and @d@ share string character ranges within themselves, but not with each other.  The functions @b@, @c@ and @e@ never share anything.
394
395[ TODO: true up with ``is thread local'' (implement that and expand this discussion to give a concurrent example, or adjust this wording) ]
396
397When the string library is running with sharing disabled, it runs without implicit thread-safety challenges (which same as the STL) and with performance goals similar to the STL's.  This thread-safety quality means concurrent users of one string object must still bring their own mutual exclusion, but the string library will not add any cross thread uses that were not apparent in the user's code.
398
399Running with sharing disabled can be thought of as STL-emulation mode.
400
401
402
403\subsection{Future work}
404
405To discuss: Unicode
406
407To discuss: Small-string optimization
408
409
410\section{Performance assessment}
411\label{s:PerformanceAssessment}
412
413I assessed the \CFA string library's speed and memory usage.  I present these results in even equivalent cases, due to either micro-optimizations foregone, or fundamental costs of the added functionality.  They also show the benefits and tradeoffs, as >100\% effects, of switching to \CFA, with the tradeoff points quantified.  The final test shows the overall win of the \CFA text-sharing mechanism.  It exercises several operations together, showing \CFA enabling clean user code to achieve performance that STL requires less-clean user code to achieve.
414
415To discuss: general goal of ... while STL makes you think about memory management, all the time, and if you do your performance can be great ... \CFA sacrifices this advantage modestly in exchange for big wins when you're not thinking about memory management.  [Does this position cover all of it?]
416
417To discuss: revisit HL v LL APIs
418
419To discuss: revisit no-sharing as STL emulation modes
420
421These tests use randomly generated text fragments of varying lengths.  A collection of such fragments is a \emph{corpus}.  The mean length of a fragment from corpus is a typical explanatory variable.  Such a length is used in one of three modes:
422\begin{description}
423    \item [Fixed-size] means all string fragments are of the stated size
424    \item [Varying from 1] means string lengths are drawn from a geometric distribution with the stated mean, and all lengths occur
425    \item [Varying from 16] means string lengths are drawn from a geometric distribution with the stated mean, but only lengths 16 and above occur; thus, the stated mean will be above 16.
426\end{description}
427The geometric distribution implies that lengths much longer than the mean occur frequently.  The special treatment of length 16 deals with comparison to STL, given that STL has short-string optimization (see [TODO: write and cross-ref future-work SSO]), currently not implemented in \CFA.  When success notwithstanding SSO is illustrated, a fixed-size or from-16 distribution ensures that extra-optimized cases are not part of the mix on the STL side.  In all experiments that use a corpus, its text is generated and loaded into the SUT before the timed phase begins.
428
429To discuss: vocabulary for reused case variables
430
431To discuss: common approach to iteration and quoted rates
432
433To discuss: hardware and such
434
435To discuss: memory allocator
436
437
438\subsubsection{Test: Append}
439
440This test measures the speed of appending fragments of text onto a growing string.  Its subcases include both \CFA being similar to STL, and their designs offering a tradeoff.
441
442One experimental variable is the user's operation being @a = a + b@ vs. @a += b@.  While experienced programmers expect the latter to be ``what you obviously should do,'' controlling the penalty of the former both helps the API be accessible to beginners and also helps offer confidence that when a user tries to compose operations, the forms that are most natural to the user's composition are viable.
443
444Another experimental variable is whether the user's logical allocation is fresh vs reused.  Here, \emph{reusing a logical allocation}, means that the program variable, into which the user is concatenating, previously held a long string:\\
445\begin{tabular}{ll}
446    Logical allocation fresh                   & Logical allocation reused                  \\
447                                               & @ string x; @                              \\
448    @ for( ... ) { @                           & @ for( ... ) { @                           \\
449    @     string x; @                          & @     x = ""; @                            \\
450    @     for( ... ) @                         & @     for( ... ) @                         \\
451    @        x += ... @                        & @        x += ... @                        \\
452    @ } @                                      & @ } @
453\end{tabular}\\
454These benchmark drivers have an outer loop for ``until a sample-worthy amount of execution has happened'' and an inner loop for ``build up the desired-length string.''  It is sensible to doubt that a user should have to care about this difference, yet the STL performs differently in these cases.  Concretely, both cases incur the cost of copying characters into the target string, but only the allocation-fresh case incurs a further reallocation cost, which is generally paid at points of doubling the length.  For the STL, this cost includes obtaining a fresh buffer from the memory allocator and copying older characters into the new buffer, while \CFA-sharing hides such a cost entirely.  The reuse-vs-fresh distinction is only relevant in the current \emph{append} tests.
455
456The \emph{append} tests use the varying-from-1 corpus construction; that is they do not assume away the STL's advantage from small-string optimization.
457
458To discuss: any other case variables introduced in the performance intro
459
460\begin{figure}
461    \includegraphics[width=\textwidth]{string-graph-peq-cppemu.png}
462    \caption{Average time per iteration with one \lstinline{x += y} invocation, comparing \CFA with STL implementations (given \CFA running in STL emulation mode), and comparing the ``fresh'' with ``reused'' reset styles, at various string sizes.}
463    \label{fig:string-graph-peq-cppemu}
464\end{figure}
465
466Figure \ref{fig:string-graph-peq-cppemu} shows this behaviour, by the STL and by \CFA in STL emulation mode.  \CFA reproduces STL's performance, up to a 15\% penalty averaged over the cases shown, diminishing with larger strings, and 50\% in the worst case.  This penalty characterizes the amount of implementation fine tuning done with STL and not done with \CFA in present state.  The larger inherent penalty, for a user mismanaging reuse, is 40\% averaged over the cases shown, is minimally 24\%, shows up consistently between the STL and \CFA implementations, and increases with larger strings.
467
468\begin{figure}
469    \includegraphics[width=\textwidth]{string-graph-peq-sharing.png}
470    \caption{Average time per iteration with one \lstinline{x += y} invocation, comparing \CFA (having implicit sharing activated) with STL, and comparing the ``fresh'' with ``reused'' reset styles, at various string sizes.}
471    \label{fig:string-graph-peq-sharing}
472\end{figure}
473
474In sharing mode, \CFA makes the fresh/reuse difference disappear, as shown in Figure \ref{fig:string-graph-peq-sharing}.  At append lengths 5 and above, \CFA not only splits the two baseline STL cases, but its slowdown of 16\% over (STL with user-managed reuse) is close to the \CFA-v-STL implementation difference seen with \CFA in STL-emulation mode.
475
476\begin{figure}
477    \includegraphics[width=\textwidth]{string-graph-pta-sharing.png}
478    \caption{Average time per iteration with one \lstinline{x = x + y} invocation (new, purple bands), comparing \CFA (having implicit sharing activated) with STL.  For context, the results from Figure \ref{fig:string-graph-peq-sharing} are repeated as the bottom bands.  While not a design goal, and not graphed out, \CFA in STL-emulation mode outperformed STL in this case; user-managed allocation reuse did not affect any of the implementations in this case.}
479    \label{fig:string-graph-pta-sharing}
480\end{figure}
481
482When the user takes a further step beyond the STL's optimal zone, by running @x = x + y@, as in Figure \ref{fig:string-graph-pta-sharing}, the STL's penalty is above $15 \times$ while \CFA's (with sharing) is under $2 \times$, averaged across the cases shown here.  Moreover, the STL's gap increases with string size, while \CFA's converges.
483
484\subsubsection{Test: Pass argument}
485
486To have introduced:  STL string library forces users to think about memory management when communicating values across a function call
487
488STL charges a prohibitive penalty for passing a string by value.  With implicit sharing active, \CFA treats this operation as normal and supported.  This test illustrates a main advantage of the \CFA sharing algorithm.  It also has a case in which STL's small-string optimization provides a successful mitigation.
489
490\begin{figure}
491    \includegraphics[width=\textwidth]{string-graph-pbv.png}
492    \caption{Average time per iteration with one call to a function that takes a by-value string argument, comparing \CFA (having implicit sharing activated) with STL.  (a) With \emph{Varying-from-1} corpus construction, in which the STL-only benefit of small-string optimization occurs, in varying degrees, at all string sizes.  (b) With \emph{Fixed-size} corpus construction, in which this benefit applies exactly to strings with length below 16.  [TODO: show version (b)]}
493    \label{fig:string-graph-pbv}
494\end{figure}
495
496Figure \ref{fig:string-graph-pbv} shows the costs for calling a function that receives a string argument by value.  STL's performance worsens as string length increases, while \CFA has the same performance at all sizes.
497
498The \CFA cost to pass is nontrivial.  The contributor is adding and removing the callee's string handle from the global list.  This cost is $1.5 \times$ to $2 \times$ over STL's when small-string optimization applies, though this cost should be avoidable in the same case, given a \CFA realization of this optimization.  At the larger sizes, when STL has to manage storage for the string, STL runs more than $3 \times$ slower, mainly due to time spent in the general-purpose memory allocator.
499
500
501\subsubsection{Test: Allocate}
502
503This test directly compares the allocation schemes of the \CFA string with sharing, compared with the STL string.  It treats the \CFA scheme as a form of garbage collection, and the STL scheme as an application of malloc-free.  The test shows that \CFA enables faster speed at a cost in memory usage.
504
505A garbage collector, afforded the freedom of managed memory, often runs faster than malloc-free (in an amortized analysis, even though it must occasionally stop to collect) because it is able to use its collection time to move objects.  (In the case of the mini-allocator powering the \CFA string library, objects are runs of text.)  Moving objects lets fresh allocations consume from a large contiguous store of available memory; the ``bump pointer'' book-keeping for such a scheme is very light.  A malloc-free implementation without the freedom to move objects must, in the general case, allocate in the spaces between existing objects; doing so entails the heavier book-keeping to navigate and maintain a linked structure.
506
507A garbage collector keeps allocations around for longer than the using program can reach them.  By contrast, a program using malloc-free (correctly) releases allocations exactly when they are no longer reachable.  Therefore, the same harness will use more memory while running under garbage collection.  A garbage collector can minimize the memory overhead by searching for these dead allocations aggressively, that is, by collecting more often.  Tuned in this way, it spends a lot of time collecting, easily so much as to overwhelm its speed advantage from bump-pointer allocation.  If it is tuned to collect rarely, then it leaves a lot of garbage allocated (waiting to be collected) but gains the advantage of little time spent doing collection.
508
509[TODO: find citations for the above knowledge]
510
511The speed for memory tradeoff is, therefore, standard for comparisons like \CFA--STL string allocations.  The test verifies that it is so and quantifies the returns available.
512
513These tests manipulate a tuning knob that controls how much extra space to use.  Specific values of this knob are not user-visible and are not presented in the results here.  Instead, its two effects (amount of space used and time per operation) are shown.  The independent variable is the liveness target, which is the fraction of the text buffer that is in use at the end of a collection.  The allocator will expand its text buffer during a collection if the actual fraction live exceeds this target.
514
515This experiment's driver allocates strings by constructing a string handle as a local variable then looping over recursive calls.  The time measurement is of nanoseconds per such allocating call.  The arrangement of recursive calls and their fan-out (iterations per recursion level) makes some of the strings long-lived and some of them short-lived.  String lifetime (measured in number of subsequent string allocations) is ?? distributed, because each node in the call tree survives as long as its descendent calls.  The run presented in this section used a call depth of 1000 and a fan-out of 1.006, which means that approximately one call in 167 makes two recursive calls, while the rest make one.  This sizing was chosen to keep the amounts of consumed memory within the machine's last-level cache.
516
517\begin{figure}
518    \includegraphics[width=\textwidth]{string-graph-allocn.png}
519    \caption{Space and time performance, under varying fraction-live targets, for the five string lengths shown, at (\emph{Fixed-size} corpus construction.  [MISSING] The identified clusters are for the default fraction-live target, which is 30\%.  MISSING: STL results, typically just below the 0.5--0.9 \CFA segment.  All runs keep an average of 836 strings live, and the median string lifetime is ?? allocations.}
520    \label{fig:string-graph-allocn}
521\end{figure}
522
523Figure \ref{fig:string-graph-allocn} shows the results of this experiment.  At all string sizes, varying the liveness threshold gives offers speed-for-space tradeoffs relative to STL.  At the default liveness threshold, all measured string sizes see a ??\%--??\% speedup for a ??\%--??\% increase in memory footprint.
524
525
526
527\subsubsection{Test: Normalize}
528
529This test is more applied than the earlier ones.  It combines the effects of several operations.  It also demonstrates a case of the \CFA API allowing user code to perform well, while being written without overt memory management, while achieving similar performance in STL requires adding memory-management complexity.
530
531To motivate: edits being rare
532
533The program is doing a specialized find-replace operation on a large body of text.  In the program under test, the replacement is just to erase a magic character.  But in the larger software problem represented, the rewrite logic belongs to a module that was originally intended to operate on simple, modest-length strings.  The challenge is to apply this packaged function across chunks taken from the large body.  Using the \CFA string library, the most natural way to write the helper module's function also works well in the adapted context.  Using the STL string, the most natural ways to write the helper module's function, given its requirements in isolation, slow down when it is driven in the adapted context.
534
535\begin{lstlisting}
536void processItem( string & item ) {
537    // find issues in item and fix them
538}
539\end{lstlisting}
540
541\section{String I/O}
Note: See TracBrowser for help on using the repository browser.