source: doc/theses/mike_brooks_MMath/string.tex @ 8f250e0

Last change on this file since 8f250e0 was 8f250e0, checked in by Peter A. Buhr <pabuhr@…>, 45 hours ago

continue proofreading string chapter

  • Property mode set to 100644
File size: 46.4 KB
Line 
1\chapter{String}
2
3\vspace*{-20pt}
4This chapter presents my work on designing and building a modern string type in \CFA.
5The discussion starts with examples of interesting string problems, followed by examples of how these issues are resolved in my design.
6
7
8\section{String Operations}
9
10To prepare for the following discussion, comparisons among C, \CC, Java and \CFA strings are presented, beginning in \VRef[Figure]{f:StrApiCompare}.
11It provides a classic ``cheat sheet'' presentation, summarizing the names of the most-common closely-equivalent operations.
12The over-arching commonality is that operations work on groups of characters for assigning, copying, scanning, and updating.
13
14\begin{figure}[h]
15\begin{cquote}
16\begin{tabular}{@{}l|l|l|l@{}}
17C @char [ ]@                    &  \CC @string@                 & Java @String@     & \CFA @string@     \\
18\hline
19@strcpy@, @strncpy@             & @=@                                   & @=@               & @=@       \\
20@strcat@, @strncat@             & @+@                                   & @+@               & @+@       \\
21@strcmp@, @strncmp@             & @==@, @!=@, @<@, @<=@, @>@, @>=@
22                                                & @equals@, @compareTo@
23                                                                                                                    & @==@, @!=@, @<@, @<=@, @>@, @>=@ \\
24@strlen@                                & @length@, @size@              & @length@                      & @size@        \\
25@[ ]@                                   & @[ ]@                                 & @charAt@          & @[ ]@     \\
26@strncpy@                               & @substr@                              & @substring@       & @( )@     \\
27@strncpy@                               & @replace@                             & @replace@         & @=@ \emph{(on a substring)}\\
28@strstr@                                & @find@                                & @indexOf@         & @find@ \\
29@strcspn@                               & @find_first_of@               & @matches@         & @include@ \\
30@strspn@                                & @find_first_not_of@   & @matches@         & @exclude@ \\
31n/a                                             & @c_str@, @data@               & n/a               & @strcpy@, @strncpy@ \\
32\end{tabular}
33\end{cquote}
34\caption{Comparison of languages' strings, API/``cheat-sheet'' perspective.}
35\label{f:StrApiCompare}
36\end{figure}
37
38As mentioned in \VRef{s:String}, a C string differs from other string types as it uses null termination rather than a length, which leads to explicit storage management;
39hence, most of its group operations are error prone and expensive.
40Most high-level string libraries use a separate length field and specialized storage management to implement group operations.
41Interestingly, \CC strings retain null termination in case it is needed to interface with C library functions.
42\begin{cfa}
43int open( @const char * pathname@, int flags );
44string fname{ "test.cc" );
45open( fname.@c_str()@, O_RDONLY );
46\end{cfa}
47Here, the \CC @c_str@ function 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{
48C functions like \lstinline{strdup} do return allocated storage that must be freed by the caller.}
49% Instead, each \CC string is null terminated just in case it might be needed for this purpose.
50Providing this backwards compatibility with C has a ubiquitous performance and storage cost.
51
52While \VRef[Figure]{f:StrApiCompare} emphasizes cross-language similarities, it elides many specific operational differences.
53For example, the @replace@ function selects a substring in the target and substitutes it with the source string, which can be smaller or larger than the substring.
54\CC performs the modification on the mutable receiver object
55\begin{cfa}
56string s1 = "abcde";
57s1.replace( 2, 3, "xy" );  $\C[2.25in]{// replace by position (zero origin) and length, mutable}\CRT$
58cout << s1 << endl;
59$\texttt{\small abxy}$
60\end{cfa}
61while Java allocates and returns a new string with the result, leaving the receiver unmodified.
62\label{p:JavaReplace}
63\begin{java}
64String s = "abcde";
65String r = s.replace( "cde", "xy" );  $\C[2.25in]{// replace by text, immutable}$
66System.out.println( s + ' ' + r );
67$\texttt{\small abcde abxy}$
68\end{java}
69% Generally, Java's @String@ type is immutable.
70Java provides a @StringBuffer@ near-analog that is mutable.
71\begin{java}
72StringBuffer sb = new StringBuffer( "abcde" );
73sb.replace( 2, 5, "xy" );  $\C[2.25in]{// replace by position, mutable}\CRT$
74System.out.println( sb );
75$\texttt{\small abxy}$
76\end{java}
77However, there are significant differences;
78\eg, @StringBuffer@'s @substring@ function returns a @String@ copy that is immutable.
79Finally, the operations between these type are asymmetric, \eg @String@ has @replace@ by text but not replace by position and vice versa for @StringBuffer@.
80
81More significant operational differences relate to storage management, often appearing through assignment (@target = source@), and are summarized in \VRef[Figure]{f:StrSemanticCompare}.
82% It calls out the consequences of each language taking a different approach on ``internal'' storage management.
83The following discussion justifies the figure's yes/no entries per language.
84
85\begin{figure}
86\setlength{\extrarowheight}{2pt}
87\begin{tabularx}{\textwidth}{@{}p{0.6in}XXcccc@{}}
88                                        &                       &                       & \multicolumn{4}{@{}c@{}}{\underline{Supports Helpful?}} \\
89                                        & Required      & Helpful       & C                     & \CC           & Java          & \CFA \\
90\hline
91Type abst'n
92                                        & Low-level: The string type is a varying amount of text communicated via a parameter or return.
93                                                                & High-level: The string-typed relieves the user of managing memory for the text.
94                                                                                        & no    & yes   & yes   & yes \\
95\hline
96State
97                                        & \multirow{2}{2in}
98                                        {Fast Initialize: The target receives the characters of the source without copying the characters, resulting in an Alias or Snapshot.}
99                                                                & Alias: The target name is within the source text; changes made in either variable are visible in both.
100                                                                                        & yes   & yes   & no    & yes \\
101\cline{3-7}
102                                        &
103                                                                & Snapshot: The target is an alias within the source until the target changes (copy on write).
104                                                                                        & no    & no    & yes   & yes \\
105\hline
106Symmetry
107                                        & Laxed: The target's type is anything string-like; it may have a different status concerning ownership.
108                                                                & Strict: The target's type is the same as the source; both strings are equivalent peers concerning ownership.
109                                                                                        & --            & no    & yes   & yes \\
110\hline
111Referent
112                                        & Variable-Constrained: The target can accept the entire text of the source.
113                                                                & Fragment: The target can accept an arbitrary substring of the source.
114                                                                                        & no    & no    & yes   & yes
115\end{tabularx}
116
117\noindent
118Notes
119\begin{itemize}[parsep=0pt]
120\item
121        All languages support Required in all criteria.
122\item
123        A language gets ``Supports Helpful'' in one criterion if it can do so without sacrificing the Required achievement on all other criteria.
124\item
125        The C ``string'' is actually @char []@, under the conventions that @<string.h>@ requires. Hence, there is no actual string type in C, so symmetry does not apply.
126\item
127        The Java @String@ class is analyzed; its @StringBuffer@ class behaves similarly to @C++@.
128\end{itemize}
129\caption{Comparison of languages' strings, storage management perspective.}
130\label{f:StrSemanticCompare}
131\end{figure}
132
133In C, the declaration
134\begin{cfa}
135char s[$\,$] = "abcde";
136\end{cfa}
137creates a second-class fixed-sized string-variable, as it can only be used in its lexical context;
138it cannot be passed by value to string operations or user functions as C array's cannot be copied because there is no string-length information passed to the function.
139Therefore, only pointers to strings are first-class, and discussed further.
140\begin{cfa}
141(const) char * s = "abcde";  $\C[2.25in]{// alias state, n/a symmetry, variable-constrained referent}$
142char * s1 = s;  $\C{// alias state, n/a symmetry, variable-constrained referent}$
143char * s2 = s;  $\C{// alias state, n/a symmetry, variable-constrained referent}$
144char * s3 = &s[1];  $\C{// alias state, n/a symmetry, variable-constrained referent}$
145char * s4 = &s3[1];  $\C{// alias state, n/a symmetry, variable-constrained referent}\CRT$
146printf( "%s %s %s %s %s\n", s, s1, s2, s3, s4 );
147$\texttt{\small abcde abcde abcde bcde cde}$
148\end{cfa}
149Note, all of these aliased strings rely on the single null termination character at the end of @s@.
150The issue of symmetry does not apply to C strings because the value and pointer strings are essentially different types, and so this feature is scored as not applicable for C.
151With the type not managing the text storage, there is no ownership question, \ie operations on @s1@ or @s2@ never leads to their memory becoming reusable.
152While @s3@ is a valid C-string that contains a proper substring of @s1@, the @s3@ technique does not constitute having a fragment referent because null termination implies the substring cannot be chosen arbitrarily; the technique works only for suffixes.
153
154In \CC, @string@ offers a high-level abstraction.
155\begin{cfa}
156string s = "abcde";
157string & s1 = s;  $\C[2.25in]{// alias state, lax symmetry, variable-constrained referent}$
158string s2 = s;  $\C{// copy (strict symmetry, variable-constrained referent)}$
159string s3 = s.substr( 1, 2 );  $\C{// copy (strict symmetry, fragment referent)}$
160string s4 = s3.substr( 1, 1 );  $\C{// copy (strict symmetry, fragment referent)}$
161cout << s << ' ' << s1 << ' ' << s2 << ' ' << s3 << ' ' << s4 << endl;
162$\texttt{\small abcde abcde abcde bc c}$
163string & s5 = s.substr(2,4);  $\C{// error: cannot point to temporary}\CRT$
164\end{cfa}
165The lax symmetry reflects how the validity of @s1@ depends on the content and lifetime of @s@.
166It is common practice in \CC to use the @s1@-style pass by reference, with the understanding that the callee only uses the referenced string for the duration of the call, \ie no side-effect using the parameter.
167So, when the called function is a constructor, it is typical to use an @s2@-style copy-initialization to string-object-typed member.
168Exceptions to this pattern are possible, but require the programmer to assure safety where the type system does not.
169The @s3@ initialization is constrained to copy the substring because @c_str@ always provides a null-terminated character, which may be different from the source string.
170@s3@ assignment could be fast by reference counting the text area and using copy-on-write, but would require an implementation upgrade.
171
172In Java, @String@ also offers a high-level abstraction:
173\begin{java}
174String s = "abcde";
175String s1 = s;  $\C[2.25in]{// snapshot state, strict symmetry, variable-constrained referent}$
176String s2 = s.substring( 1, 3 );  $\C{// snapshot state (possible), strict symmetry, fragment referent}$
177String s3 = s2.substring( 1, 2 );  $\C{// snapshot state (possible), strict symmetry, fragment referent}\CRT$
178System.out.println( s + ' ' + s1 + ' ' + s2 + ' ' + s3 );
179System.out.println( (s == s1) + " " + (s == s2) + " " + (s2 == s3) );
180$\texttt{\small abcde abcde bc c}$
181$\texttt{\small true false false}$
182\end{java}
183Note, @substring@ takes a start and end position, rather than a start position and length.
184Here, facts about Java's implicit pointers and pointer equality can over complicate the picture, and so are ignored.
185Furthermore, Java's string immutability means string variables behave as simple values.
186The result in @s1@ is the pointer in @s@, and their pointer equality confirm no time is spent copying characters.
187With @s2@, the case for fast-copy is more subtle.
188Certainly, its value is not pointer-equal to @s@, implying at least a further allocation.
189\PAB{TODO: finish the fast-copy case.}
190Java does not meet the aliasing requirement because immutability make it impossible to modify.
191Java's @StringBuffer@ provides aliasing (see @replace@ example on \VPageref{p:JavaReplace}), though without supporting symmetric treatment of a fragment referent, \eg @substring@ of a @StringBuffer@ is a @String@;
192as a result, @StringBuffer@ scores as \CC.
193The 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 @s3@, though without the consequence of complicating memory management.
194\PAB{What complex storage management is going on here?}
195
196Finally, in \CFA, @string@ also offers a high-level abstraction:
197\begin{cfa}
198string s = "abcde";
199string & s1 = s; $\C[2.25in]{// alias state, strict symmetry, variable-constrained referent}$
200string s2 = s; $\C{// snapshot state, strict symmetry, variable-constrained referent}$
201string s3 = s`share; $\C{// alias state, strict symmetry, variable-constrained referent}\CRT$
202string s4 = s( 1, 2 );
203string s5 = s4( 1, 1 );
204sout | s | s1 | s2 | s3 | s4 | s5;
205$\texttt{\small abcde abcde abcde abcde bc c}$
206\end{cfa}
207% all helpful criteria of \VRef[Figure]{f:StrSemanticCompare} are satisfied.
208The \CFA string manages storage, handles all assignments, including those of fragment referents with fast initialization, provides the choice between snapshot and alias semantics, and does so symmetrically with one type (which assures text validity according to the lifecycles of the string variables).
209The intended metaphor for \CFA stings is similar to a GUI text-editor or web browser.
210Select a consecutive block of text using the mouse generates an aliased substring in the file/dialog-box.
211Typing into the selected area is like assigning to an aliased substring, where the highlighted text is replaced with more or less text;
212depending on the text entered, the file/dialog-box content grows or shrinks.
213\PAB{Need to discuss the example, as for the other languages.}
214
215The remainder of this chapter explains how the \CFA string achieves this usage style.
216
217
218\section{Storage Management}
219
220This section discusses issues related to storage management of strings.
221Specifically, it is common for strings to logically overlap partially or completely.
222\begin{cfa}
223string s1 = "abcdef";
224string s2 = s1; $\C{// complete overlap, s2 == "abcdef"}$
225string s3 = s1.substr( 0, 3 ); $\C{// partial overlap, s3 == "abc"}$
226\end{cfa}
227This raises the question of how strings behave when an overlapping component is changed,
228\begin{cfa}
229s3[1] = 'w'; $\C{// what happens to s1 and s2?}$
230\end{cfa}
231which is restricted by a string's mutable or immutable property.
232For example, Java's immutable strings require copy-on-write when any overlapping string changes.
233Note, the notion of underlying string mutability is not specified by @const@; \eg in \CC:
234\begin{cfa}
235const string s1 = "abc";
236\end{cfa}
237the @const@ applies to the @s1@ pointer to @"abc"@, and @"abc"@ is an immutable constant that is \emph{copied} into the string's storage.
238Hence, @s1@ is not pointing at an immutable constant, meaning its underlying string can be mutable, unless some other designation is specified, such as Java's global immutable rule.
239
240
241\subsection{Logical overlap}
242
243\CFA provides a dynamic mechanism to indicate mutable or immutable using the attribute @`share@.
244This aliasing relationship is a sticky-property established at initialization.
245For example, here strings @s1@ and @s1a@ are in an aliasing relationship, while @s2@ is in a copy relationship.
246\input{sharing1.tex}
247Here, the aliasing (@`share@) causes partial changes (subscripting) to flow in both directions.
248\input{sharing2.tex}
249Similarly for complete changes.
250\input{sharing3.tex}
251
252Because string assignment copies the value, RHS aliasing is irrelevant.
253Hence, aliasing of the LHS is unaffected.
254\input{sharing4.tex}
255
256Now, consider string @s1_mid@ being an alias in the middle of @s1@, along with @s2@, made by a simple copy from the middle of @s1@.
257\input{sharing5.tex}
258Again, @`share@ passes changes in both directions; copy does not.
259As a result, the index values for the position of @b@ are 1 in the longer string @"abcd"@ and 0 in the shorter aliased string @"bc"@.
260This alternate positioning also applies to subscripting.
261\input{sharing6.tex}
262
263Finally, assignment flows through the aliasing relationship without affecting its structure.
264\input{sharing7.tex}
265In the @"ff"@ assignment, the result is straightforward to accept because the flow direction is from contained (small) to containing (large).
266The following rules explain aliasing substrings that flow in the opposite direction, large to small.
267
268Growth and shrinkage are natural extensions, as for the text-editor example mentioned earlier, where an empty substring is as real real as an empty string.
269\input{sharing8.tex}
270
271Multiple portions of a string can be aliased.
272% When there are several aliasing substrings at once, the text editor analogy becomes an online multi-user editor.
273%I 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.
274\input{sharing9.tex}
275When @s1_bgn@'s size increases by 3, @s1_mid@'s starting location moves from 1 to 4 and @s1_end@'s from 3 to 6,
276
277When changes happens on an aliasing substring that overlap.
278\input{sharing10.tex}
279Strings @s1_crs@ and @s1_mid@ overlap at character 4, @j@ because the substrings are 3,2 and 4,2.
280When @s1_crs@'s size increases by 1, @s1_mid@'s starting location moves from 4 to 5, but the overlapping character remains, changing to @'+'@.
281
282\PAB{TODO: finish typesetting the demo}
283
284%\input{sharing-demo.tex}
285
286
287\subsection{RAII limitations}
288
289Earlier work on \CFA~\cite[ch.~2]{Schluntz17} implemented object constructors and destructors for all types (basic and user defined).
290A 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.
291This 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.
292
293The purposes of these invariants goes beyond ensuring authentic values inside an object.
294Invariants can also track occurrences of managed objects in other data structures.
295For example, reference counting is a typical application of an invariant outside of the data values.
296With a reference-counting smart-pointer, the constructor and destructor \emph{of a pointer type} tracks the life cycle of the object it points to.
297Both \CC and \CFA RAII systems are powerful enough to achieve reference counting.
298
299In 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.
300\begin{cfa}
301struct S { int * ip; };
302void ?{}( S & @this@ ) { this.ip = new(); } $\C[3in]{// default constructor}$
303void ?{}( S & @this@, int i ) { ?{}(this); *this.ip = i; } $\C{// initializing constructor}$
304void ?{}( S & @this@, S s ) { this = s; } $\C{// copy constructor}$
305void ^?{}( S & @this@ ) { delete( this.ip ); } $\C{// destructor}\CRT$
306\end{cfa}
307The lifecycle implementation can then add this object to a collection at creation and remove it at destruction.
308A module providing lifecycle semantics can traverse the collection at relevant times to keep the objects ``good.''
309Hence, 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.
310
311In many cases, the relationship between memory location and lifecycle is straightforward.
312For 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.
313What 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.
314To provide this knowledge, languages differentiate between initialization and assignment to a left-hand side.
315\begin{cfa}
316Obj obj2 = obj1;  $\C[1.5in]{// initialization, obj2 is initialized}$
317obj2 = obj1;      $\C{// assignment, obj2 must be initialized for management to work}\CRT$
318\end{cfa}
319Initialization occurs at declaration by value, parameter by argument, return temporary by function call.
320Hence, it is necessary to have two kinds of constructors: by value or object.
321\begin{cfa}
322Obj obj1{ 1, 2, 3 }; $\C[1.5in]{// by value, management is initialized}$
323Obj obj2 = obj1;     $\C{// by obj, management is updated}\CRT$
324\end{cfa}
325When no object management is required, initialization copies the right-hand value.
326Hence, the calling convention remains uniform, where the unmanaged case uses @memcpy@ as the initialization constructor and managed uses the specified initialization constructor.
327
328The \CFA RAII system supports lifecycle functions, except for returning a value from a function to a temporary.
329For example, in \CC:
330\begin{c++}
331struct S {...};
332S identity( S s ) { return s; }
333S s;
334s = identity( s ); // S temp = identity( s ); s = temp;
335\end{c++}
336the generated code for the function call created a temporary with initialization from the function call, and then assigns the temporary to the object.
337This two step approach means extra storage for the temporary and two copies to get the result into the object variable.
338\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''.
339\CFA uses C semantics for function return giving direct value-assignment, which eliminates unnecessary code, but skips an essential feature needed by lifetime management.
340The following discusses the consequences of this semantics with respect to lifetime management of \CFA strings.
341
342The present string-API contribution provides lifetime management with initialization semantics on function return.
343The workaround to achieve the full lifetime semantics does have a runtime performance penalty.
344An alternative API sacrifices return initialization semantics to recover full runtime performance.
345These APIs are layered, with the slower, friendlier High Level API (HL) wrapping the faster, more primitive Low Level API (LL).
346Both API present the same features, up to lifecycle management, with return initialization being disabled in LL and implemented with the workaround in HL.
347The intention is for most future code to target HL.
348When \CFA becomes a full compiler, it can provide return initialization with RVO optimizations.
349Then, programs written with the HL API will simply run faster.
350In the meantime, performance-critical sections of applications use LL.
351Subsequent performance experiments \see{\VRef{s:PerformanceAssessment}} with other string libraries has \CFA strings using the LL API.
352These measurement gives a fair estimate of the goal state for \CFA.
353
354
355\subsection{Memory management}
356
357A centrepiece of the string module is its memory manager.
358The management scheme defines a shared buffer for string text.
359Allocation in this buffer is via a bump-pointer;
360the buffer is compacted and/or relocated with growth when it fills.
361A string is a smart pointer into this buffer.
362
363This cycle of frequent cheap allocations, interspersed with infrequent expensive compactions, has obvious similarities to a general-purpose memory manager based on garbage collection (GC).
364A few differences are noteworthy.
365First, in a general purpose manager, the allocated objects may contain pointers to other objects, making the transitive reachability of these objects a crucial property.
366Here, the allocations are text, so one allocation never keeps another alive.
367Second, in a general purpose manager, the handle that keeps an allocation alive is just a lean pointer.
368For strings, a fatter representation is acceptable because there are fewer string head pointers versus chained pointers within nodes as for linked containers.
369
370\begin{figure}
371\includegraphics{memmgr-basic}
372\caption{String memory-management data structures}
373\label{f:memmgr-basic}
374\end{figure}
375
376\VRef[Figure]{f:memmgr-basic} shows the representation.
377The heap header and text buffer define a sharing context.
378Normally, one global sharing context is appropriate for an entire program;
379concurrent exceptions are discussed in \VRef{s:AvoidingImplicitSharing}.
380A string is a handle into the buffer and linked into a list.
381The list is doubly linked for $O(1)$ insertion and removal at any location.
382Strings are orders in the list by string-text address, where there is no overlapping, and approximately, where there is.
383The header maintains a next-allocation pointer, @alloc@, pointing to the last live allocation in the buffer.
384No external references point into the buffer and the management procedure relocates the text allocations as needed.
385A string handle references a containing string, while its string is contiguous and not null terminated.
386The length sets an upper limit on the string size, but is large (4 or 8 bytes).
387String handles can be allocated in the stack or heap, and represent the string variables in a program.
388Normal C life-time rules apply to guarantee correctness of the string linked-list.
389The text buffer is large enough with good management so that often only one dynamic allocation is necessary during program execution.
390% During this period, strings can vary in size dynamically.
391
392When the text buffer fills, \ie the next new string allocation causes @alloc@ to point beyond the end of the buffer, the strings are compacted.
393The linked handles define all live strings in the buffer, which indirectly defines the allocated and free space in the buffer.
394Since 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.
395After 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.
396Note, the list of string handles is unaffected during a compaction;
397only the string pointers in the handles are modified to new buffer locations.
398
399Object lifecycle events are the \emph{subscription-management} triggers in such a service.
400There are two fundamental string-creation routines: importing external text like a C-string or reading a string, and initialization from an existing \CFA string.
401When importing, storage comes from the end of the buffer, into which the text is copied.
402The new string handle is inserted at the end of the handle list because the new text is at the end of the buffer.
403When initializing from text already in the text buffer, the new handle is a second reference into the original run of characters.
404In this case, the new handle's linked-list position is after the original handle.
405Both string initialization styles preserve the string module's internal invariant that the linked-list order matches the buffer order.
406For string destruction, handles are removed from the list.
407
408Certain string operations can results in a subset (substring) of another string.
409The resulting handle is then placed in the correct sorted position in the list, possible with a short linear search to locate the position.
410For string operations resulting in a new string, that string is allocated at the end of the buffer.
411For shared-edit strings, handles that originally referenced containing locations need to see the new value at the new buffer location.
412These strings are moved to appropriate locations at the end of the list \see{[xref: TBD]}.
413For nonshared-edit strings, a containing string can be moved and the nonshared strings can remain in the same position.
414String assignment words similarly to string initialization, maintain the invariant of linked-list order matching buffer order.
415
416At the level of the memory manager, these modifications can always be explained as assignments and appendment;
417for example, an append is an assignment into the empty substring at the end of the buffer.
418Favourable conditions allow for in-place editing: where there is room for the resulting value in the original buffer location, and where all handles referring to the original buffer location see the new value.
419However, the general case requires a new buffer allocation: where the new value does not fit in the old place, or if other handles are still using the old value.
420
421
422\subsection{Sharing implementation}
423
424The \CFA string module has two mechanisms to handle the case when string handles share a string of text. 
425
426The first type of sharing is the user requests both string handles be views of the same logical, modifiable string.
427This state is typically produced by the substring operation.
428\begin{cfa}
429string s = "abcde";
430string s1 = s( 1, 2 )@`share@; $\C[2.25in]{// explicit sharing}$
431s[1] = 'x'; $\C{// change s and s1}\CRT$
432sout | s | s1;
433$\texttt{\small axcde xc}$
434\end{cfa}
435In 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.
436In this state, a subsequent modification made by either is visible in both.
437
438The 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.
439This state is typically produced by constructing a new string, using an original string as its initialization source.
440\begin{cfa}
441string s = "abcde";
442string s1 = s( 1, 2 )@@; $\C[2.25in]{// no sharing}$
443s[1] = 'x'; $\C{// copy-on-write s1}\CRT$
444sout | s | s1;
445$\texttt{\small axcde bc}$
446\end{cfa}
447In this state, a subsequent modification done on one handle triggers the deferred copy action, leaving the handles referencing different text within the buffer, holding distinct values.
448
449A further abstraction, in the string module's implementation, helps distinguish the two senses of sharing.
450A share-edit set (SES) is an equivalence class over string handles, being the reflexive, symmetric and transitive closure of the relationship of one string being constructed from another, with the ``share'' opt-in given.
451The SES is represented by a second linked list among the handles.
452A string that shares edits with no other is in a SES by itself.
453Inside a SES, a logical modification of one substring portion may change the logical value in another, depending on whether the two actually overlap.
454Conversely, no logical value change can flow outside of a SES.
455Even if a modification on one string handle does not reveal itself \emph{logically} to anther handle in the same SES (because they do not overlap), if the modification is length-changing, completing the modification requires visiting the second handle to adjust its location in the sliding text.
456
457
458\subsection{Avoiding implicit sharing}
459\label{s:AvoidingImplicitSharing}
460
461There are tradeoffs associated with the copy-on-write mechanism.
462Several qualitative matters are detailed in the \VRef{s:PerformanceAssessment} and the qualitative issue of multi-threaded support is introduced here.
463The \CFA sting library provides a switch to disable the sharing mechanism for situations where it is inappropriate.
464
465Because 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.
466A 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.
467A positive consequence is that a single-threaded program, or a program with several independent threads, can use the sharing context without locking overhead.
468
469When the string library is running with sharing disabled, it runs without implicit thread-safety challenges, which is the same as the \CC STL, and with performance goals similar to the STL.
470Running with sharing disabled can be thought of as a STL-emulation mode.
471Hence, concurrent users of string objects must still bring their own mutual exclusion, but the string library does not add any cross thread uses that are not apparent in a user's code.
472
473\PAB{I could not get this to do anything.}
474The \CFA string library does provides a @string_sharectx@ type to control an ambient sharing context for the current thread.
475It allows two adjustments: to opt out of sharing entirely or to begin sharing within a private context.
476Either way, the chosen mode applies only 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.
477The intended use is with stack-managed lifetimes, in which the established context lasts until the current function returns, and affects all functions called that do not create their own contexts.
478\lstinputlisting[language=CFA, firstline=20, lastline=34]{sharectx.run.cfa}
479In this example, the single-letter functions are called in alphabetic order.
480The functions @a@ and @d@ share string character ranges within themselves, but not with each other.
481The functions @b@, @c@ and @e@ never share anything.
482
483[ TODO: true up with ``is thread local'' (implement that and expand this discussion to give a concurrent example, or adjust this wording) ]
484
485
486\subsection{Future work}
487
488To discuss: Unicode
489
490To discuss: Small-string optimization
491
492
493\section{Performance assessment}
494\label{s:PerformanceAssessment}
495
496I assessed the \CFA string library's speed and memory usage against strings in \CC STL.
497The results are presented in even equivalent cases, due to either micro-optimizations foregone, or fundamental costs of the added functionality.
498They also show the benefits and tradeoffs, as >100\% effects, of switching to \CFA, with the tradeoff points quantified.
499The final test shows the overall win of the \CFA text-sharing mechanism.
500It exercises several operations together, showing \CFA enabling clean user code to achieve performance that STL requires less-clean user code to achieve.
501
502To discuss: general goal of ...
503while STL makes you think about memory management, all the time, and if you do your performance can be great ...
504\CFA sacrifices this advantage modestly in exchange for big wins when you're not thinking about memory management.
505[Does this position cover all of it?]
506
507To discuss: revisit HL v LL APIs
508
509To discuss: revisit no-sharing as STL emulation modes
510
511
512\subsection{Methodology}
513
514These tests use randomly generated text fragments of varying lengths.
515A collection of such fragments is a \emph{corpus}.
516The mean length of a fragment from a corpus is a typical explanatory variable.
517Such a length is used in one of three modes:
518\begin{description}
519        \item [Fixed-size] means all string fragments are of the stated size.
520        \item [Varying from 1] means string lengths are drawn from a geometric distribution with the stated mean, and all lengths occur.
521        \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 is above 16.
522\end{description}
523The geometric distribution implies that lengths much longer than the mean occur frequently.
524The 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.
525When 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.
526In all experiments that use a corpus, its text is generated and loaded into the SUT before the timed phase begins.
527
528To discuss: vocabulary for reused case variables
529
530To discuss: common approach to iteration and quoted rates
531
532To discuss: hardware and such
533
534To discuss: memory allocator
535
536
537\subsection{Test: Append}
538
539This test measures the speed of appending fragments of text onto a growing string.
540Its subcases include both \CFA being similar to STL and their designs offering a tradeoff.
541
542One experimental variable is logically equivalent operations such as @a = a + b@ \vs @a += b@.
543For numeric types, the generated code is equivalence, giving identical performance.
544However, for string types there can be a significant difference in performance, especially if this code appears in a loop iterating a large number of times.
545This difference might not be intuitive to beginners.
546
547Another experimental variable is whether the user's logical allocation is fresh \vs reused.
548Here, \emph{reusing a logical allocation}, means that the program variable, into which the user is concatenating, previously held a long string:\\
549\begin{cquote}
550\setlength{\tabcolsep}{20pt}
551\begin{tabular}{@{}ll@{}}
552\begin{cfa}
553
554for ( ... ) {
555        @string x;@   // fresh
556        for ( ... )
557                x += ...
558}
559\end{cfa}
560&
561\begin{cfa}
562string x;
563for ( ... ) {
564        @x = "";@  // reused
565        for ( ... )
566                x += ...
567}
568\end{cfa}
569\end{tabular}
570\end{cquote}
571These 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.''
572In general, a user should not have to care about this difference, yet the STL performs differently in these cases.
573Furthermore, if a routine takes a string by reference, if cannot use the fresh approach.
574Concretely, 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.
575For 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.
576The fresh \vs reuse distinction is only relevant in the \emph{append} tests.
577
578The \emph{append} tests use the varying-from-1 corpus construction, \ie they do not assume away the STL's advantage for small-string optimization.
579\PAB{To discuss: any other case variables introduced in the performance intro}
580Figure \ref{fig:string-graph-peq-cppemu} shows this behaviour, by the STL and by \CFA in STL emulation mode.
581\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.
582This penalty characterizes the amount of implementation fine tuning done with STL and not done with \CFA in present state.
583The 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.
584
585\PAB{Most of your graphs are unreadable. gnuplot is a good tool for generating high quality graphs.}
586
587\begin{figure}
588        \includegraphics[width=\textwidth]{string-graph-peq-cppemu.png}
589        \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.}
590        \label{fig:string-graph-peq-cppemu}
591\end{figure}
592
593\begin{figure}
594        \includegraphics[width=\textwidth]{string-graph-peq-sharing.png}
595        \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.}
596        \label{fig:string-graph-peq-sharing}
597\end{figure}
598
599In sharing mode, \CFA makes the fresh/reuse difference disappear, as shown in Figure \ref{fig:string-graph-peq-sharing}.
600At 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.
601
602\begin{figure}
603        \includegraphics[width=\textwidth]{string-graph-pta-sharing.png}
604        \caption{Average time per iteration with one \lstinline{x = x + y} invocation (new, purple bands), comparing \CFA (having implicit sharing activated) with STL.
605For context, the results from Figure \ref{fig:string-graph-peq-sharing} are repeated as the bottom bands.
606While 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.}
607        \label{fig:string-graph-pta-sharing}
608\end{figure}
609
610When 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.
611Moreover, the STL's gap increases with string size, while \CFA's converges.
612
613\subsubsection{Test: Pass argument}
614
615To have introduced:  STL string library forces users to think about memory management when communicating values across a function call
616
617STL charges a prohibitive penalty for passing a string by value.
618With implicit sharing active, \CFA treats this operation as normal and supported.
619This test illustrates a main advantage of the \CFA sharing algorithm.
620It also has a case in which STL's small-string optimization provides a successful mitigation.
621
622\begin{figure}
623        \includegraphics[width=\textwidth]{string-graph-pbv.png}
624        \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.
625(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.
626(b) With \emph{Fixed-size} corpus construction, in which this benefit applies exactly to strings with length below 16.
627[TODO: show version (b)]}
628        \label{fig:string-graph-pbv}
629\end{figure}
630
631Figure \ref{fig:string-graph-pbv} shows the costs for calling a function that receives a string argument by value.
632STL's performance worsens as string length increases, while \CFA has the same performance at all sizes.
633
634The \CFA cost to pass is nontrivial.
635The contributor is adding and removing the callee's string handle from the global list.
636This 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.
637At 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.
638
639
640\subsubsection{Test: Allocate}
641
642This test directly compares the allocation schemes of the \CFA string with sharing, compared with the STL string.
643It treats the \CFA scheme as a form of garbage collection, and the STL scheme as an application of malloc-free.
644The test shows that \CFA enables faster speed at a cost in memory usage.
645
646A 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.
647(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.
648A 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.
649
650A garbage collector keeps allocations around for longer than the using program can reach them.
651By contrast, a program using malloc-free (correctly) releases allocations exactly when they are no longer reachable.
652Therefore, the same harness will use more memory while running under garbage collection.
653A garbage collector can minimize the memory overhead by searching for these dead allocations aggressively, that is, by collecting more often.
654Tuned in this way, it spends a lot of time collecting, easily so much as to overwhelm its speed advantage from bump-pointer allocation.
655If 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.
656
657[TODO: find citations for the above knowledge]
658
659The speed for memory tradeoff is, therefore, standard for comparisons like \CFA--STL string allocations.
660The test verifies that it is so and quantifies the returns available.
661
662These tests manipulate a tuning knob that controls how much extra space to use.
663Specific values of this knob are not user-visible and are not presented in the results here.
664Instead, its two effects (amount of space used and time per operation) are shown.
665The independent variable is the liveness target, which is the fraction of the text buffer that is in use at the end of a collection.
666The allocator will expand its text buffer during a collection if the actual fraction live exceeds this target.
667
668This experiment's driver allocates strings by constructing a string handle as a local variable then looping over recursive calls.
669The time measurement is of nanoseconds per such allocating call.
670The 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.
671String lifetime (measured in number of subsequent string allocations) is ?? distributed, because each node in the call tree survives as long as its descendent calls.
672The 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.
673This sizing was chosen to keep the amounts of consumed memory within the machine's last-level cache.
674
675\begin{figure}
676  \includegraphics[width=\textwidth]{string-graph-allocn.png}
677  \caption{Space and time performance, under varying fraction-live targets, for the five string lengths shown, at (\emph{Fixed-size} corpus construction.
678[MISSING] The identified clusters are for the default fraction-live target, which is 30\%.
679MISSING: STL results, typically just below the 0.5--0.9 \CFA segment.
680All runs keep an average of 836 strings live, and the median string lifetime is ?? allocations.}
681  \label{fig:string-graph-allocn}
682\end{figure}
683
684Figure \ref{fig:string-graph-allocn} shows the results of this experiment.
685At all string sizes, varying the liveness threshold gives offers speed-for-space tradeoffs relative to STL.
686At the default liveness threshold, all measured string sizes see a ??\%--??\% speedup for a ??\%--??\% increase in memory footprint.
687
688
689\subsubsection{Test: Normalize}
690
691This test is more applied than the earlier ones.
692It combines the effects of several operations.
693It 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.
694
695To motivate: edits being rare
696
697The program is doing a specialized find-replace operation on a large body of text.
698In the program under test, the replacement is just to erase a magic character.
699But in the larger software problem represented, the rewrite logic belongs to a module that was originally intended to operate on simple, modest-length strings.
700The challenge is to apply this packaged function across chunks taken from the large body.
701Using the \CFA string library, the most natural way to write the helper module's function also works well in the adapted context.
702Using 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.
703
704\begin{lstlisting}
705void processItem( string & item ) {
706         // find issues in item and fix them
707}
708\end{lstlisting}
709
710\section{String I/O}
Note: See TracBrowser for help on using the repository browser.