1 | \chapter{String}
|
---|
2 |
|
---|
3 | \vspace*{-20pt}
|
---|
4 | This chapter presents my work on designing and building a modern string type in \CFA.
|
---|
5 | The discussion starts with an overview of the string API, then a number of interesting string problems, followed by how these issues are resolved in this work.
|
---|
6 |
|
---|
7 |
|
---|
8 | \section{String Operations}
|
---|
9 |
|
---|
10 | % https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(string_functions)
|
---|
11 |
|
---|
12 | \VRef[Figure]{f:StrApiCompare} shows a general comparison of string APIs for C, \CC, Java and \CFA.
|
---|
13 | It provides a classic ``cheat sheet'', summarizing the names of the most-common closely-equivalent operations.
|
---|
14 | The over-arching commonality is that operations work on groups of characters for assigning, copying, scanning, and updating.
|
---|
15 |
|
---|
16 | \begin{figure}[h]
|
---|
17 | \begin{cquote}
|
---|
18 | \begin{tabular}{@{}l|l|l|l@{}}
|
---|
19 | C @char [ ]@ & \CC @string@ & Java @String@ & \CFA @string@ \\
|
---|
20 | \hline
|
---|
21 | @strcpy@, @strncpy@ & @=@ & @=@ & @=@ \\
|
---|
22 | @strcat@, @strncat@ & @+@, @+=@ & @+@, @+=@ & @+@, @+=@ \\
|
---|
23 | @strcmp@, @strncmp@ & @==@, @!=@, @<@, @<=@, @>@, @>=@
|
---|
24 | & @equals@, @compareTo@
|
---|
25 | & @==@, @!=@, @<@, @<=@, @>@, @>=@ \\
|
---|
26 | @strlen@ & @length@, @size@ & @length@ & @size@ \\
|
---|
27 | @[ ]@ & @[ ]@ & @charAt@ & @[ ]@ \\
|
---|
28 | @strncpy@ & @substr@ & @substring@ & @( )@, on RHS of @=@ \\
|
---|
29 | @strncpy@ & @replace@ & @replace@ & @( )@, on LHS of @=@ \\
|
---|
30 | @strstr@ & @find@ & @indexOf@ & @find@ \\
|
---|
31 | @strcspn@ & @find_first_of@ & @matches@ & @include@ \\
|
---|
32 | @strspn@ & @find_first_not_of@ & @matches@ & @exclude@ \\
|
---|
33 | n/a & @c_str@, @data@ & n/a & @strcpy@, @strncpy@ \\
|
---|
34 | \end{tabular}
|
---|
35 | \end{cquote}
|
---|
36 | \caption{Language comparison of string API}
|
---|
37 | \label{f:StrApiCompare}
|
---|
38 | \end{figure}
|
---|
39 |
|
---|
40 | As mentioned in \VRef{s:String}, a C string uses null termination rather than a length, which leads to explicit storage management;
|
---|
41 | hence, most of its group operations are error prone and expensive due to copying.
|
---|
42 | Most high-level string libraries use a separate length field and specialized storage management to implement group operations.
|
---|
43 | Interestingly, \CC strings retain null termination in case it is needed to interface with C library functions.
|
---|
44 | \begin{cfa}
|
---|
45 | int open( @const char * pathname@, int flags );
|
---|
46 | string fname{ "test.cc" );
|
---|
47 | open( fname.@c_str()@, O_RDONLY ); // null terminated value of string
|
---|
48 | \end{cfa}
|
---|
49 | Here, 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{
|
---|
50 | C functions like \lstinline{strdup} do return allocated storage that must be freed by the caller.}
|
---|
51 | % Instead, each \CC string is null terminated just in case it might be needed for this purpose.
|
---|
52 | Providing this backwards compatibility with C has a ubiquitous performance and storage cost.
|
---|
53 |
|
---|
54 |
|
---|
55 | \section{\CFA \lstinline{string} type}
|
---|
56 | \label{s:stringType}
|
---|
57 |
|
---|
58 | The \CFA string type is for manipulation of dynamically-sized character-strings versus C @char *@ type for manipulation of statically-sized null-terminated character-strings.
|
---|
59 | Hence, the amount of storage for a \CFA string changes dynamically at runtime to fit the string size, whereas the amount of storage for a C string is fixed at compile time.
|
---|
60 | As a result, a @string@ declaration does not specify a maximum length, where a C string must.
|
---|
61 | The maximum storage for a \CFA @string@ value is @size_t@ characters, which is $2^{32}$ or $2^{64}$ respectively.
|
---|
62 | A \CFA string manages its length separately from the string, so there is no null (@'\0'@) terminating value at the end of a string value.
|
---|
63 | Hence, a \CFA string cannot be passed to a C string manipulation function, such as @strcat@.
|
---|
64 | Like C strings, characters in a @string@ are numbered from the left starting at 0 (because subscripting is zero-origin), and in \CFA numbered from the right starting at -1.
|
---|
65 | \begin{cquote}
|
---|
66 | \rm
|
---|
67 | \begin{tabular}{@{}rrrrll@{}}
|
---|
68 | \small\tt "a & \small\tt b & \small\tt c & \small\tt d & \small\tt e" \\
|
---|
69 | 0 & 1 & 2 & 3 & 4 & left to right index \\
|
---|
70 | -5 & -4 & -3 & -2 & -1 & right to left index
|
---|
71 | \end{tabular}
|
---|
72 | \end{cquote}
|
---|
73 | The following operations manipulate an instance of type @string@, where the discussion assumes the following declarations.
|
---|
74 | \begin{cfa}
|
---|
75 | #include @<string.hfa>@
|
---|
76 | @string@ s = "abcde", name = "MIKE", digit = "0123456789";
|
---|
77 | const char cs[$\,$] = "abc";
|
---|
78 | int i;
|
---|
79 | \end{cfa}
|
---|
80 | Note, the include file @<string.hfa>@ to access type @string@.
|
---|
81 |
|
---|
82 |
|
---|
83 | \subsection{Implicit String Conversions}
|
---|
84 |
|
---|
85 | The ability to convert from internal (machine) to external (human) format is useful in situations other than I/O.
|
---|
86 | Hence, the basic types @char@, @char *@, @int@, @double@, @_Complex@, including any signness and size variations, implicitly convert to type @string@ (as in Java).
|
---|
87 | \begin{cquote}
|
---|
88 | \setlength{\tabcolsep}{15pt}
|
---|
89 | \begin{tabular}{@{}l|ll|l@{}}
|
---|
90 | \begin{cfa}
|
---|
91 | string s;
|
---|
92 | s = 'x';
|
---|
93 | s = "abc";
|
---|
94 | s = cs;
|
---|
95 | s = 45hh;
|
---|
96 | s = 45h;
|
---|
97 | \end{cfa}
|
---|
98 | &
|
---|
99 | \begin{cfa}
|
---|
100 |
|
---|
101 | "x"
|
---|
102 | "abc"
|
---|
103 | "abc"
|
---|
104 | "45"
|
---|
105 | "45"
|
---|
106 | \end{cfa}
|
---|
107 | &
|
---|
108 | \begin{cfa}
|
---|
109 | s = (ssize_t)MIN;
|
---|
110 | s = (size_t)MAX;
|
---|
111 | s = 5.5;
|
---|
112 | s = 5.5L;
|
---|
113 | s = 5.5+3.4i;
|
---|
114 | s = 5.5L+3.4Li;
|
---|
115 | \end{cfa}
|
---|
116 | &
|
---|
117 | \begin{cfa}
|
---|
118 | "-9223372036854775808"
|
---|
119 | "18446744073709551615"
|
---|
120 | "5.5"
|
---|
121 | "5.5"
|
---|
122 | "5.5+3.4i"
|
---|
123 | "5.5+3.4i"
|
---|
124 | \end{cfa}
|
---|
125 | \end{tabular}
|
---|
126 | \end{cquote}
|
---|
127 | Conversions can be explicitly specified using a compound literal.
|
---|
128 | \begin{cfa}
|
---|
129 | s = (string){ "abc" }; $\C{// converts char * to string}$
|
---|
130 | s = (string){ 5 }; $\C{// converts int to string}$
|
---|
131 | s = (string){ 5.5 }; $\C{// converts double to string}$
|
---|
132 | \end{cfa}
|
---|
133 |
|
---|
134 | Conversions from @string@ to @char *@ attempt to be safe:
|
---|
135 | either by requiring the maximum length of the @char *@ storage (@strncpy@) or allocating the @char *@ storage for the string characters (ownership), meaning the programmer must free the storage.
|
---|
136 | Note, a C string is always null terminated, implying a minimum size of 1 character.
|
---|
137 | \begin{cquote}
|
---|
138 | \setlength{\tabcolsep}{15pt}
|
---|
139 | \begin{tabular}{@{}l|l@{}}
|
---|
140 | \begin{cfa}
|
---|
141 | strncpy( cs, s, sizeof(cs) );
|
---|
142 | char * cp = s;
|
---|
143 | delete( cp );
|
---|
144 | cp = s + ' ' + s;
|
---|
145 | delete( cp );
|
---|
146 | \end{cfa}
|
---|
147 | &
|
---|
148 | \begin{cfa}
|
---|
149 | "abc\0", in place
|
---|
150 | "abcde\0", malloc
|
---|
151 | ownership
|
---|
152 | "abcde abcde\0", malloc
|
---|
153 | ownership
|
---|
154 | \end{cfa}
|
---|
155 | \end{tabular}
|
---|
156 | \end{cquote}
|
---|
157 |
|
---|
158 |
|
---|
159 | \subsection{Length}
|
---|
160 |
|
---|
161 | The @len@ operation (short for @strlen@) returns the length of a C or \CFA string.
|
---|
162 | For compatibility, @strlen@ also works with \CFA strings.
|
---|
163 | \begin{cquote}
|
---|
164 | \setlength{\tabcolsep}{15pt}
|
---|
165 | \begin{tabular}{@{}l|l@{}}
|
---|
166 | \begin{cfa}
|
---|
167 | i = len( "" );
|
---|
168 | i = len( "abc" );
|
---|
169 | i = len( cs );
|
---|
170 | i = strlen( cs );
|
---|
171 | i = len( name );
|
---|
172 | i = strlen( name );
|
---|
173 | \end{cfa}
|
---|
174 | &
|
---|
175 | \begin{cfa}
|
---|
176 | 0
|
---|
177 | 3
|
---|
178 | 3
|
---|
179 | 3
|
---|
180 | 4
|
---|
181 | 4
|
---|
182 | \end{cfa}
|
---|
183 | \end{tabular}
|
---|
184 | \end{cquote}
|
---|
185 |
|
---|
186 |
|
---|
187 | \subsection{Comparison Operators}
|
---|
188 |
|
---|
189 | The binary relational, @<@, @<=@, @>@, @>=@, and equality, @==@, @!=@, operators compare \CFA string values using lexicographical ordering, where longer strings are greater than shorter strings.
|
---|
190 | In C, these operators compare the C string pointer not its value, which does not match programmer expectation.
|
---|
191 | C strings use function @strcmp@ to lexicographically compare the string value.
|
---|
192 | Java has the same issue with @==@ and @.equals@.
|
---|
193 |
|
---|
194 |
|
---|
195 | \subsection{Concatenation}
|
---|
196 |
|
---|
197 | The binary operators @+@ and @+=@ concatenate C @char@, @char *@ and \CFA strings, creating the sum of the characters.
|
---|
198 | \par\noindent
|
---|
199 | \begin{tabular}{@{}l|l@{\hspace{15pt}}l|l@{\hspace{15pt}}l|l@{}}
|
---|
200 | \begin{cfa}
|
---|
201 | s = "";
|
---|
202 | s = 'a' + 'b';
|
---|
203 | s = 'a' + "b";
|
---|
204 | s = "a" + 'b';
|
---|
205 | s = "a" + "b";
|
---|
206 | \end{cfa}
|
---|
207 | &
|
---|
208 | \begin{cfa}
|
---|
209 |
|
---|
210 | "ab"
|
---|
211 | "ab"
|
---|
212 | "ab"
|
---|
213 | "ab"
|
---|
214 | \end{cfa}
|
---|
215 | &
|
---|
216 | \begin{cfa}
|
---|
217 | s = "";
|
---|
218 | s = 'a' + 'b' + s;
|
---|
219 | s = 'a' + 'b' + s;
|
---|
220 | s = 'a' + "b" + s;
|
---|
221 | s = "a" + 'b' + s;
|
---|
222 | \end{cfa}
|
---|
223 | &
|
---|
224 | \begin{cfa}
|
---|
225 |
|
---|
226 | "ab"
|
---|
227 | "abab"
|
---|
228 | "ababab"
|
---|
229 | "abababab"
|
---|
230 | \end{cfa}
|
---|
231 | &
|
---|
232 | \begin{cfa}
|
---|
233 | s = "";
|
---|
234 | s = s + 'a' + 'b';
|
---|
235 | s = s + 'a' + "b";
|
---|
236 | s = s + "a" + 'b';
|
---|
237 | s = s + "a" + "b";
|
---|
238 | \end{cfa}
|
---|
239 | &
|
---|
240 | \begin{cfa}
|
---|
241 |
|
---|
242 | "ab"
|
---|
243 | "abab"
|
---|
244 | "ababab"
|
---|
245 | "abababab"
|
---|
246 | \end{cfa}
|
---|
247 | \end{tabular}
|
---|
248 | \par\noindent
|
---|
249 | However, including @<string.hfa>@ can result in ambiguous uses of the overloaded @+@ operator.\footnote{Combining multiple packages in any programming language can result in name clashes or ambiguities.}
|
---|
250 | While subtracting characters or pointers has a low-level use-case
|
---|
251 | \begin{cfa}
|
---|
252 | ch - '0' $\C[2in]{// find character offset}$
|
---|
253 | cs - cs2; $\C{// find pointer offset}\CRT$
|
---|
254 | \end{cfa}
|
---|
255 | addition is less obvious
|
---|
256 | \begin{cfa}
|
---|
257 | ch + 'b' $\C[2in]{// add character values}$
|
---|
258 | cs + 'a'; $\C{// move pointer cs['a']}\CRT$
|
---|
259 | \end{cfa}
|
---|
260 | There are legitimate use cases for arithmetic with @signed@/@unsigned@ characters (bytes), and these types are treated differently from @char@ in \CC and \CFA.
|
---|
261 | However, backwards compatibility makes it impossible to restrict or remove addition on type @char@.
|
---|
262 | Similarly, it is impossible to restrict or remove addition on type @char *@ because (unfortunately) it is subscripting: @cs + 'a'@ implies @cs['a']@ or @'a'[cs]@.
|
---|
263 |
|
---|
264 | The prior \CFA concatenation examples show complex mixed-mode interactions among @char@, @char *@, and @string@ (variables are the same as constants) work correctly.
|
---|
265 | The reason is that the \CFA type-system handles this kind of overloading well using the left-hand assignment-type and complex conversion costs.
|
---|
266 | Hence, the type system correctly handles all uses of addition (explicit or implicit) for @char *@.
|
---|
267 | \begin{cfa}
|
---|
268 | printf( "%s %s %s %c %c\n", "abc", cs, cs + 3, cs['a'], 'a'[cs] );
|
---|
269 | \end{cfa}
|
---|
270 | Only @char@ addition can result in ambiguities, and only when there is no left-hand information.
|
---|
271 | \begin{cfa}
|
---|
272 | ch = ch + 'b'; $\C[2in]{// LHS disambiguate, add character values}$
|
---|
273 | s = 'a' + 'b'; $\C{// LHS disambiguate, concatenate characters}$
|
---|
274 | printf( "%c\n", @'a' + 'b'@ ); $\C[2in]{// no LHS information, ambiguous}$
|
---|
275 | printf( "%c\n", @(return char)@('a' + 'b') ); $\C{// disambiguate with ascription cast}$
|
---|
276 | \end{cfa}
|
---|
277 | The ascription cast, @(return T)@, disambiguates by stating a (LHS) type to use during expression resolution (not a conversion).
|
---|
278 | Fortunately, character addition without LHS information is rare in C/\CFA programs, so repurposing the operator @+@ for @string@ types is not a problem.
|
---|
279 | Note, other programming languages that repurpose @+@ for concatenation, could have similar ambiguity issues.
|
---|
280 |
|
---|
281 | Interestingly, \CC cannot support this generality because it does not use the left-hand side of assignment in expression resolution.
|
---|
282 | While it can special case some combinations:
|
---|
283 | \begin{c++}
|
---|
284 | s = 'a' + s; $\C[2in]{// compiles in C++}$
|
---|
285 | s = "a" + s;
|
---|
286 | \end{c++}
|
---|
287 | it cannot generalize to any number of steps:
|
---|
288 | \begin{c++}
|
---|
289 | s = 'a' + 'b' + s; $\C{// does not compile in C++}\CRT$
|
---|
290 | s = "a" + "b" + s;
|
---|
291 | \end{c++}
|
---|
292 |
|
---|
293 |
|
---|
294 | \subsection{Repetition}
|
---|
295 |
|
---|
296 | The binary operators @*@ and @*=@ repeat a string $N$ times.
|
---|
297 | If $N = 0$, a zero length string, @""@, is returned.
|
---|
298 | \begin{cquote}
|
---|
299 | \setlength{\tabcolsep}{15pt}
|
---|
300 | \begin{tabular}{@{}l|l@{}}
|
---|
301 | \begin{cfa}
|
---|
302 | s = 'x' * 0;
|
---|
303 | s = 'x' * 3;
|
---|
304 | s = "abc" * 3;
|
---|
305 | s = (name + ' ') * 3;
|
---|
306 | \end{cfa}
|
---|
307 | &
|
---|
308 | \begin{cfa}
|
---|
309 | "
|
---|
310 | "xxx"
|
---|
311 | "abcabcabc"
|
---|
312 | "MIKE MIKE MIKE "
|
---|
313 | \end{cfa}
|
---|
314 | \end{tabular}
|
---|
315 | \end{cquote}
|
---|
316 | Like concatenation, there is a potential ambiguity with multiplication of characters;
|
---|
317 | multiplication for pointers does not exist in C.
|
---|
318 | \begin{cfa}
|
---|
319 | ch = ch * 3; $\C[2in]{// LHS disambiguate, multiply character values}$
|
---|
320 | s = 'a' * 3; $\C{// LHS disambiguate, concatenate characters}$
|
---|
321 | printf( "%c\n", @'a' * 3@ ); $\C[2in]{// no LHS information, ambiguous}$
|
---|
322 | printf( "%c\n", @(return char)@('a' * 3) ); $\C{// disambiguate with ascription cast}$
|
---|
323 | \end{cfa}
|
---|
324 | Fortunately, character multiplication without LHS information is even rarer than addition, so repurposing the operator @*@ for @string@ types is not a problem.
|
---|
325 |
|
---|
326 |
|
---|
327 | \subsection{Substring}
|
---|
328 | The substring operation returns a subset of a string starting at a position in the string and traversing a length or matching a pattern string.
|
---|
329 | \begin{cquote}
|
---|
330 | \setlength{\tabcolsep}{10pt}
|
---|
331 | \begin{tabular}{@{}l|ll|l@{}}
|
---|
332 | \multicolumn{2}{c}{\textbf{length}} & \multicolumn{2}{c}{\textbf{pattern}} \\
|
---|
333 | \begin{cfa}
|
---|
334 | s = name( 2, 2 );
|
---|
335 | s = name( 3, -2 );
|
---|
336 | s = name( 2, 8 );
|
---|
337 | s = name( 0, -1 );
|
---|
338 | s = name( -1, -1 );
|
---|
339 | s = name( -3 );
|
---|
340 | \end{cfa}
|
---|
341 | &
|
---|
342 | \begin{cfa}
|
---|
343 | "KE"
|
---|
344 | "IK"
|
---|
345 | "KE", clip length to 2
|
---|
346 | "", beyond string clip to null
|
---|
347 | "K"
|
---|
348 | "IKE", to end of string
|
---|
349 | \end{cfa}
|
---|
350 | &
|
---|
351 | \begin{cfa}
|
---|
352 | s = name( "IK" );
|
---|
353 | s = name( "WW" );
|
---|
354 |
|
---|
355 |
|
---|
356 |
|
---|
357 |
|
---|
358 | \end{cfa}
|
---|
359 | &
|
---|
360 | \begin{cfa}
|
---|
361 | "IK"
|
---|
362 | ""
|
---|
363 |
|
---|
364 |
|
---|
365 |
|
---|
366 |
|
---|
367 | \end{cfa}
|
---|
368 | \end{tabular}
|
---|
369 | \end{cquote}
|
---|
370 | A negative starting position is a specification from the right end of the string.
|
---|
371 | A negative length means that characters are selected in the opposite (right to left) direction from the starting position.
|
---|
372 | If the substring request extends beyond the beginning or end of the string, it is clipped (shortened) to the bounds of the string.
|
---|
373 | If the substring request is completely outside of the original string, a null string is returned.
|
---|
374 | The pattern-form either returns the pattern string is the pattern matches or a null string if the pattern does not match.
|
---|
375 | The usefulness of this mechanism is discussed next.
|
---|
376 |
|
---|
377 | The substring operation can appear on the left side of assignment, where it defines a replacement substring.
|
---|
378 | The length of the right string may be shorter, the same, or longer than the length of left string.
|
---|
379 | Hence, the left string may decrease, stay the same, or increase in length.
|
---|
380 | \begin{cquote}
|
---|
381 | \setlength{\tabcolsep}{15pt}
|
---|
382 | \begin{tabular}{@{}l|l@{}}
|
---|
383 | \begin{cfa}[escapechar={}]
|
---|
384 | digit( 3, 3 ) = "";
|
---|
385 | digit( 4, 3 ) = "xyz";
|
---|
386 | digit( 7, 0 ) = "***";
|
---|
387 | digit(-4, 3 ) = "$$$";
|
---|
388 | digit( 5 ) = "LLL";
|
---|
389 | \end{cfa}
|
---|
390 | &
|
---|
391 | \begin{cfa}[escapechar={}]
|
---|
392 | "0126789"
|
---|
393 | "0126xyz"
|
---|
394 | "0126xyz"
|
---|
395 | "012$$$z"
|
---|
396 | "012$$LLL"
|
---|
397 | \end{cfa}
|
---|
398 | \end{tabular}
|
---|
399 | \end{cquote}
|
---|
400 | Now pattern matching is useful on the left-hand side of assignment.
|
---|
401 | \begin{cquote}
|
---|
402 | \setlength{\tabcolsep}{15pt}
|
---|
403 | \begin{tabular}{@{}l|l@{}}
|
---|
404 | \begin{cfa}[escapechar={}]
|
---|
405 | digit( "$$" ) = "345";
|
---|
406 | digit( "LLL") = "6789";
|
---|
407 | \end{cfa}
|
---|
408 | &
|
---|
409 | \begin{cfa}
|
---|
410 | "012345LLL"
|
---|
411 | "0123456789"
|
---|
412 | \end{cfa}
|
---|
413 | \end{tabular}
|
---|
414 | \end{cquote}
|
---|
415 | Extending the pattern to a regular expression is a possible extension.
|
---|
416 |
|
---|
417 | The replace operation extensions substring to substitute all occurrences.
|
---|
418 | \begin{cquote}
|
---|
419 | \setlength{\tabcolsep}{15pt}
|
---|
420 | \begin{tabular}{@{}l|l@{}}
|
---|
421 | \begin{cfa}
|
---|
422 | s = replace( "PETER", "E", "XX" );
|
---|
423 | s = replace( "PETER", "ET", "XX" );
|
---|
424 | s = replace( "PETER", "W", "XX" );
|
---|
425 | \end{cfa}
|
---|
426 | &
|
---|
427 | \begin{cfa}
|
---|
428 | "PXXTXXR"
|
---|
429 | "PXXER"
|
---|
430 | "PETER"
|
---|
431 | \end{cfa}
|
---|
432 | \end{tabular}
|
---|
433 | \end{cquote}
|
---|
434 | The replacement is done left-to-right and substituted text is not examined for replacement.
|
---|
435 |
|
---|
436 |
|
---|
437 | \subsection{Searching}
|
---|
438 |
|
---|
439 | The find operation returns the position of the first occurrence of a key in a string.
|
---|
440 | If the key does not appear in the string, the length of the string is returned.
|
---|
441 | \begin{cquote}
|
---|
442 | \setlength{\tabcolsep}{15pt}
|
---|
443 | \begin{tabular}{@{}l|l@{}}
|
---|
444 | \begin{cfa}
|
---|
445 | i = find( digit, '3' );
|
---|
446 | i = find( digit, "45" );
|
---|
447 | i = find( digit, "abc" );
|
---|
448 | \end{cfa}
|
---|
449 | &
|
---|
450 | \begin{cfa}
|
---|
451 | 3
|
---|
452 | 4
|
---|
453 | 10
|
---|
454 | \end{cfa}
|
---|
455 | \end{tabular}
|
---|
456 | \end{cquote}
|
---|
457 |
|
---|
458 | A character-class operation indicates if a string is composed completely of a particular class of characters, \eg, alphabetic, numeric, vowels, \etc.
|
---|
459 | \begin{cquote}
|
---|
460 | \setlength{\tabcolsep}{15pt}
|
---|
461 | \begin{tabular}{@{}l|l@{}}
|
---|
462 | \begin{cfa}
|
---|
463 | charclass vowels{ "aeiouy" };
|
---|
464 | i = include( "aaeiuyoo", vowels );
|
---|
465 | i = include( "aabiuyoo", vowels );
|
---|
466 | \end{cfa}
|
---|
467 | &
|
---|
468 | \begin{cfa}
|
---|
469 |
|
---|
470 | 8 // compliant
|
---|
471 | 2 // b non-compliant
|
---|
472 | \end{cfa}
|
---|
473 | \end{tabular}
|
---|
474 | \end{cquote}
|
---|
475 | @vowels@ defines a character class and function @include@ checks if all characters in the string appear in the class (compliance).
|
---|
476 | The position of the last character is returned if the string is compliant or the position of the first non-compliant character.
|
---|
477 | There is no relationship between the order of characters in the two strings.
|
---|
478 | Function @exclude@ is the reverse of @include@, checking if all characters in the string are excluded from the class (compliance).
|
---|
479 | \begin{cquote}
|
---|
480 | \setlength{\tabcolsep}{15pt}
|
---|
481 | \begin{tabular}{@{}l|l@{}}
|
---|
482 | \begin{cfa}
|
---|
483 | i = exclude( "cdbfghmk", vowels );
|
---|
484 | i = exclude( "cdyfghmk", vowels );
|
---|
485 | \end{cfa}
|
---|
486 | &
|
---|
487 | \begin{cfa}
|
---|
488 | 8 // compliant
|
---|
489 | 2 // y non-compliant
|
---|
490 | \end{cfa}
|
---|
491 | \end{tabular}
|
---|
492 | \end{cquote}
|
---|
493 | Both forms can return the longest substring of compliant characters.
|
---|
494 | \begin{cquote}
|
---|
495 | \setlength{\tabcolsep}{15pt}
|
---|
496 | \begin{tabular}{@{}l|l@{}}
|
---|
497 | \begin{cfa}
|
---|
498 | s = include( "aaeiuyoo", vowels );
|
---|
499 | s = include( "aabiuyoo", vowels );
|
---|
500 | s = exclude( "cdbfghmk", vowels );
|
---|
501 | s = exclude( "cdyfghmk", vowels );
|
---|
502 | \end{cfa}
|
---|
503 | &
|
---|
504 | \begin{cfa}
|
---|
505 | "aaeiuyoo"
|
---|
506 | "aa"
|
---|
507 | "cdbfghmk"
|
---|
508 | "cd"
|
---|
509 | \end{cfa}
|
---|
510 | \end{tabular}
|
---|
511 | \end{cquote}
|
---|
512 |
|
---|
513 | There are also versions of @include@ and @exclude@, returning a position or string, taking a validation function, like one of the C character-class routines.\footnote{It is part of the hereditary of C that these function take and return an \lstinline{int} rather than a \lstinline{bool}, which affects the function type.}
|
---|
514 | \begin{cquote}
|
---|
515 | \setlength{\tabcolsep}{15pt}
|
---|
516 | \begin{tabular}{@{}l|l@{}}
|
---|
517 | \begin{cfa}
|
---|
518 | i = include( "1FeC34aB", @isxdigit@ );
|
---|
519 | i = include( ".,;'!\"", @ispunct@ );
|
---|
520 | i = include( "XXXx", @isupper@ );
|
---|
521 | \end{cfa}
|
---|
522 | &
|
---|
523 | \begin{cfa}
|
---|
524 | 8 // compliant
|
---|
525 | 6 // compliant
|
---|
526 | 3 // non-compliant
|
---|
527 | \end{cfa}
|
---|
528 | \end{tabular}
|
---|
529 | \end{cquote}
|
---|
530 | These operations perform an \emph{apply} of the validation function to each character, where the function returns a boolean indicating a stopping condition for the search.
|
---|
531 | The position of the last character is returned if the string is compliant or the position of the first non-compliant character.
|
---|
532 |
|
---|
533 | The translate operation returns a string with each character transformed by one of the C character transformation functions.
|
---|
534 | \begin{cquote}
|
---|
535 | \setlength{\tabcolsep}{15pt}
|
---|
536 | \begin{tabular}{@{}l|l@{}}
|
---|
537 | \begin{cfa}
|
---|
538 | s = translate( "abc", @toupper@ );
|
---|
539 | s = translate( "ABC", @tolower@ );
|
---|
540 | int tospace( int c ) { return isspace( c ) ? ' ' : c; }
|
---|
541 | s = translate( "X X\tX\nX", @tospace@ );
|
---|
542 | \end{cfa}
|
---|
543 | &
|
---|
544 | \begin{cfa}
|
---|
545 | "ABC"
|
---|
546 | "abc"
|
---|
547 |
|
---|
548 | "X X X X"
|
---|
549 | \end{cfa}
|
---|
550 | \end{tabular}
|
---|
551 | \end{cquote}
|
---|
552 |
|
---|
553 |
|
---|
554 | \subsection{Returning N on Search Failure}
|
---|
555 |
|
---|
556 | Some of the prior string operations are composite, \eg string operations returning the longest substring of compliant characters (@include@) are built using a search and then substring the appropriate text.
|
---|
557 | However, string search can fail, which is reported as an alternate search outcome, possibly an exception.
|
---|
558 | Many string libraries use a return code to indicate search failure, with a failure value of @0@ or @-1@ (PL/I~\cite{PLI} returns @0@).
|
---|
559 | This semantics leads to the awkward pattern, which can appear many times in a string library or user code.
|
---|
560 | \begin{cfa}
|
---|
561 | i = exclude( s, alpha );
|
---|
562 | if ( i != -1 ) return s( 0, i );
|
---|
563 | else return "";
|
---|
564 | \end{cfa}
|
---|
565 |
|
---|
566 | \CFA adopts a return code but the failure value is taken from the index-of function in APL~\cite{apl}, which returns the length of the target string $N$ (or $N+1$ for 1 origin).
|
---|
567 | This semantics allows many search and substring functions to be written without conditions, \eg:
|
---|
568 | \begin{cfa}
|
---|
569 | string include( const string & s, int (*f)( int ) ) { return @s( 0, include( s, f ) )@; }
|
---|
570 | string exclude( const string & s, int (*f)( int ) ) { return @s( 0, exclude( s, f ) )@; }
|
---|
571 | \end{cfa}
|
---|
572 | In string systems with an $O(1)$ length operator, checking for failure is low cost.
|
---|
573 | \begin{cfa}
|
---|
574 | if ( include( line, alpha ) == len( line ) ) ... // not found, 0 origin
|
---|
575 | \end{cfa}
|
---|
576 | \VRef[Figure]{f:ExtractingWordsText} compares \CC and \CFA string code for extracting words from a line of text, repeatedly removing non-word text and then a word until the line is empty.
|
---|
577 | The \CFA code is simpler solely because of the choice for indicating search failure.
|
---|
578 | (A simplification of the \CC version is to concatenate a sentinel character at the end of the line so the call to @find_first_not_of@ does not fail.)
|
---|
579 |
|
---|
580 | \begin{figure}
|
---|
581 | \begin{cquote}
|
---|
582 | \setlength{\tabcolsep}{15pt}
|
---|
583 | \begin{tabular}{@{}l|l@{}}
|
---|
584 | \multicolumn{1}{c}{\textbf{\CC}} & \multicolumn{1}{c}{\textbf{\CFA}} \\
|
---|
585 | \begin{cfa}
|
---|
586 | for ( ;; ) {
|
---|
587 | string::size_type posn = line.find_first_of( alpha );
|
---|
588 | if ( posn == string::npos ) break;
|
---|
589 | line = line.substr( posn );
|
---|
590 | posn = line.find_first_not_of( alpha );
|
---|
591 | if ( posn != string::npos ) {
|
---|
592 | cout << line.substr( 0, posn ) << endl;
|
---|
593 | line = line.substr( posn );
|
---|
594 | } else {
|
---|
595 | cout << line << endl;
|
---|
596 | line = "";
|
---|
597 | }
|
---|
598 | }
|
---|
599 | \end{cfa}
|
---|
600 | &
|
---|
601 | \begin{cfa}
|
---|
602 | for ( ;; ) {
|
---|
603 | size_t posn = exclude( line, alpha );
|
---|
604 | if ( posn == len( line ) ) break;
|
---|
605 | line = line( posn );
|
---|
606 | posn = include( line, alpha );
|
---|
607 |
|
---|
608 | sout | line( 0, posn );
|
---|
609 | line = line( posn );
|
---|
610 |
|
---|
611 |
|
---|
612 |
|
---|
613 |
|
---|
614 | }
|
---|
615 | \end{cfa}
|
---|
616 | \end{tabular}
|
---|
617 | \end{cquote}
|
---|
618 | \caption{Extracting Words from Line of Text}
|
---|
619 | \label{f:ExtractingWordsText}
|
---|
620 | \end{figure}
|
---|
621 |
|
---|
622 |
|
---|
623 | \subsection{C Compatibility}
|
---|
624 |
|
---|
625 | To ease conversion from C to \CFA, \CFA provides companion C @string@ functions.
|
---|
626 | Hence, it is possible to convert a block of C string operations to \CFA strings just by changing the type @char *@ to @string@.
|
---|
627 | \begin{cquote}
|
---|
628 | \setlength{\tabcolsep}{15pt}
|
---|
629 | \begin{tabular}{@{}ll@{}}
|
---|
630 | \begin{cfa}
|
---|
631 | char s[32]; // string s;
|
---|
632 | strlen( s );
|
---|
633 | strnlen( s, 3 );
|
---|
634 | strcmp( s, "abc" );
|
---|
635 | strncmp( s, "abc", 3 );
|
---|
636 | \end{cfa}
|
---|
637 | &
|
---|
638 | \begin{cfa}
|
---|
639 |
|
---|
640 | strcpy( s, "abc" );
|
---|
641 | strncpy( s, "abcdef", 3 );
|
---|
642 | strcat( s, "xyz" );
|
---|
643 | strncat( s, "uvwxyz", 3 );
|
---|
644 | \end{cfa}
|
---|
645 | \end{tabular}
|
---|
646 | \end{cquote}
|
---|
647 | However, the conversion fails with I/O because @printf@ cannot print a @string@ using format code @%s@ because \CFA strings are not null terminated.
|
---|
648 | Nevertheless, this capability does provide a useful starting point for conversion to safer \CFA strings.
|
---|
649 |
|
---|
650 |
|
---|
651 | \subsection{I/O Operators}
|
---|
652 |
|
---|
653 | The ability to input and output strings is as essential as for any other type.
|
---|
654 | The goal for character I/O is to also work with groups rather than individual characters.
|
---|
655 | A comparison with \CC string I/O is presented as a counterpoint to \CFA string I/O.
|
---|
656 |
|
---|
657 | The \CC output @<<@ and input @>>@ operators are defined on type @string@.
|
---|
658 | \CC output for @char@, @char *@, and @string@ are similar.
|
---|
659 | The \CC manipulators are @setw@, and its associated width controls @left@, @right@ and @setfill@.
|
---|
660 | \begin{cquote}
|
---|
661 | \setlength{\tabcolsep}{15pt}
|
---|
662 | \begin{tabular}{@{}l|l@{}}
|
---|
663 | \begin{c++}
|
---|
664 | string s = "abc";
|
---|
665 | cout << setw(10) << left << setfill( 'x' ) << s << endl;
|
---|
666 | \end{c++}
|
---|
667 | &
|
---|
668 | \begin{c++}
|
---|
669 |
|
---|
670 | "abcxxxxxxx"
|
---|
671 | \end{c++}
|
---|
672 | \end{tabular}
|
---|
673 | \end{cquote}
|
---|
674 |
|
---|
675 | The \CFA input/output operator @|@ is defined on type @string@.
|
---|
676 | \CFA output for @char@, @char *@, and @string@ are similar.
|
---|
677 | The \CFA manipulators are @bin@, @oct@, @hex@, @wd@, and its associated width control and @left@.
|
---|
678 | \begin{cquote}
|
---|
679 | \setlength{\tabcolsep}{15pt}
|
---|
680 | \begin{tabular}{@{}l|l@{}}
|
---|
681 | \begin{cfa}
|
---|
682 | string s = "abc";
|
---|
683 | sout | bin( s ) | nl
|
---|
684 | | oct( s ) | nl
|
---|
685 | | hex( s ) | nl
|
---|
686 | | wd( 10, s ) | nl
|
---|
687 | | wd( 10, 2, s ) | nl
|
---|
688 | | left( wd( 10, s ) );
|
---|
689 | \end{cfa}
|
---|
690 | &
|
---|
691 | \begin{cfa}
|
---|
692 |
|
---|
693 | "0b1100001 0b1100010 0b1100011"
|
---|
694 | "0141 0142 0143"
|
---|
695 | "0x61 0x62 0x63"
|
---|
696 | " abc"
|
---|
697 | " ab"
|
---|
698 | "abc "
|
---|
699 | \end{cfa}
|
---|
700 | \end{tabular}
|
---|
701 | \end{cquote}
|
---|
702 |
|
---|
703 | \CC input matching for @char@, @char *@, and @string@ are similar, where \emph{all} input characters are read from the current point in the input stream to the end of the type size, format width, whitespace, end of line (@'\n'@), or end of file.
|
---|
704 | The \CC manipulator is @setw@ to restrict the size.
|
---|
705 | Reading into a @char@ is safe as the size is 1, @char *@ is unsafe without using @setw@ to constraint the length (which includes @'\0'@), @string@ is safe as its grows dynamically as characters are read.
|
---|
706 | \begin{cquote}
|
---|
707 | \setlength{\tabcolsep}{15pt}
|
---|
708 | \begin{tabular}{@{}l|l@{}}
|
---|
709 | \begin{c++}
|
---|
710 | char ch, c[10];
|
---|
711 | string s;
|
---|
712 | cin >> ch >> setw( 5 ) >> c >> s;
|
---|
713 | @abcde fg@
|
---|
714 | \end{c++}
|
---|
715 | &
|
---|
716 | \begin{c++}
|
---|
717 |
|
---|
718 |
|
---|
719 | 'a' "bcde" "fg"
|
---|
720 |
|
---|
721 | \end{c++}
|
---|
722 | \end{tabular}
|
---|
723 | \end{cquote}
|
---|
724 | Input text can be gulped, including whitespace, from the current point to an arbitrary delimiter character using @getline@.
|
---|
725 |
|
---|
726 | The \CFA philosophy for input is that, for every constant type in C, these constants should be usable as input.
|
---|
727 | For example, the complex constant @3.5+4.1i@ can appear as input to a complex variable.
|
---|
728 | \CFA input matching for @char@, @char *@, and @string@ are similar.
|
---|
729 | C-strings may only be read with a width field, which should match the string size.
|
---|
730 | Certain input manipulators support a scanset, which is a simple regular expression from @printf@.
|
---|
731 | The \CFA manipulators for these types are @wdi@,\footnote{Due to an overloading issue in the type-resolver, the input width name must be temporarily different from the output, \lstinline{wdi} versus \lstinline{wd}.} and its associated width control and @left@, @quote@, @incl@, @excl@, and @getline@.
|
---|
732 | \begin{cquote}
|
---|
733 | \setlength{\tabcolsep}{10pt}
|
---|
734 | \begin{tabular}{@{}l|l@{}}
|
---|
735 | \begin{c++}
|
---|
736 | char ch, c[10];
|
---|
737 | string s;
|
---|
738 | sin | ch | wdi( 5, c ) | s;
|
---|
739 | @abcde fg@
|
---|
740 | sin | quote( ch ) | quote( wdi( sizeof(c), c ) ) | quote( s, '[', ']' ) | nl;
|
---|
741 | @'a' "bcde" [fg]@
|
---|
742 | sin | incl( "a-zA-Z0-9 ?!&\n", s ) | nl;
|
---|
743 | @x?&000xyz TOM !.@
|
---|
744 | sin | excl( "a-zA-Z0-9 ?!&\n", s );
|
---|
745 | @<>{}{}STOP@
|
---|
746 | \end{c++}
|
---|
747 | &
|
---|
748 | \begin{c++}
|
---|
749 |
|
---|
750 |
|
---|
751 | 'a' "bcde" "fg"
|
---|
752 |
|
---|
753 | 'a' "bcde" "fg"
|
---|
754 |
|
---|
755 | "x?&000xyz TOM !"
|
---|
756 |
|
---|
757 | "<>{}{}"
|
---|
758 |
|
---|
759 | \end{c++}
|
---|
760 | \end{tabular}
|
---|
761 | \end{cquote}
|
---|
762 | Note, the ability to read in quoted strings to match with program string constants.
|
---|
763 | The @nl@ at the end of an input ignores the rest of the line.
|
---|
764 |
|
---|
765 |
|
---|
766 | \subsection{Assignment}
|
---|
767 |
|
---|
768 | While \VRef[Figure]{f:StrApiCompare} emphasizes cross-language similarities, it elides many specific operational differences.
|
---|
769 | For example, the \CC @replace@ function selects a substring in the target and substitutes it with the source string, which can be smaller or larger than the substring.
|
---|
770 | \CC modifies the mutable receiver object, replacing by position (zero origin) and length.
|
---|
771 | \begin{cquote}
|
---|
772 | \setlength{\tabcolsep}{15pt}
|
---|
773 | \begin{tabular}{@{}l|l@{}}
|
---|
774 | \begin{c++}
|
---|
775 | string s1 = "abcde";
|
---|
776 | s1.replace( 2, 3, "xy" );
|
---|
777 | \end{c++}
|
---|
778 | &
|
---|
779 | \begin{c++}
|
---|
780 |
|
---|
781 | "abxy"
|
---|
782 | \end{c++}
|
---|
783 | \end{tabular}
|
---|
784 | \end{cquote}
|
---|
785 | Java cannot modify the receiver (immutable strings) so it returns a new string, replacing by text.
|
---|
786 | \label{p:JavaReplace}
|
---|
787 | \begin{cquote}
|
---|
788 | \setlength{\tabcolsep}{15pt}
|
---|
789 | \begin{tabular}{@{}l|l@{}}
|
---|
790 | \begin{java}
|
---|
791 | String s = "abcde";
|
---|
792 | String r = s.replace( "cde", "xy" );
|
---|
793 | \end{java}
|
---|
794 | &
|
---|
795 | \begin{java}
|
---|
796 |
|
---|
797 | "abxy"
|
---|
798 | \end{java}
|
---|
799 | \end{tabular}
|
---|
800 | \end{cquote}
|
---|
801 | Java also provides a mutable @StringBuffer@, replacing by position (zero origin) and length.
|
---|
802 | \begin{cquote}
|
---|
803 | \setlength{\tabcolsep}{15pt}
|
---|
804 | \begin{tabular}{@{}l|l@{}}
|
---|
805 | \begin{java}
|
---|
806 | StringBuffer sb = new StringBuffer( "abcde" );
|
---|
807 | sb.replace( 2, 5, "xy" );
|
---|
808 | \end{java}
|
---|
809 | &
|
---|
810 | \begin{java}
|
---|
811 |
|
---|
812 | "abxy"
|
---|
813 | \end{java}
|
---|
814 | \end{tabular}
|
---|
815 | \end{cquote}
|
---|
816 | However, there are anomalies.
|
---|
817 | @StringBuffer@'s @substring@ returns a @String@ copy that is immutable rather than modifying the receiver.
|
---|
818 | As well, the operations are asymmetric, \eg @String@ has @replace@ by text but not replace by position and vice versa for @StringBuffer@.
|
---|
819 |
|
---|
820 | More significant operational differences relate to storage management, often appearing through assignment (@target = source@), and are summarized in \VRef[Figure]{f:StrSemanticCompare}, which defines properties type abstraction, state, symmetry, and referent.
|
---|
821 | The following discussion justifies the figure's yes/no entries per language.
|
---|
822 |
|
---|
823 | \begin{figure}
|
---|
824 | \setlength{\extrarowheight}{2pt}
|
---|
825 | \begin{tabularx}{\textwidth}{@{}p{0.6in}XXcccc@{}}
|
---|
826 | & & & \multicolumn{4}{@{}c@{}}{\underline{Supports Helpful?}} \\
|
---|
827 | & Required & Helpful & C & \CC & Java & \CFA \\
|
---|
828 | \hline
|
---|
829 | Type abst'n
|
---|
830 | & Low-level: The string type is a varying amount of text communicated via a parameter or return.
|
---|
831 | & High-level: The string-typed relieves the user of managing memory for the text.
|
---|
832 | & no & yes & yes & yes \\
|
---|
833 | \hline
|
---|
834 | State
|
---|
835 | & \multirow{2}{2in}
|
---|
836 | {Fast Initialize: The target receives the characters of the source without copying the characters, resulting in an Alias or Snapshot.}
|
---|
837 | & Alias: The target variable names the source text; changes made in either variable are visible in both.
|
---|
838 | & yes & yes & no & yes \\
|
---|
839 | \cline{3-7}
|
---|
840 | &
|
---|
841 | & Snapshot: Subsequent operations on either the source or target variable do not affect the other.
|
---|
842 | & no & no & yes & yes \\
|
---|
843 | \hline
|
---|
844 | Symmetry
|
---|
845 | & Laxed: The target's type is anything string-like; it may have a different status concerning ownership.
|
---|
846 | & Strict: The target's type is the same as the source; both strings are equivalent peers concerning ownership.
|
---|
847 | & n/a & no & yes & yes \\
|
---|
848 | \hline
|
---|
849 | Referent
|
---|
850 | & Variable-Constrained: The target can accept the entire text of the source.
|
---|
851 | & Fragment: The target can accept an arbitrary substring of the source.
|
---|
852 | & no & no & yes & yes
|
---|
853 | \end{tabularx}
|
---|
854 |
|
---|
855 | \noindent
|
---|
856 | Notes
|
---|
857 | \begin{itemize}[parsep=0pt]
|
---|
858 | \item
|
---|
859 | All languages support Required in all criteria.
|
---|
860 | \item
|
---|
861 | A language gets ``Supports Helpful'' in one criterion if it can do so without sacrificing the Required achievement on all other criteria.
|
---|
862 | \item
|
---|
863 | The C ``string'' is @char *@, under the conventions of @<string.h>@. Because this type does not manage a text allocation, symmetry does not apply.
|
---|
864 | \item
|
---|
865 | The Java @String@ class is analyzed; its @StringBuffer@ class behaves similarly to @C++@.
|
---|
866 | \end{itemize}
|
---|
867 | \caption{Comparison of languages' strings, storage management perspective.}
|
---|
868 | \label{f:StrSemanticCompare}
|
---|
869 | \end{figure}
|
---|
870 |
|
---|
871 | In C, these declarations give very different things.
|
---|
872 | \begin{cfa}
|
---|
873 | char x[$\,$] = "abcde";
|
---|
874 | char * y = "abcde";
|
---|
875 | \end{cfa}
|
---|
876 | Both associate the declared name with fixed-six contiguous bytes, filled as @{'a', 'b', 'c', 'd', 'e', 0}@.
|
---|
877 | But @x@ gets them allocated in the active stack frame (with values filled in as control passes the declaration), while @y@ refers into the executable's read-only data section.
|
---|
878 | With @x@ representing an allocation, it offers information in @sizeof(x)@ that @y@ does not.
|
---|
879 | But this extra information is second-class, as it can only be used in the immediate lexical context, \ie it cannot be passed on to string operations or user functions.
|
---|
880 | Only pointers to text buffers are first-class, and discussed further.
|
---|
881 |
|
---|
882 | \begin{cfa}
|
---|
883 | char * s = "abcde";
|
---|
884 | char * s1 = s; $\C{// alias state, n/a symmetry, variable-constrained referent}$
|
---|
885 | char * s2 = &s[1]; $\C{// alias state, n/a symmetry, variable-constrained referent}\CRT$
|
---|
886 | char * s3 = &s2[1]; $\C{// alias state, n/a symmetry, variable-constrained referent}
|
---|
887 | printf( "%s %s %s %s\n", s, s1, s2, s3 );
|
---|
888 | $\texttt{\small abcde abcde bcde cde}$
|
---|
889 | \end{cfa}
|
---|
890 | Note, all of these aliased strings rely on the single null termination character at the end of @s@.
|
---|
891 | The issue of symmetry does not apply to a low-level API because no management of a text buffer is provided.
|
---|
892 | With the @char *@ type not managing the text storage, there is no ownership question, \ie operations on @s1@ or @s2@ never lead to their memory becoming reusable.
|
---|
893 | While @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.
|
---|
894 |
|
---|
895 | In \CC, @string@ offers a high-level abstraction.
|
---|
896 | \begin{cfa}
|
---|
897 | string s = "abcde";
|
---|
898 | string & s1 = s; $\C[2.25in]{// alias state, lax symmetry, variable-constrained referent}$
|
---|
899 | string s2 = s; $\C{// no fast-initialize (strict symmetry, variable-constrained referent)}$
|
---|
900 | string s3 = s.substr( 1, 2 ); $\C{// no fast-initialize (strict symmetry, fragment referent)}$
|
---|
901 | string s4 = s3.substr( 1, 1 ); $\C{// no fast-initialize (strict symmetry, fragment referent)}$
|
---|
902 | cout << s << ' ' << s1 << ' ' << s2 << ' ' << s3 << ' ' << s4 << endl;
|
---|
903 | $\texttt{\small abcde abcde abcde bc c}$
|
---|
904 | string & s5 = s.substr(2,4); $\C{// error: cannot point to temporary}\CRT$
|
---|
905 | \end{cfa}
|
---|
906 | The @s1@ lax symmetry reflects how its validity of depends on the lifetime of @s@.
|
---|
907 | It is common practice in \CC to use the @s1@-style for a by-reference function parameter.
|
---|
908 | Doing so assumes that the callee only uses the referenced string for the duration of the call, \ie no storing the parameter (as a reference) for later.
|
---|
909 | So, when the called function is a constructor, its definition typically uses an @s2@-style copy-initialization.
|
---|
910 | Exceptions to this pattern are possible, but require the programmer to assure safety where the type system does not.
|
---|
911 | The @s3@ initialization must copy the substring because it must support a subsequent @c_str@ call, which provides a null-termination, generally at a different position than the source string's.
|
---|
912 | @s2@ assignment could be made fast, by reference-counting the text area and using copy-on-write, but would require an implementation upgrade.
|
---|
913 |
|
---|
914 | In Java, @String@ also offers a high-level abstraction:
|
---|
915 | \begin{java}
|
---|
916 | String s = "abcde";
|
---|
917 | String s1 = s; $\C[2.25in]{// snapshot state, strict symmetry, variable-constrained referent}$
|
---|
918 | String s2 = s.substring( 1, 3 ); $\C{// snapshot state (possible), strict symmetry, fragment referent}$
|
---|
919 | String s3 = s2.substring( 1, 2 ); $\C{// snapshot state (possible), strict symmetry, fragment referent}\CRT$
|
---|
920 | System.out.println( s + ' ' + s1 + ' ' + s2 + ' ' + s3 );
|
---|
921 | System.out.println( (s == s1) + " " + (s == s2) + " " + (s2 == s3) );
|
---|
922 | $\texttt{\small abcde abcde bc c}$
|
---|
923 | $\texttt{\small true false false}$
|
---|
924 | \end{java}
|
---|
925 | Note, Java's @substring@ takes a start and end position, rather than a start position and length.
|
---|
926 | Here, facts about Java's implicit pointers and pointer equality can over complicate the picture, and so are ignored.
|
---|
927 | Furthermore, Java's string immutability means string variables behave as simple values.
|
---|
928 | The result in @s1@ is the pointer in @s@, and their pointer equality confirm no time is spent copying characters.
|
---|
929 | With @s2@, the case for fast-copy is more subtle.
|
---|
930 | Certainly, its value is not pointer-equal to @s@, implying at least a further allocation.
|
---|
931 | But because Java is not constrained to use a null-terminated representation, a standard-library implementation is free to refer to the source characters in-place.
|
---|
932 | Java does not meet the aliasing requirement because immutability makes it impossible to modify.
|
---|
933 | Java'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@;
|
---|
934 | as a result, @StringBuffer@ scores as \CC.
|
---|
935 | The easy symmetry that the Java string enjoys is aided by Java's garbage collection; Java's @s1@ is doing effectively the operation of \CC's @s1@, though without the consequence of complicating memory management.
|
---|
936 |
|
---|
937 | % former \PAB{What complex storage management is going on here?}
|
---|
938 | % answer: the variable names in the sentence were out of date; fixed
|
---|
939 |
|
---|
940 | Finally, in \CFA, @string@ also offers a high-level abstraction:
|
---|
941 | \begin{cfa}
|
---|
942 | string s = "abcde";
|
---|
943 | string & s1 = s; $\C[2.25in]{// alias state, lax symmetry, variable-constrained referent}$
|
---|
944 | string s2 = s; $\C{// snapshot state, strict symmetry, variable-constrained referent}$
|
---|
945 | string s3 = s`share; $\C{// alias state, strict symmetry, variable-constrained referent}$
|
---|
946 | string s4 = s( 1, 2 ); $\C{// snapshot state, strict symmetry, fragment referent}$
|
---|
947 | string s5 = s4( 1, 1 )`share'; $\C{// alias state, strict symmetry, fragment referent}\CRT$
|
---|
948 | sout | s | s1 | s2 | s3 | s4 | s5;
|
---|
949 | $\texttt{\small abcde abcde abcde abcde bc c}$
|
---|
950 | \end{cfa}
|
---|
951 | % all helpful criteria of \VRef[Figure]{f:StrSemanticCompare} are satisfied.
|
---|
952 | The \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).
|
---|
953 | The @s1@ case is the same in \CFA as in \CC.
|
---|
954 | But the \CFA @s2@ delivers a fast snapshot, while the \CC @s2@ does not.
|
---|
955 | \CFA offers the snapshot-vs-alias choice in @s2@-vs-@s3@ and @s4@-vs-@s5@.
|
---|
956 | Regardless of snapshot-vs-alias and fragment-vs-whole, the target is always of the same @string@ type.
|
---|
957 |
|
---|
958 | % former \PAB{Need to discuss the example, as for the other languages.}
|
---|
959 | % answer: done
|
---|
960 |
|
---|
961 |
|
---|
962 |
|
---|
963 | \subsection{Logical overlap}
|
---|
964 |
|
---|
965 | It may be unfamiliar to combine \VRef[Figure]{f:StrSemanticCompare}'s alias state and fragment referent in one API, or at the same time.
|
---|
966 | This section shows the capability in action.
|
---|
967 |
|
---|
968 | In summary, the metaphor of a GUI text editor is intended.
|
---|
969 | Selecting a consecutive block of text using the mouse defines an aliased substring within the file.
|
---|
970 | Typing in this state overwrites what was there before, replacing the originally selected text with more or less text.
|
---|
971 | But the \emph{whole file} grows or shrinks as a result, not just the selection.
|
---|
972 | This action models assigning to an aliased substring when the two strings overlap by total containment: one string is the selection, the other is the whole file.
|
---|
973 |
|
---|
974 | Now extend the metaphor to a multi-user online editor.
|
---|
975 | If Alice selects a range of text at the bottom of the file, wile Bob is rewriting a paragraph at the top, Alice's selection holds onto the logical characters initially selected, unaffected by Bob making the total file grow/shrink, and unaffectd by Bob causing the start index of Alice's selction to vary.
|
---|
976 | This action models assigning to an aliased substring when the two strings do not overlap at all: one string is Alice's selection, the other is Bob's.
|
---|
977 |
|
---|
978 | If a third office worker were also watching Alice's and Bob's actions on the whole file (a string with ``all the text'' is kept around), then two further single-user-edit cases give the semantics of the individual edits flowing into the whole.
|
---|
979 | But, departing from the document analogy, it is not necessary to keep a such a third string:
|
---|
980 | no one has to resource-manage ``the document.''
|
---|
981 | When an original string, from which both the Alice- and Bob-parts came, ceases to exist, Alice and Bob are left with two independent strings.
|
---|
982 | They are independent because Alice and Bob have no API for growing the bounds of a string to subsume text that may once have been around it.
|
---|
983 |
|
---|
984 | Edge cases, notably ``Venn-diagram overlap,'' had to have handlings chosen.
|
---|
985 | The intent in fleshing out these details was to achieve the above story, with a single API, while keeping the rest as simple as possible.
|
---|
986 | The remainder of this section shows the resulting decisions, played out at the API level.
|
---|
987 |
|
---|
988 | \CFA uses the marker @`share@ as a dynamic mechanism to indicate alias (mutations shared) \vs snapshot (not quite an immutable result, but one with subsequent mutations isolated).
|
---|
989 | This aliasing relationship is a sticky property established at initialization.
|
---|
990 | For example, here strings @s1@ and @s1a@ are in an aliasing relationship, while @s2@ is in a copy relationship.
|
---|
991 | \input{sharing1.tex}
|
---|
992 | Here, the aliasing (@`share@) causes partial changes (subscripting) to flow in both directions.
|
---|
993 | (In the following examples, watch how @s1@ and @s1a@ change together, and @s2@ is independent.)
|
---|
994 | \input{sharing2.tex}
|
---|
995 | Similarly for complete changes.
|
---|
996 | \input{sharing3.tex}
|
---|
997 | Because string assignment copies the value, RHS aliasing is irrelevant.
|
---|
998 | Hence, aliasing of the LHS is unaffected.
|
---|
999 | \input{sharing4.tex}
|
---|
1000 |
|
---|
1001 | Now, 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@.
|
---|
1002 | \input{sharing5.tex}
|
---|
1003 | Again, @`share@ passes changes in both directions; copy does not.
|
---|
1004 | As 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"@.
|
---|
1005 | This alternate positioning also applies to subscripting.
|
---|
1006 | \input{sharing6.tex}
|
---|
1007 |
|
---|
1008 | Finally, assignment flows through the aliasing relationship without affecting its structure.
|
---|
1009 | \input{sharing7.tex}
|
---|
1010 | In the @"ff"@ assignment, the result is straightforward to accept because the flow direction is from contained (small) to containing (large).
|
---|
1011 | The following rules explain aliasing substrings that flow in the opposite direction, large to small.
|
---|
1012 |
|
---|
1013 | Growth and shrinkage are natural extensions, as for the text-editor analogy, where an empty substring is as real as an empty string.
|
---|
1014 | \input{sharing8.tex}
|
---|
1015 |
|
---|
1016 | Multiple portions of a string can be aliased.
|
---|
1017 | % When there are several aliasing substrings at once, the text editor analogy becomes an online multi-user editor.
|
---|
1018 | %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.
|
---|
1019 | \input{sharing9.tex}
|
---|
1020 | When @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,
|
---|
1021 |
|
---|
1022 | When changes happens on an aliasing substring that overlap.
|
---|
1023 | \input{sharing10.tex}
|
---|
1024 | Strings @s1_crs@ and @s1_mid@ overlap at character 4, @j@ because the substrings are 3,2 and 4,2.
|
---|
1025 | When @s1_crs@'s size increases by 1, @s1_mid@'s starting location moves from 4 to 5, but the overlapping character remains, changing to @'+'@.
|
---|
1026 |
|
---|
1027 | \PAB{TODO: finish typesetting the demo}
|
---|
1028 |
|
---|
1029 | %\input{sharing-demo.tex}
|
---|
1030 |
|
---|
1031 | \VRef[Figure]{f:ParameterPassing} shows similar relationships when passing the results of substring operations by reference and by value to a subprogram.
|
---|
1032 | Again, notice the side-effects to other reference parameters as one is modified.
|
---|
1033 |
|
---|
1034 | \begin{figure}
|
---|
1035 | \begin{cfa}
|
---|
1036 | // x, a, b, c, & d are substring results passed by reference
|
---|
1037 | // e is a substring result passed by value
|
---|
1038 | void test( string & x, string & a, string & b, string & c, string & d, string e ) {
|
---|
1039 | \end{cfa}
|
---|
1040 | \begin{cquote}
|
---|
1041 | \setlength{\tabcolsep}{2pt}
|
---|
1042 | \begin{tabular}{@{}ll@{}}
|
---|
1043 | \begin{cfa}
|
---|
1044 |
|
---|
1045 | a( 0, 2 ) = "aaa";
|
---|
1046 | b( 1, 12 ) = "bbb";
|
---|
1047 | c( 4, 5 ) = "ccc";
|
---|
1048 | c = "yyy";
|
---|
1049 | d( 0, 3 ) = "ddd";
|
---|
1050 | e( 0, 3 ) = "eee";
|
---|
1051 | x = e;
|
---|
1052 | }
|
---|
1053 | \end{cfa}
|
---|
1054 | &
|
---|
1055 | \sf
|
---|
1056 | \setlength{\extrarowheight}{-0.5pt}
|
---|
1057 | \begin{tabular}{@{}llllll@{}}
|
---|
1058 | x & a & b & c & d & e \\
|
---|
1059 | @"aaaxxxxxxxxx"@ & @"aaax"@ & @"xxx"@ & @"xxxxx"@ & @"xxx"@ & @"xxx"@ \\
|
---|
1060 | @"aaaxbbbxxxxxx"@ & @"aaax"@ & @"xbbb"@ & @"xxxx"@ & @"xxx"@ & @"xxx"@ \\
|
---|
1061 | @"aaaxbbbxxxcccxx"@ & @"aaax"@ & @"xbbb"@ & @"xxxccc"@& @"cccxx"@ & @"xxx"@ \\
|
---|
1062 | @"aaaxbbbyyyxx"@ & @"aaax"@ & @"aaab"@ & @"yyy"@ & @"xx"@ & @"xxx"@ \\
|
---|
1063 | @"aaaxbbbyyyddd"@ & @"aaax"@ & @"xbbb"@ & @"yyy"@ & @"ddd"@ & @"xxx"@ \\
|
---|
1064 | @"aaaxbbbyyyddd"@ & @"aaax"@ & @"xbbb"@ & @"yyy"@ & @"ddd"@ & @"eee"@ \\
|
---|
1065 | @"eee"@ & @""@ & @""@ & @""@ & @"eee"@ \\
|
---|
1066 | & \\
|
---|
1067 | \end{tabular}
|
---|
1068 | \end{tabular}
|
---|
1069 | \end{cquote}
|
---|
1070 | \begin{cfa}
|
---|
1071 | int main() {
|
---|
1072 | string x = "xxxxxxxxxxx";
|
---|
1073 | test( x, x(0, 3), x(2, 3), x(4, 5), x(8, 5), x(8, 5) );
|
---|
1074 | }
|
---|
1075 | \end{cfa}
|
---|
1076 | \caption{Parameter Passing}
|
---|
1077 | \label{f:ParameterPassing}
|
---|
1078 | \end{figure}
|
---|
1079 |
|
---|
1080 |
|
---|
1081 |
|
---|
1082 | \section{Storage management}
|
---|
1083 |
|
---|
1084 | This section discusses issues related to storage management of strings.
|
---|
1085 | Specifically, it is common for strings to logically overlap partially or completely.
|
---|
1086 | \begin{cfa}
|
---|
1087 | string s1 = "abcdef";
|
---|
1088 | string s2 = s1; $\C{// complete overlap, s2 == "abcdef"}$
|
---|
1089 | string s3 = s1.substr( 0, 3 ); $\C{// partial overlap, s3 == "abc"}$
|
---|
1090 | \end{cfa}
|
---|
1091 | This raises the question of how strings behave when an overlapping component is changed,
|
---|
1092 | \begin{cfa}
|
---|
1093 | s3[1] = 'w'; $\C{// what happens to s1 and s2?}$
|
---|
1094 | \end{cfa}
|
---|
1095 | which is restricted by a string's mutable or immutable property.
|
---|
1096 | For example, Java's immutable strings require copy-on-write when any overlapping string changes.
|
---|
1097 | Note, the notion of underlying string mutability is not specified by @const@; \eg in \CC:
|
---|
1098 | \begin{cfa}
|
---|
1099 | const string s1 = "abc";
|
---|
1100 | \end{cfa}
|
---|
1101 | the @const@ applies to the @s1@ pointer to @"abc"@, and @"abc"@ is an immutable constant that is \emph{copied} into the string's storage.
|
---|
1102 | Hence, @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.
|
---|
1103 |
|
---|
1104 |
|
---|
1105 |
|
---|
1106 | \subsection{General implementation}
|
---|
1107 | \label{string-general-impl}
|
---|
1108 |
|
---|
1109 | A centrepiece of the string module is its memory manager.
|
---|
1110 | The management scheme defines a shared buffer for string text.
|
---|
1111 | Allocation in this buffer is always via a bump-pointer;
|
---|
1112 | the buffer is compacted and/or relocated (to grow) when it fills.
|
---|
1113 | A string is a smart pointer into this buffer.
|
---|
1114 |
|
---|
1115 | This cycle of frequent cheap allocations, interspersed with infrequent expensive compactions, has obvious similarities to a general-purpose memory manager based on garbage collection (GC).
|
---|
1116 | A few differences are noteworthy.
|
---|
1117 | First, in a general purpose manager, the allocated objects may contain pointers to other objects, making the transitive reachability of these objects a crucial property.
|
---|
1118 | Here, the allocations are text, so one allocation never keeps another alive.
|
---|
1119 | Second, in a general purpose manager, the handle that keeps an allocation alive is a bare pointer.
|
---|
1120 | For strings, a fatter representation is acceptable because this pseudo-pointer is only used for enty into the string-heap, not for general data-sub-structure linking around the general heap.
|
---|
1121 |
|
---|
1122 | \begin{figure}
|
---|
1123 | \includegraphics{memmgr-basic.pdf}
|
---|
1124 | \caption{String memory-management data structures}
|
---|
1125 | \label{f:memmgr-basic}
|
---|
1126 | \end{figure}
|
---|
1127 |
|
---|
1128 | \VRef[Figure]{f:memmgr-basic} shows the representation.
|
---|
1129 | The heap header and text buffer define a sharing context.
|
---|
1130 | Normally, one global sharing context is appropriate for an entire program;
|
---|
1131 | concurrent exceptions are discussed in \VRef{s:ControllingImplicitSharing}.
|
---|
1132 | A string is a handle into the buffer and node within a linked list.
|
---|
1133 | The list is doubly linked for $O(1)$ insertion and removal at any location.
|
---|
1134 | Strings are ordered in the list by text start address.
|
---|
1135 | The header maintains a next-allocation pointer, @alloc@, pointing to the last live allocation in the buffer.
|
---|
1136 | No external references point into the buffer and the management procedure relocates the text allocations as needed.
|
---|
1137 | A string handle references a containing string, while its string is contiguous and not null terminated.
|
---|
1138 | The length sets an upper limit on the string size, but is large (4 or 8 bytes).
|
---|
1139 | String handles can be allocated in the stack or heap, and represent the string variables in a program.
|
---|
1140 | Normal C life-time rules apply to guarantee correctness of the string linked-list.
|
---|
1141 | The text buffer is large enough with good management so that often only one dynamic allocation is necessary during program execution.
|
---|
1142 | % During this period, strings can vary in size dynamically.
|
---|
1143 |
|
---|
1144 | When the text buffer fills, \ie the next new string allocation causes @alloc@ to point beyond the end of the buffer, the strings are compacted.
|
---|
1145 | The linked handles define all live strings in the buffer, which indirectly defines the allocated and free space in the buffer.
|
---|
1146 | Since the string handles are in sorted order, the handle list can be traversed, copying the first live text to the start of the buffer, and subsequent strings after each other.
|
---|
1147 | If, upon compaction, the amount of free storage would still be less than the new string allocation, a larger text buffer is heap-allocated, the current buffer is copied into the new buffer, and the original buffer is freed.
|
---|
1148 | Note, the list of string handles is structurally unaffected during a compaction;
|
---|
1149 | only the text pointers in the handles are modified to new buffer locations.
|
---|
1150 |
|
---|
1151 | Object lifecycle events are the \emph{subscription-management} triggers in such a service.
|
---|
1152 | There are two fundamental string-creation functions: importing external text like a C-string or reading a string, and initialization from an existing \CFA string.
|
---|
1153 | When importing, storage comes from the end of the buffer, into which the text is copied.
|
---|
1154 | The new string handle is inserted at the end of the handle list because the new text is at the end of the buffer.
|
---|
1155 | When initializing from text already in the buffer, the new handle is a second reference into the original run of characters.
|
---|
1156 | In this case, the new handle's linked-list position is after the original handle.
|
---|
1157 | Both string initialization styles preserve the string module's internal invariant that the linked-list order matches the buffer order.
|
---|
1158 | For string destruction, handles are removed from the list.
|
---|
1159 | As a result, once a last handle using a run of buffer characters is destroyed, that buffer space gets excluded from the next compaction, making its character-count available in the compacted buffer.
|
---|
1160 |
|
---|
1161 | Certain string operations can result in a substring of another string.
|
---|
1162 | The resulting handle is then placed in the correct sorted position in the list, possible with a short linear search to locate the position.
|
---|
1163 | For string operations resulting in a new string, that string is allocated at the end of the buffer.
|
---|
1164 | For shared-edit strings, handles that originally referenced containing locations need to see the new value at the new buffer location.
|
---|
1165 | These strings are moved to appropriate locations at the end of the list \see{details in \VRef{sharing-impl}}.
|
---|
1166 | For nonshared-edit strings, a containing string can be moved and the other strings can remain in the same position.
|
---|
1167 | String assignment works similarly to string initialization, maintaining the invariant of linked-list order matching buffer order.
|
---|
1168 |
|
---|
1169 | At the level of the memory manager, these modifications can always be explained as assignment and appendment.
|
---|
1170 | For example, an append is an assignment into the empty substring at the end of the buffer.
|
---|
1171 | Favourable 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.
|
---|
1172 | One notable example of favourable conditions occurs because the most recently written string is often the last in the buffer, after which a large amount of free space occurs.
|
---|
1173 | So, repeated appends often occur without copying previously accumulated characters.
|
---|
1174 | However, 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.
|
---|
1175 |
|
---|
1176 |
|
---|
1177 | \subsection{RAII limitations}
|
---|
1178 | \label{string-raii-limit}
|
---|
1179 |
|
---|
1180 | Earlier work on \CFA~\cite[ch.~2]{Schluntz17} implemented object constructors and destructors for all types (basic and user defined).
|
---|
1181 | A constructor is a user-defined function run implicitly \emph{after} an object's storage is allocated, and a destructor is a user-defined function run \emph{before} an object's storage is deallcated.
|
---|
1182 | This feature, called Resource Acquisition Is Initialization (RAII)~\cite[p.~389]{Stroustrup94}, helps guarantee invariants for users before accessing an object and for the programming environment after an object terminates.
|
---|
1183 |
|
---|
1184 | The purposes of these invariants goes beyond ensuring authentic values inside an object.
|
---|
1185 | Invariants can also track data about managed objects in other data structures.
|
---|
1186 | Reference counting is an example application of an invariant outside of the immediate data value.
|
---|
1187 | With a reference-counting smart-pointer, the constructor and destructor \emph{of a pointer type} track the lifecycle of the object it points to.
|
---|
1188 | Both \CC and \CFA RAII systems are powerful enough to achieve reference counting.
|
---|
1189 |
|
---|
1190 | In 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.
|
---|
1191 | \begin{cfa}
|
---|
1192 | struct S { int * ip; };
|
---|
1193 | void ?{}( S & @this@ ) { this.ip = new(); } $\C[3in]{// default constructor}$
|
---|
1194 | void ?{}( S & @this@, int i ) { (this){}; *this.ip = i; } $\C{// initializing constructor}$
|
---|
1195 | void ?{}( S & @this@, S s ) { (this){*s.ip}; } $\C{// copy constructor}$
|
---|
1196 | void ^?{}( S & @this@ ) { delete( this.ip ); } $\C{// destructor}\CRT$
|
---|
1197 | \end{cfa}
|
---|
1198 | Such basic examples use the @this@ address only to gain access to the values being managed.
|
---|
1199 | But the lifecycle logic can use the pointer generally, too.
|
---|
1200 | For example, they can add @this@ object to a collection at creation and remove it at destruction.
|
---|
1201 | \begin{cfa}
|
---|
1202 | // header
|
---|
1203 | struct T {
|
---|
1204 | // private
|
---|
1205 | inline dlink(T);
|
---|
1206 | };
|
---|
1207 | void ?{}( T & ); $\C[3in]{// default constructor}$
|
---|
1208 | void ^?{}( T & ); $\C{// destructor}\CRT$
|
---|
1209 | // implementation
|
---|
1210 | static dlist(T) @all_T@;
|
---|
1211 | void ?{}( T & this ) { insert_last(all_T, @this@) }
|
---|
1212 | void ^?{}( T & this ) { remove(this); }
|
---|
1213 | \end{cfa}
|
---|
1214 | A module providing the @T@ type can traverse @all_T@ at relevant times, to keep the objects ``good.''
|
---|
1215 | Hence, declaring a @T@ not only ensures that it begins with an initially ``good'' value, but it also provides an implicit subscription to a service that keeps the value ``good'' in the future.
|
---|
1216 | Again, both \CFA and \CC support this usage style.
|
---|
1217 |
|
---|
1218 | A third capability concerns \emph{implicitly} requested copies.
|
---|
1219 | When stack-allocated objects are used as parameter and return values, a sender's version exists in one stack frame and a receiver's version exists in another.
|
---|
1220 | In the parameter direction, the language's function-call handling must arrange for a copy-constructor call to happen\footnote{
|
---|
1221 | \CC also offers move constructors and return-value optimization~\cite{RVO20}.
|
---|
1222 | These features help reduce unhelpful copy-constructor calls, which, for types like the example \lstinline{S}, would lead to extra memory allocations.
|
---|
1223 | \CFA does not currently have these features; adding similarly-intended features to \CFA is desirable.
|
---|
1224 | However, this section is about a problem in the realization of features that \CFA already supports.
|
---|
1225 | To understand the problem presented, the appropriate comparison is with classic versions of \CC that treated such copy-constructor calls as necessary.}
|
---|
1226 | at a time near the control transfer into the callee, with the source as the caller's (sender's) version and the target as the callee's (receiver's) version.
|
---|
1227 | (In the return direction, the roles are reversed and the copy-constructor call happens near the return of control.)
|
---|
1228 | \CC supports this capability without qualification.
|
---|
1229 | \CFA offers limited support here; simple examples work, but implicit copying does not combine successfully with the other RAII capabilities discussed.
|
---|
1230 |
|
---|
1231 | To summarize the unsupported combinations, the relevant features are:
|
---|
1232 | \begin{enumerate}
|
---|
1233 | \item
|
---|
1234 | Object provider implements lifecycle functions to manage a resource outside of the object.
|
---|
1235 | \item
|
---|
1236 | Object provider implements lifecycle functions to store references back to the object, often originating from outside of it.
|
---|
1237 | \item
|
---|
1238 | Object user expects to pass (in either direction) an object by value for function calls.
|
---|
1239 | \end{enumerate}
|
---|
1240 | \CC supports all three simultaneously. \CFA does not currently support \#2 and \#3 on the same object, though \#1 works along with either one of \#2 or \#3. \CFA needs to be fixed to support all three simultaneously.
|
---|
1241 |
|
---|
1242 | The reason that \CFA does not support \#2 with \#3 is a holdover from how \CFA function calls lowered to C, before \CFA got references and RAII.
|
---|
1243 | At that time, adhering to a principal of minimal intervention, this code could always be treated as passthrough:
|
---|
1244 | \begin{cfa}
|
---|
1245 | struct U {...};
|
---|
1246 | // RAII to go here
|
---|
1247 | void f( U u ) { F_BODY(u) }
|
---|
1248 | U x;
|
---|
1249 | f( x );
|
---|
1250 | \end{cfa}
|
---|
1251 | But adding custom RAII (at ``...here'') changes things.
|
---|
1252 | The common C++ lowering~\cite[Sec. 3.1.2.3]{cxx:raii-abi} proceeds differently than the present CFA lowering.
|
---|
1253 |
|
---|
1254 | \noindent
|
---|
1255 | \begin{tabular}{l|l}
|
---|
1256 | \begin{cfa}
|
---|
1257 | // C++, likely CFA to be
|
---|
1258 | struct U {...};
|
---|
1259 | // RAII elided
|
---|
1260 | void f( U * __u_orig ) {
|
---|
1261 | U u = * __u_orig; // call copy ctor
|
---|
1262 | F_BODY(u)
|
---|
1263 | // call dtor, u
|
---|
1264 | }
|
---|
1265 | U x; // call default ctor
|
---|
1266 | f( & x ) ;
|
---|
1267 | // call dtor, x
|
---|
1268 | \end{cfa}
|
---|
1269 | &
|
---|
1270 | \begin{cfa}
|
---|
1271 | // CFA today
|
---|
1272 | struct U {...};
|
---|
1273 | // RAII elided
|
---|
1274 | void f( U u ) {
|
---|
1275 | F_BODY(u)
|
---|
1276 | }
|
---|
1277 | U x; // call default ctor
|
---|
1278 | {
|
---|
1279 | U __u_for_f = x; // call copy ctor
|
---|
1280 | f( __u_for_f );
|
---|
1281 | // call dtor, __u_for_f
|
---|
1282 | }
|
---|
1283 | // call dtor, x
|
---|
1284 | \end{cfa}
|
---|
1285 | \end{tabular}
|
---|
1286 |
|
---|
1287 | In the CFA-today scheme, the lowered form is still using a by-value C call.
|
---|
1288 | C does a @memcpy@ on structs passed by value.
|
---|
1289 | And so, @F_BDY@ sees the bits of @__u_for_f@ occurring at an address that has never been presented to the @U@ lifecycle functions.
|
---|
1290 | If @U@ is trying to have a style-\#2 invariant, it shows up broken in @F_BDY@: references that are supposed to be to @u@ are actually to the different location @__u_for_f@.
|
---|
1291 | The \CC scheme does not have this problem because it constructs the for-@f@ copy in the correct location.
|
---|
1292 |
|
---|
1293 | Yet, the \CFA-today scheme is sufficient to deliver style-\#1 invariants (in this style-\#3 use case) because this scheme still does the correct number of lifecycle calls, using correct values, at correct times. So, reference-counting or simple ownership applications get their invariants respected under call/return-by-value.
|
---|
1294 |
|
---|
1295 | % [Mike is not currently seeing how distinguishing initialization from assignment is relevant]
|
---|
1296 | % as opposed to assignment where sender and receiver are in the same stack frame.
|
---|
1297 | % What 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.
|
---|
1298 | % To provide this knowledge, languages differentiate between initialization and assignment to a left-hand side.
|
---|
1299 | % \begin{cfa}
|
---|
1300 | % Obj obj2 = obj1; $\C[1.5in]{// initialization, obj2 is initialized}$
|
---|
1301 | % obj2 = obj1; $\C{// assignment, obj2 must be initialized for management to work}\CRT$
|
---|
1302 | % \end{cfa}
|
---|
1303 | % Initialization occurs at declaration by value, parameter by argument, return temporary by function call.
|
---|
1304 | % Hence, it is necessary to have two kinds of constructors: by value or object.
|
---|
1305 | % \begin{cfa}
|
---|
1306 | % Obj obj1{ 1, 2, 3 }; $\C[1.5in]{// by value, management is initialized}$
|
---|
1307 | % Obj obj2 = obj1; $\C{// by obj, management is updated}\CRT$
|
---|
1308 | % \end{cfa}
|
---|
1309 | % When no object management is required, initialization copies the right-hand value.
|
---|
1310 | % Hence, the calling convention remains uniform, where the unmanaged case uses @memcpy@ as the initialization constructor and managed uses the specified initialization constructor.
|
---|
1311 |
|
---|
1312 | % [Mike is currently seeing RVO as orthogonal, addressed with the footnote]
|
---|
1313 | % to a temporary.
|
---|
1314 | % For example, in \CC:
|
---|
1315 | % \begin{c++}
|
---|
1316 | % struct S {...};
|
---|
1317 | % S identity( S s ) { return s; }
|
---|
1318 | % S s;
|
---|
1319 | % s = identity( s ); // S temp = identity( s ); s = temp;
|
---|
1320 | % \end{c++}
|
---|
1321 | % the generated code for the function call created a temporary with initialization from the function call, and then assigns the temporary to the object.
|
---|
1322 | % This two step approach means extra storage for the temporary and two copies to get the result into the object variable.
|
---|
1323 | % \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''.
|
---|
1324 | % \CFA uses C semantics for function return giving direct value-assignment, which eliminates unnecessary code, but skips an essential feature needed by lifetime management.
|
---|
1325 | % The following discusses the consequences of this semantics with respect to lifetime management of \CFA strings.
|
---|
1326 |
|
---|
1327 |
|
---|
1328 | The string API offers style \#3's pass-by-value in, for example, in the return of @"a" + "b"@.
|
---|
1329 | Its implementation uses the style-\#2 invariant of the string handles being linked to each other, helping to achieve high performance.
|
---|
1330 | Since these two RAII styles cannont coexist, a workaround splits the API into two layers: one that provides pass-by-value, built upon the other with inter-linked handles.
|
---|
1331 | The layer with pass-by-value incurs a performance penalty, while the layer without delivers the desired runtime performance.
|
---|
1332 | The slower, friendlier High Level API (HL, type @string@) wrapps the faster, more primitive Low Level API (LL, type @string_res@, abbreviating ``resource'').
|
---|
1333 | Both APIs present the same features, up to return-by-value operations being unavailable in LL and implemented via the workaround in HL.
|
---|
1334 | The intention is for most future code to target HL.
|
---|
1335 | When the RAII issue is fixed, the full HL feature set will be acheivable using the LL-style lifetime management.
|
---|
1336 | So then, there will be no need for two API levels; HL will be removed; LL's type will be renamed to @string@; programs written for current HL will run faster.
|
---|
1337 | In the meantime, performance-critical sections of applications must use LL.
|
---|
1338 | Subsequent performance experiments \see{\VRef{s:PerformanceAssessment}} use the LL API when comparing \CFA to other languages.
|
---|
1339 | This measurement gives a fair estimate of the goal state for \CFA.
|
---|
1340 | A separate measure of the HL overhead is also included.
|
---|
1341 |
|
---|
1342 | \VRef[Section]{string-general-impl} described the goal state for \CFA. In present state, the type @string_res@ replaces its mention of @string@ as inter-linked handle.
|
---|
1343 |
|
---|
1344 | To use LL, a programmer rewrites invocations that used pass-by-value APIs into invocations where the resourcing is more explicit.
|
---|
1345 | Many invocations are unaffected, notably including assignment and comparison.
|
---|
1346 | Of the capabilities listed in \VRef[Figure]{f:StrApiCompare}, only the following three cases have revisions.
|
---|
1347 |
|
---|
1348 | \noindent
|
---|
1349 | \begin{tabular}{ll}
|
---|
1350 | HL & LL \\
|
---|
1351 | \hline
|
---|
1352 | \begin{cfa}
|
---|
1353 | string s = "a" + "b";
|
---|
1354 | \end{cfa}
|
---|
1355 | &
|
---|
1356 | \begin{cfa}
|
---|
1357 | string_res sr = "a";
|
---|
1358 | sr += b;
|
---|
1359 | \end{cfa}
|
---|
1360 | \\
|
---|
1361 | \hline
|
---|
1362 | \begin{cfa}
|
---|
1363 | string s = "abcde";
|
---|
1364 | string s2 = s(2, 3); // s2 == "cde"
|
---|
1365 | s(2,3) = "x"; // s == "abx" && s2 == "cde"
|
---|
1366 | \end{cfa}
|
---|
1367 | &
|
---|
1368 | \begin{cfa}
|
---|
1369 | string_res sr = "abcde";
|
---|
1370 | string_res sr2 = {sr, 2, 3}; // sr2 == "cde"
|
---|
1371 | string_res sr_mid = { sr, 2, 3, SHARE };
|
---|
1372 | sr_mid = "x"; // sr == "abx" && sr2 == "cde"
|
---|
1373 | \end{cfa}
|
---|
1374 | \\
|
---|
1375 | \hline
|
---|
1376 | \begin{cfa}
|
---|
1377 | string s = "abcde";
|
---|
1378 | s[2] = "xxx"; // s == "abxxxde"
|
---|
1379 | \end{cfa}
|
---|
1380 | &
|
---|
1381 | \begin{cfa}
|
---|
1382 | string_res sr = "abcde";
|
---|
1383 | string_res sr_mid = { sr, 2, 1, SHARE };
|
---|
1384 | mid = "xxx"; // sr == "abxxxde"
|
---|
1385 | \end{cfa}
|
---|
1386 | \end{tabular}
|
---|
1387 |
|
---|
1388 | The actual HL workaround is having @string@ wrap a pointer to a uniquely owned, heap-allocated @string_res@. This arrangement has @string@ being style-\#1 RAII, which is compatible with pass-by-value.
|
---|
1389 |
|
---|
1390 |
|
---|
1391 |
|
---|
1392 | \subsection{Sharing implementation}
|
---|
1393 | \label{sharing-impl}
|
---|
1394 |
|
---|
1395 | The \CFA string module has two mechanisms to handle the case when string handles share a run of text.
|
---|
1396 |
|
---|
1397 | In the first type of sharing, the user requests that both string handles be views of the same logical, modifiable string.
|
---|
1398 | This state is typically produced by the substring operation.
|
---|
1399 | \begin{cfa}
|
---|
1400 | string s = "abcde";
|
---|
1401 | string s1 = s( 1, 2 )@`share@; $\C[2.25in]{// explicit sharing}$
|
---|
1402 | s[1] = 'x'; $\C{// change s and s1}\CRT$
|
---|
1403 | sout | s | s1;
|
---|
1404 | $\texttt{\small axcde xc}$
|
---|
1405 | \end{cfa}
|
---|
1406 | 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.
|
---|
1407 | In this state, a subsequent modification made by either is visible in both.
|
---|
1408 |
|
---|
1409 | The 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.
|
---|
1410 | This state is typically produced by constructing a new string, using an original string as its initialization source.
|
---|
1411 | \begin{cfa}
|
---|
1412 | string s = "abcde";
|
---|
1413 | string s1 = s( 1, 2 )@@; $\C[2.25in]{// no sharing}$
|
---|
1414 | s[1] = 'x'; $\C{// copy-on-write s1}\CRT$
|
---|
1415 | sout | s | s1;
|
---|
1416 | $\texttt{\small axcde bc}$
|
---|
1417 | \end{cfa}
|
---|
1418 | In 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.
|
---|
1419 |
|
---|
1420 | A further abstraction, in the string module's implementation, helps distinguish the two senses of sharing.
|
---|
1421 | A 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'' option given.
|
---|
1422 | The SES is represented by a second linked list among the handles.
|
---|
1423 | A string that shares edits with no other is in a SES by itself.
|
---|
1424 | Inside a SES, a logical modification of one substring portion may change the logical value in another substring portion, depending on whether the two actually overlap.
|
---|
1425 | Conversely, no logical value change can flow outside of a SES.
|
---|
1426 | Even 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.
|
---|
1427 |
|
---|
1428 |
|
---|
1429 | \subsection{Controlling implicit sharing}
|
---|
1430 | \label{s:ControllingImplicitSharing}
|
---|
1431 |
|
---|
1432 | There are tradeoffs associated with sharing and its implicit copy-on-write mechanism.
|
---|
1433 | Several qualitative matters are detailed in \VRef{s:PerformanceAssessment}.
|
---|
1434 | In detail, string sharing has inter-linked string handles, so managing one string is also managing the neighbouring strings, and from there, a data structure of the ``set of all strings.''
|
---|
1435 | Therefore, it is useful to toggle this capability on or off when it is not providing any application benefit.
|
---|
1436 |
|
---|
1437 | \begin{figure}
|
---|
1438 | \begin{tabular}{ll}
|
---|
1439 | \lstinputlisting[language=CFA, firstline=10, lastline=55]{sharectx.run.cfa}
|
---|
1440 | &
|
---|
1441 | \raisebox{-0.17\totalheight}{\includegraphics{string-sharectx.pdf}} % lower
|
---|
1442 | \end{tabular}
|
---|
1443 | \caption{Controlling copying vs sharing of strings using \lstinline{string_sharectx}.}
|
---|
1444 | \label{fig:string-sharectx}
|
---|
1445 | \end{figure}
|
---|
1446 |
|
---|
1447 | The \CFA string library provides the type @string_sharectx@ to control an ambient sharing context.
|
---|
1448 | It allows two adjustments: to opt out of sharing entirely or to begin sharing within a private context.
|
---|
1449 | Running with sharing disabled can be thought of as a \CC STL-emulation mode, where each string is dynamically allocated.
|
---|
1450 | The chosen mode applies for the duration of the lifetime of the created @string_sharectx@ object, up to being suspended by child lifetimes of different contexts.
|
---|
1451 | \VRef[Figure]{fig:string-sharectx} illustrates this behaviour by showing the stack frames of a program in execution.
|
---|
1452 | In this example, the single-letter functions are called in alphabetic order.
|
---|
1453 | The functions @a@, @b@ and @g@ share string character ranges with each other, because they occupy a common sharing-enabled context.
|
---|
1454 | The function @e@ shares within itself (because its is in a sharing-enabled context), but not with the rest of the program (because its context is not occupied by any of the rest of the program).
|
---|
1455 | The functions @c@, @d@ and @f@ never share anything, because they are in a sharing-disabled context.
|
---|
1456 | Executing the example does not produce an interesting outcome, but the comments in the picture indicate when the logical copy operation runs with
|
---|
1457 | \begin{description}
|
---|
1458 | \item[share:] the copy being deferred, as described through the rest of this section (fast), or
|
---|
1459 | \item[copy:] the copy performed eagerly (slow).
|
---|
1460 | \end{description}
|
---|
1461 | Only eager copies can cross @string_sharectx@ boundaries.
|
---|
1462 | The 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.
|
---|
1463 |
|
---|
1464 | [ TODO: true up with ``is thread local'' (implement that and expand this discussion to give a concurrent example, or adjust this wording) ]
|
---|
1465 |
|
---|
1466 |
|
---|
1467 | \subsection{Sharing and threading}
|
---|
1468 |
|
---|
1469 | The \CFA string library provides no thread safety, the same as \CC string, providing similar performance goals.
|
---|
1470 | Threads can create their own string buffers and avoid passing these strings to other threads, or require that shared strings be immutable, as concurrent reading is safe.
|
---|
1471 | A positive consequence of this approach is that independent threads can use the sharing buffer without locking overhead.
|
---|
1472 | When string sharing amongst threads is required, program-wide string-management can toggled to non-sharing using @malloc@/@free@, where the storage allocator is assumed to be thread-safe.
|
---|
1473 | Finally, concurrent users of string objects can provide their own mutual exclusion.
|
---|
1474 |
|
---|
1475 |
|
---|
1476 | \subsection{Future work}
|
---|
1477 |
|
---|
1478 | Implementing the small-string optimization is straightforward, as a string header contains a pointer to the string text in the buffer.
|
---|
1479 | This pointer could be marked with a flag and contain a small string.
|
---|
1480 | However, there is now a conditional check required on the fast-path to switch between small and large string operations.
|
---|
1481 |
|
---|
1482 | It might be possible to pack 16- or 32-bit Unicode characters within the same string buffer as 8-bit characters.
|
---|
1483 | Again, locations for identification flags must be found and checked along the fast path to select the correct actions.
|
---|
1484 | Handling utf8 (variable length), is more problematic because simple pointer arithmetic cannot be used to stride through the variable-length characters.
|
---|
1485 | Trying to use a secondary array of fixed-sized pointers/offsets to the characters is possible, but raises the question of storage management for the utf8 characters themselves.
|
---|
1486 |
|
---|
1487 |
|
---|
1488 | \section{Performance assessment}
|
---|
1489 | \label{s:PerformanceAssessment}
|
---|
1490 |
|
---|
1491 | I assessed the \CFA string library's speed and memory usage against strings in \CC STL.
|
---|
1492 |
|
---|
1493 | Overall, this analysis shows that adding support for the features shown earlier in the chapter comes at no substantial cost in the performance of featrues common to both APIs.
|
---|
1494 |
|
---|
1495 | Moreover, the results support the \CFA string's position as a high-level enabler of simplified text processing.
|
---|
1496 | STL makes its user think about memory management.
|
---|
1497 | When the user does, and is successful, STL's performance can be very good.
|
---|
1498 | But when the user fails to think through the consequences of the STL representation, performance becomes poor.
|
---|
1499 | The \CFA string lets the user work at the level of just putting the right text into right variables, with corresponding performance degradations reduced or eliminated.
|
---|
1500 |
|
---|
1501 | % The final test shows the overall win of the \CFA text-sharing mechanism.
|
---|
1502 | % It exercises several operations together, showing \CFA enabling clean user code to achieve performance that STL requires less-clean user code to achieve.
|
---|
1503 |
|
---|
1504 |
|
---|
1505 | \subsection{Methodology}
|
---|
1506 |
|
---|
1507 | These tests use a \emph{corpus} of strings.
|
---|
1508 | Their lengths are important; the specific characters occurring in them are immaterial.
|
---|
1509 | In a result graph, a corpus's mean string length is often the independent variable shown on the X axis.
|
---|
1510 |
|
---|
1511 | When a corpus contains strings of different lenghths, the lengths are drawn from a geometric distribution.
|
---|
1512 | Therefore, strings much longer than the mean occur nontrivially and strings slightly shorter than the mean occur most often.
|
---|
1513 | A corpus's string sizes are one of:
|
---|
1514 | \begin{description}
|
---|
1515 | \item [Fixed-size] all string lengths are of the stated size.
|
---|
1516 | \item [Varying 1 and up] the string lengths are drawn from the geometric distribution with a stated mean and all lengths occur.
|
---|
1517 | \item [Varying 16 and up] string lengths are drawn from the geometric distribution with the stated mean, but only lengths 16 and above occur; thus, the stated mean is above 16. \PAB{Is this one unused? May have just been for ``normalize.''}
|
---|
1518 | \end{description}
|
---|
1519 | The special treatment of length 16 deals with the short-string optimization (SSO) in STL @string@, currently not implemented in \CFA, though a fine future improvement to \CFA.
|
---|
1520 | In the general case, an STL string handle is a pointer (to separately allocated text) and a length.
|
---|
1521 | But when the text is shorter than this representation, the optimization repurposes the handle's storage to eliminate using the heap.
|
---|
1522 | \begin{c++}
|
---|
1523 | class string {
|
---|
1524 | union {
|
---|
1525 | struct { $\C{// long string, text in heap}$
|
---|
1526 | size_t size;
|
---|
1527 | char * strptr;
|
---|
1528 | } lstr;
|
---|
1529 | char sstr[sizeof(lstr)]; $\C{// short string <16 characters, text in situ}$
|
---|
1530 | };
|
---|
1531 | $\C{// tagging for kind (short or long) elided}$
|
---|
1532 | };
|
---|
1533 | \end{c++}
|
---|
1534 |
|
---|
1535 | A fixed-size or from-16 distribution ensures that \CC's extra-optimized cases are isolated within, or removed from, the comparison.
|
---|
1536 |
|
---|
1537 | In all experiments that use a corpus, its text is generated and loaded into the system under test before the timed phase begins.
|
---|
1538 |
|
---|
1539 | To ensure comparable results, a common memory allocator is used for \CFA and \CC.
|
---|
1540 | \CFA runs the llheap allocator~\cite{Zulfiqar22}; the test rig plugs this same allocator into \CC.
|
---|
1541 |
|
---|
1542 | The operations being measured take dozens of nanoseconds, so a succession of many invocations is run and timed as a group.
|
---|
1543 | The experiments run with fixed duration (targeting approximately 5 seconds), stopping upon passing a goal time, as determined by re-checking @clock()@ every 10,000 invocations, which is never more often than once per 80 ms.
|
---|
1544 | Timing outcomes reprt mean nanoseconds per invocation, which includes harness overhead and the targeted string API execution.
|
---|
1545 |
|
---|
1546 | \PAB{To discuss: hardware and such}
|
---|
1547 |
|
---|
1548 | As discussed in \VRef[Section]{string-raii-limit}, general performance comparisons are made using \CFA's faster, low-level string API, whose string type is named @string_res@.
|
---|
1549 |
|
---|
1550 | \VRef{s:ControllingImplicitSharing} presents an operational mode where \CFA string sharing is turned off. In this mode, the \CFA string operates similarly to \CC's, by using a distinct heap allocation for each string's text.
|
---|
1551 | Some experiments include measurements in this mode for baselining purposes.
|
---|
1552 | It is called ``\CC emulation mode'' or ``nosharing'' here.
|
---|
1553 |
|
---|
1554 |
|
---|
1555 |
|
---|
1556 | \subsection{Test: Append}
|
---|
1557 |
|
---|
1558 | These tests measure the speed of appending strings from the corpus onto a larger, growing string. They show \CFA performing comparably to \CC overall, though with reduced penalties for simple API misuses for which \CC programmers may not know to watch out.
|
---|
1559 |
|
---|
1560 | The basic harness is:
|
---|
1561 | \begin{cquote}
|
---|
1562 | \setlength{\tabcolsep}{20pt}
|
---|
1563 | \begin{cfa}
|
---|
1564 | START_TIMER
|
---|
1565 | for ( ... ) {
|
---|
1566 | string_res accum;
|
---|
1567 | for ( i; 100 ) {
|
---|
1568 | accum += corpus[ f(i) ]; // importing from char * here
|
---|
1569 | COUNT_ONE_OP_DONE
|
---|
1570 | }
|
---|
1571 | }
|
---|
1572 | STOP_TIMER
|
---|
1573 | \end{cfa}
|
---|
1574 | \end{cquote}
|
---|
1575 | The harness's outer loop executes until a sample-worthy amount of execution has happened.
|
---|
1576 | The inner loop builds up the desired-length string with successive appends, before the outer makes it start over from a blank accumulator.
|
---|
1577 | Each harness run targets a specific (mean) corpus string length and produces one data point on the result graph.
|
---|
1578 |
|
---|
1579 | Three specific comparisons are made with this harness.
|
---|
1580 | Each picks its own independent-variable basis of comparison.
|
---|
1581 |
|
---|
1582 | All three comparisons use the varying-from-1 corpus construction, \ie they allow the STL to show its advantage from small-string optimization.
|
---|
1583 |
|
---|
1584 |
|
---|
1585 | \subsubsection{Fresh vs Reuse in \CC, Emulation Baseline}
|
---|
1586 |
|
---|
1587 | The first experiment compares \CFA with \CC, with \CFA operating in nosharing mode (and \CC having no other mode).
|
---|
1588 | This experiment simply baselines how \CFA modestly lags \CC's optimization/tuning level generally, yet reproduces a coarser phenomenon.
|
---|
1589 |
|
---|
1590 | This experiment also introduces the first \CC coding pitfall, which the next experiment will show is helped by turning on \CFA sharing. By this pitfall, a \CC programmer must pay attention to string variable reuse.
|
---|
1591 |
|
---|
1592 | \begin{cquote}
|
---|
1593 | \setlength{\tabcolsep}{20pt}
|
---|
1594 | \begin{tabular}{@{}ll@{}}
|
---|
1595 | % \multicolumn{1}{c}{\textbf{fresh}} & \multicolumn{1}{c}{\textbf{reuse}} \\
|
---|
1596 | \begin{cfa}
|
---|
1597 |
|
---|
1598 | for ( ... ) {
|
---|
1599 | @string_res accum;@ // fresh
|
---|
1600 | for ( ... )
|
---|
1601 | accum @+=@ ...
|
---|
1602 | }
|
---|
1603 | \end{cfa}
|
---|
1604 | &
|
---|
1605 | \begin{cfa}
|
---|
1606 | string_res accum;
|
---|
1607 | for ( ... ) {
|
---|
1608 | @accum = "";@ $\C[1in]{// reuse\CRT}$
|
---|
1609 | for ( ... )
|
---|
1610 | accum @+=@ ...
|
---|
1611 | }
|
---|
1612 | \end{cfa}
|
---|
1613 | \end{tabular}
|
---|
1614 | \end{cquote}
|
---|
1615 |
|
---|
1616 | Both programs are doing the same thing: start with @x@ empty and build it up by appending the same chunks.
|
---|
1617 | A programmer should not have to consider this difference.
|
---|
1618 | But from under the covers, each string being an individual allocation leaks through.
|
---|
1619 | While the inner loop is appending text to an @x@ that had not yet grown to have a large capacity, the program is, naturally, paying to extend the variable-length allocation, occasionally.
|
---|
1620 | This capacity stretching is a sticky property that survives assigning a (short, empty-string) value into an existing initialization.
|
---|
1621 | So, the ``reuse'' version benefits from not growing the allocation on subsequent runs of the inner loop.
|
---|
1622 | Yet, the ``fresh'' version is constantly restarting from a small buffer.
|
---|
1623 |
|
---|
1624 | \begin{figure}
|
---|
1625 | \centering
|
---|
1626 | \includegraphics{plot-string-peq-cppemu.pdf}
|
---|
1627 | % \includegraphics[width=\textwidth]{string-graph-peq-cppemu.png}
|
---|
1628 | \caption{Fresh vs Reuse in \CC, Emulation Baseline. Average time per iteration with one \lstinline{x += y} invocation (lower is better). Comparing \CFA's STL emulation mode with STL implementations, and comparing the ``fresh'' with ``reused'' reset styles.}
|
---|
1629 | \label{fig:string-graph-peq-cppemu}
|
---|
1630 | \end{figure}
|
---|
1631 |
|
---|
1632 | \VRef[Figure]{fig:string-graph-peq-cppemu} shows the resulting performance.
|
---|
1633 | The fresh \vs reuse penalty is the dominant difference.
|
---|
1634 | The cost is 40\% averaged over the cases shown and minimally 24\%.
|
---|
1635 | It shows up consistently on both the \CFA and STL implementations, and this cost is more prominent with larger strings.
|
---|
1636 |
|
---|
1637 | The lesser \CFA \vs STL difference shows \CFA reproducing STL's performance, up to a 15\% penalty averaged over the cases shown, diminishing with larger strings, and 50\% in the worst case.
|
---|
1638 | This penalty characterizes implementation fine tuning done with STL and not done yet done with \CFA.
|
---|
1639 |
|
---|
1640 |
|
---|
1641 | \subsubsection{\CFA's Fresh-Reuse Compromise}
|
---|
1642 |
|
---|
1643 | This comparison has the same setup as the last one, except that the \CFA implementation is switched to use its sharing mode. The outcome is that the fresh/reuse difference vanishes in \CFA, with \CFA consistently delivering performance that compromises between the two \CC cases.
|
---|
1644 |
|
---|
1645 | \begin{figure}
|
---|
1646 | \centering
|
---|
1647 | \includegraphics{string-graph-peq-sharing.pdf}
|
---|
1648 | % \includegraphics[width=\textwidth]{string-graph-peq-sharing.png}
|
---|
1649 | \caption{\CFA Compromise for Fresh \vs Reuse. Average time per iteration with one \lstinline{x += y} invocation (lower is better). Comparing \CFA's sharing mode with STL, and comparing the ``fresh'' with ``reused'' reset styles. The \CC results are repeated from \ref{fig:string-graph-peq-cppemu}.}
|
---|
1650 | \label{fig:string-graph-peq-sharing}
|
---|
1651 | \end{figure}
|
---|
1652 |
|
---|
1653 | \VRef[Figure]{fig:string-graph-peq-sharing} has the result.
|
---|
1654 | At append lengths 5 and above, \CFA not only splits the two STL cases, but its slowdown of 16\% over STL with user-managed reuse is close to the baseline \CFA-v-STL implementation difference seen with \CFA in STL-emulation mode.
|
---|
1655 |
|
---|
1656 |
|
---|
1657 | \subsubsection{\CFA's low overhead for misusing \lstinline{+}}
|
---|
1658 |
|
---|
1659 | A further pitfall occurs when the user writes @x = x + y@, rather than @x += y@. Again, they are logically equivalent.
|
---|
1660 | For numeric types, the generated code is equivalent, giving identical performance.
|
---|
1661 | However, for string types there can be a significant difference.
|
---|
1662 | This pitfall is a particularly likely hazard for beginners.
|
---|
1663 |
|
---|
1664 | In earlier experiments, the choice of \CFA API among HL and LL had no impact on the functionality being tested.
|
---|
1665 | Here, however, the @+@ operation, which returns its result by value, is only available in HL.
|
---|
1666 | The \CFA @+@ number was obtained by inlining the HL implementation of @+@, which is done using LL's @+=@, into the test harness, while omitting the HL-inherent extra dynamic allocation. The HL-upon-LL @+@ implementation, is:
|
---|
1667 | \begin{cfa}
|
---|
1668 | struct string {
|
---|
1669 | string_res * inner; // RAII manages malloc/free, simple ownership
|
---|
1670 | };
|
---|
1671 | void ?+=?( string & s, string s2 ) {
|
---|
1672 | (*s.inner) += (*s2.inner);
|
---|
1673 | }
|
---|
1674 | string @?+?@( string lhs, string rhs ) {
|
---|
1675 | string ret = lhs;
|
---|
1676 | ret @+=@ rhs;
|
---|
1677 | return ret;
|
---|
1678 | }
|
---|
1679 | \end{cfa}
|
---|
1680 | This @+@ implementation is also the goal implementation of @+@ once the HL/LL workaround is no longer needed. Inlining the induced LL steps into the test harness gives:
|
---|
1681 | \begin{cquote}
|
---|
1682 | \setlength{\tabcolsep}{20pt}
|
---|
1683 | \begin{tabular}{@{}ll@{}}
|
---|
1684 | \multicolumn{1}{c}{\textbf{Goal}} & \multicolumn{1}{c}{\textbf{Measured}} \\
|
---|
1685 | \begin{cfa}
|
---|
1686 |
|
---|
1687 | for ( ... ) {
|
---|
1688 | string accum;
|
---|
1689 | for ( ... ) {
|
---|
1690 | accum = @accum + ...@
|
---|
1691 |
|
---|
1692 |
|
---|
1693 | }
|
---|
1694 | }
|
---|
1695 | \end{cfa}
|
---|
1696 | &
|
---|
1697 | \begin{cfa}
|
---|
1698 | string_res pta_ll_temp;
|
---|
1699 | for ( ... ) {
|
---|
1700 | string_res accum;
|
---|
1701 | for ( ... ) {
|
---|
1702 | pta_ll_temp = @accum@;
|
---|
1703 | pta_ll_temp @+= ...@;
|
---|
1704 | accum = pta_ll_temp;
|
---|
1705 | }
|
---|
1706 | }
|
---|
1707 | \end{cfa}
|
---|
1708 | \end{tabular}
|
---|
1709 | \end{cquote}
|
---|
1710 | Note that this ``Goal'' code functions today in HL.
|
---|
1711 |
|
---|
1712 | \begin{figure}
|
---|
1713 | \centering
|
---|
1714 | \includegraphics{string-graph-pta-sharing.pdf}
|
---|
1715 | % \includegraphics[width=\textwidth]{string-graph-pta-sharing.png}
|
---|
1716 | \caption{CFA's low overhead for misusing \lstinline{+}. Average time per iteration with one \lstinline{x += y} invocation (lower is better). Comparing \CFA (having implicit sharing activated) with STL, and comparing the \lstinline{+}-then-\lstinline{=} with the \lstinline{+=} append styles. The \lstinline{+=} results are repeated from \VRef[Figure]{fig:string-graph-peq-sharing}.}
|
---|
1717 | \label{fig:string-graph-pta-sharing}
|
---|
1718 | \end{figure}
|
---|
1719 |
|
---|
1720 | \VRef[Figure]{fig:string-graph-pta-sharing} gives the outcome. The STL's penalty is $8 \times$ while \CFA's is only $2 \times$, averaged across the cases shown here.
|
---|
1721 | Moreover, the STL's gap increases with string size, while \CFA's converges.
|
---|
1722 | So again, \CFA helps users who just want to treat strings as values, and not think about the resource management under the covers.
|
---|
1723 |
|
---|
1724 | While not a design goal, and not graphed out, \CFA in STL-emulation mode heppened to outperform STL in this case. User-managed allocation reuse did not affect either implementation in this case; only ``fresh'' results are shown.
|
---|
1725 |
|
---|
1726 |
|
---|
1727 | \subsection{Test: Pass argument}
|
---|
1728 |
|
---|
1729 | STL has a penalty for passing a string by value, which forces users to think about memory management when communicating values with a function.
|
---|
1730 | The key \CFA value-add is that a user can think of a string simply as a value; this test shows that \CC charges a stiff penalty for thining this way, while \CFA does not.
|
---|
1731 | This test illustrates a main advantage of the \CFA sharing algorithm (in one case).
|
---|
1732 | It shows STL's small-string optimization providing a successful mitigation (in the other case).
|
---|
1733 |
|
---|
1734 | The basic operation considered is:
|
---|
1735 | \begin{cfa}
|
---|
1736 | void foo( string s );
|
---|
1737 | string s = "abc";
|
---|
1738 | foo( s );
|
---|
1739 | \end{cfa}
|
---|
1740 | With implicit sharing active, \CFA treats this operation as normal and supported.
|
---|
1741 |
|
---|
1742 | Again, an HL-LL difference requires an LL mockup. This time, the fact to integrate into the test harness is that LL does not directly support pass-by-value.
|
---|
1743 | \begin{cquote}
|
---|
1744 | \setlength{\tabcolsep}{20pt}
|
---|
1745 | \begin{tabular}{@{}ll@{}}
|
---|
1746 | \multicolumn{1}{c}{\textbf{Goal}} & \multicolumn{1}{c}{\textbf{Measured}} \\
|
---|
1747 | \begin{cfa}
|
---|
1748 | void helper( string q ) {
|
---|
1749 |
|
---|
1750 | }
|
---|
1751 | START_TIMER
|
---|
1752 | for ( i; ... ) {
|
---|
1753 | helper( corpus[ f(i) ] ); // imported from char * previously
|
---|
1754 | COUNT_ONE_OP_DONE
|
---|
1755 | }
|
---|
1756 | STOP_TIMER
|
---|
1757 | \end{cfa}
|
---|
1758 | &
|
---|
1759 | \begin{cfa}
|
---|
1760 | void helper( string_res & qref ) {
|
---|
1761 | string_res q = { qref, COPY_VALUE };
|
---|
1762 | }
|
---|
1763 | // rest same, elided
|
---|
1764 |
|
---|
1765 |
|
---|
1766 |
|
---|
1767 |
|
---|
1768 |
|
---|
1769 | \end{cfa}
|
---|
1770 | \end{tabular}
|
---|
1771 | \end{cquote}
|
---|
1772 | The Goal (HL) version gives the simplest sketch of the test harness.
|
---|
1773 | It uses a single level of looping.
|
---|
1774 | Each iteration uses a corpus item as the argument to a function call.
|
---|
1775 | These corpus items were imported to the string heap before beginning the timed run.
|
---|
1776 |
|
---|
1777 |
|
---|
1778 | \begin{figure}
|
---|
1779 | \centering
|
---|
1780 | \includegraphics{string-graph-pbv.pdf}
|
---|
1781 | % \includegraphics[width=\textwidth]{string-graph-pbv.png}
|
---|
1782 | \caption{Average time per iteration (lower is better) with one call to a function that takes a by-value string argument, comparing \CFA (having implicit sharing activated) with STL.
|
---|
1783 | (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.
|
---|
1784 | (b) With \emph{Fixed-size} corpus construction, in which this benefit applies exactly to strings with length below 16.
|
---|
1785 | [TODO: show version (b)]}
|
---|
1786 | \label{fig:string-graph-pbv}
|
---|
1787 | \end{figure}
|
---|
1788 |
|
---|
1789 |
|
---|
1790 | \VRef[Figure]{fig:string-graph-pbv} shows the costs for calling a function that receives a string argument by value.
|
---|
1791 | STL's performance worsens as string length increases, while \CFA has the same performance at all sizes.
|
---|
1792 |
|
---|
1793 | While improved, the \CFA cost to pass a string is still nontrivial.
|
---|
1794 | The contributor is adding and removing the callee's string handle from the global list.
|
---|
1795 | 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, upon a \CFA realization of this optimization.
|
---|
1796 | 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.
|
---|
1797 | \PAB{Need to check that. Expecting copying to dominate.}
|
---|
1798 |
|
---|
1799 |
|
---|
1800 | \subsection{Test: Allocate}
|
---|
1801 |
|
---|
1802 | This test directly compares the allocation schemes of the \CFA string (sharing active), with the STL string.
|
---|
1803 | It treats the \CFA scheme as a form of garbage collection (GC), and the STL scheme as an application of malloc-free.
|
---|
1804 | The test shows that \CFA enables faster speed in exchange for greater memory usage.
|
---|
1805 |
|
---|
1806 | A garbage collector, afforded the freedom of managed memory (where it knows about all the pointers and is allowed to modify them), often runs faster than malloc-free in an amortized analysis, even though it must occasionally stop to collect.
|
---|
1807 | The sppedup happens because GC is able to use its collection time to move objects.
|
---|
1808 | (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'' bookkeeping for such a scheme is very light.
|
---|
1809 | 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 bookkeeping of maintaining a linked structure of freed allocations and/or coalescing freed allocations.
|
---|
1810 |
|
---|
1811 | A garbage collector keeps allocations around for longer than the using program can reach them.
|
---|
1812 | By contrast, a program (correctly) using malloc-free releases allocations exactly when they are no longer reachable.
|
---|
1813 | Therefore, the same harness will use more memory while running under garbage collection.
|
---|
1814 | A garbage collector can minimize the memory overhead by searching for these dead allocations aggressively, that is, by collecting more often.
|
---|
1815 | Tuned in this way, it spends a lot of time collecting, easily so much as to overwhelm its speed advantage from bump-pointer allocation.
|
---|
1816 | 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.
|
---|
1817 |
|
---|
1818 | [TODO: find citations for the above knowledge]
|
---|
1819 |
|
---|
1820 | The speed for memory tradeoff is, therefore, standard for comparisons like \CFA--STL string allocations.
|
---|
1821 | The test verifies that it is so and quantifies the returns available.
|
---|
1822 |
|
---|
1823 | These tests manipulate a tuning knob that controls how much extra space to use.
|
---|
1824 | Specific values of this knob are not user-visible and are not presented in the results here.
|
---|
1825 | Instead, its two effects (amount of space used and time per operation) are shown.
|
---|
1826 | 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.
|
---|
1827 | The allocator will expand its text buffer during a collection if the actual fraction live exceeds this target.
|
---|
1828 |
|
---|
1829 | \begin{figure}
|
---|
1830 | \centering
|
---|
1831 | \includegraphics{string-perf-alloc.pdf}
|
---|
1832 | \caption{}
|
---|
1833 | \label{fig:string-perf-alloc}
|
---|
1834 | \end{figure}
|
---|
1835 |
|
---|
1836 | \VRef[Figure]{fig:string-perf-alloc} shows the memory-allocation pattern that the test harness drives.
|
---|
1837 | Note how this pattern creates few long-lived strings and many short-lived strings.
|
---|
1838 | To extend this pattern to the scale of a performance test, the harness is actually recursive, to achieve the nesting (three levels in the figure), and iterative, to achieve the fan-out (factor two / binary tree in the figure).
|
---|
1839 | \begin{cfa}
|
---|
1840 | void f( int depth ) {
|
---|
1841 | if (depth == 0) return;
|
---|
1842 | string_res x = corpus[...]; // importing from char * here
|
---|
1843 | COUNT_ONE_OP_DONE
|
---|
1844 | for ( fanOut ) {
|
---|
1845 | // elided: terminate fixed-duration experiment
|
---|
1846 | f( depth - 1 );
|
---|
1847 | }
|
---|
1848 | }
|
---|
1849 | START_TIMER
|
---|
1850 | f( launchDepth );
|
---|
1851 | STOP_TIMER
|
---|
1852 | \end{cfa}
|
---|
1853 | The runs presented use 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.
|
---|
1854 | This sizing keeps an average of 836 strings live.
|
---|
1855 | This sizing was chosen to keep the amounts of consumed memory within the machine's last-level cache.
|
---|
1856 |
|
---|
1857 | The time measurement is of nanoseconds per such @f@-call, be it internal or leaf.
|
---|
1858 |
|
---|
1859 | % 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.
|
---|
1860 |
|
---|
1861 |
|
---|
1862 | \begin{figure}
|
---|
1863 | \centering
|
---|
1864 | \includegraphics{string-graph-allocn.pdf}
|
---|
1865 | % \includegraphics[width=\textwidth]{string-graph-allocn.png}
|
---|
1866 | \caption{Space and time performance, under varying fraction-live targets, for the five string lengths shown, at \emph{Fixed-size} corpus construction.
|
---|
1867 | [MISSING] The identified clusters are for the default fraction-live target, which is 30\%.
|
---|
1868 | MISSING: STL results, typically just below the 0.5--0.9 \CFA segment.
|
---|
1869 | }
|
---|
1870 | \label{fig:string-graph-allocn}
|
---|
1871 | \end{figure}
|
---|
1872 |
|
---|
1873 | \VRef[Figure]{fig:string-graph-allocn} shows the results of this experiment.
|
---|
1874 | At all string sizes, varying the liveness threshold gives speed-for-space tradeoffs relative to STL.
|
---|
1875 | At the default liveness threshold, all measured string sizes see a ??\%--??\% speedup for a ??\%--??\% increase in memory footprint.
|
---|
1876 |
|
---|
1877 |
|
---|
1878 | % \subsection{Test: Normalize}
|
---|
1879 |
|
---|
1880 | % This test is more applied than the earlier ones.
|
---|
1881 | % It combines the effects of several operations.
|
---|
1882 | % 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.
|
---|
1883 |
|
---|
1884 | % To motivate: edits being rare
|
---|
1885 |
|
---|
1886 | % The program is doing a specialized find-replace operation on a large body of text.
|
---|
1887 | % In the program under test, the replacement is just to erase a magic character.
|
---|
1888 | % 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.
|
---|
1889 | % The challenge is to apply this packaged function across chunks taken from the large body.
|
---|
1890 | % Using the \CFA string library, the most natural way to write the helper module's function also works well in the adapted context.
|
---|
1891 | % 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.
|
---|
1892 |
|
---|
1893 | \begin{lstlisting}
|
---|
1894 | void processItem( string & item ) {
|
---|
1895 | // find issues in item and fix them
|
---|
1896 | }
|
---|
1897 | \end{lstlisting}
|
---|