Changeset e1107ec for doc/theses/mike_brooks_MMath
- Timestamp:
- Nov 7, 2024, 10:11:31 AM (6 weeks ago)
- Branches:
- master
- Children:
- 04c6340
- Parents:
- 174a11a
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/theses/mike_brooks_MMath/array.tex
r174a11a re1107ec 1091 1091 1092 1092 1093 1094 1093 \section{Array lifecycle} 1095 1094 1096 An array is an aggregate, like a struct .1097 Both wrapsubordinate objects.1098 An arbitrary other type, like @string@, can be used as a struct member or an array element.1099 When it is, that type's lifecycle assumptions must be respected.1100 In the case of @string@, it would be disasterous to start calling string-management functions on uninitialized storage.1101 The @string@ type provides its constructors and destructor; 1102 \CFA is supposed to provide a reasonable assurance that they will be called, even if the string is wrapped in something else, even if the user-programmer isn't thinking about it.1103 1104 Preexisting \CFA mechanisms achieve this requirement in correctness, but not with reasonable performance.1105 Furthermore, advanced users of arrays need an exception to the bluntly-stated requirement, which does not come up when workingwith other aggregates.1106 So the \CFA array has subtleties in its support for element lifecycle.1107 1108 The preexisting \CFA support for contained-element lifecycle is based on a recursive implementation of the object-type ( otype) pseudo-trait.1109 A type is an otype if it provides a parameterless constructor, copy constructor, assignment operator and destructor.1110 When declaring a struct whose members are all otypes, the compiler generates implementations of the four otype functions for the outer struct.1111 The generated parameterless constructor for the outer struct calls the parameterless constructor for each memeber, and the other otypefunctions work similarly.1112 For a member that is a C array, these calls occur in loops, which work correctly for dynamic bounds.1113 This logic works the same, whether the member is a concrete type (that happens to be an otype) or if the member is a polymorphic type variable asserted to be an otype (which is implicit in the defaultsyntax, @forall(T)@).1095 An array is an aggregate, like a structure; 1096 both are containers wrapping subordinate objects. 1097 Any arbitrary object type, like @string@, can be an array element or structure member. 1098 A consequence is that the lifetime of the container must match with its subordinate objects. 1099 For example, all elements and members must be initialized/uninitialized implicitly as part of the container's allocation/deallocation. 1100 Modern programming languages implicitly perform these operations via a type's constructor and destructor. 1101 Therefore, \CFA must assure these lifetime operations are called, regardless of the lexical nesting depth of containers and their components. 1102 1103 Preexisting \CFA mechanisms achieve this requirement, but with poor performance (discussed shortly). 1104 Furthermore, advanced array users need an exception to the basic mechanism, which does not occur with other aggregates. 1105 Hence, there are subtleties in supporting an element's lifecycle with arrays. 1106 1107 The preexisting \CFA support for contained-element lifecycle is based on a recursive implementation of the object-type (@otype@) pseudo-trait. 1108 A type is an @otype@, if it provides a parameterless (default) constructor, copy constructor, assignment operator, and destructor (like \CC). 1109 When declaring a structure with @otype@ members, the compiler implicitly generates implementations of the four @otype@ functions for the outer structure. 1110 Then the generated default constructor for the outer structure calls the default constructor for each member, and the other @otype@ functions work similarly. 1111 For a member that is a C array, these calls occur in a loop for each array element, which even for VLAs. 1112 This logic works the same, whether the member is a concrete type (that happens to be an @otype@) or if the member is a polymorphic type asserted to be an @otype@ (which is implicit in the syntax, @forall(T)@). 1114 1113 The \CFA array has the simplified form (similar to one seen before): 1115 1114 \begin{cfa} 1116 forall( T * ) // non-otype element, no lifecycle functions1115 forall( T * ) // non-otype element, no lifecycle functions 1117 1116 // forall( T ) // otype element, lifecycle functions asserted 1118 1117 struct array5 { … … 1120 1119 }; 1121 1120 \end{cfa} 1122 Being a struct with a C-array member, using the otype-form declaration for @T@ causes @array5(float)@ to implement otypetoo.1123 1124 But this otype-recursion pattern has a performance issue.1125 A cube of @float@ is, in the running simplification:1121 Being a structure with a C-array member, using the otype-form declaration for @T@ causes @array5(float)@ to implement @otype@ too. 1122 1123 But this @otype@-recursion pattern has a performance issue. 1124 For example, in a cube of @float@: 1126 1125 \begin{cfa} 1127 1126 array5( array5( array5( float ) ) ) 1128 1127 \end{cfa} 1129 Its lifecycle functions, under the otype-recursion pattern, would be: 1130 \begin{cfa} 1131 TODO: rework this crude exposition into a readable figure 1132 1128 its lifecycle functions, under the @otype@-recursion pattern, is shown in \VRef[Figure]{}, where 1129 \begin{cfa} 1130 array5(array5(array5(float))) 1131 \end{cfa} 1132 has 256 leaves. 1133 1134 %array5(T) offers 1135 %1 parameterless ctor, which asks for T to have 1136 % 1 parameterless ctor 1137 % 2 copy ctor 1138 % 3 asgt 1139 % 4 dtor 1140 %2 copy ctor, which asks for T to have 1141 % 1 parameterless ctor 1142 % 2 copy ctor 1143 % 3 asgt 1144 % 4 dtor 1145 %3 asgt, which asks for T to have 1146 % 1 parameterless ctor 1147 % 2 copy ctor 1148 % 3 asgt 1149 % 4 dtor 1150 %4 dtor, which asks for T to have 1151 % 1 parameterless ctor 1152 % 2 copy ctor 1153 % 3 asgt 1154 % 4 dtor 1155 1156 \begin{figure} 1157 \centering 1158 \setlength{\tabcolsep}{15pt} 1159 \begin{tabular}{@{}lll@{}} 1160 \begin{cfa}[deletekeywords={default}] 1133 1161 float offers 1134 1 parameterlessctor1162 1 default ctor 1135 1163 2 copy ctor 1136 1164 3 asgt 1137 1165 4 dtor 1138 1166 1139 array5(T) offers 1140 1 parameterless ctor, which asks for T to have 1141 1 parameterless ctor 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 \end{cfa} 1191 & 1192 \begin{cfa}[deletekeywords={default}] 1193 array5(float) has 1194 1 default ctor, using float's 1195 1 default ctor 1142 1196 2 copy ctor 1143 1197 3 asgt 1144 1198 4 dtor 1145 2 copy ctor, which asks for T to have1146 1 parameterlessctor1199 2 copy ctor, using float's 1200 1 default ctor 1147 1201 2 copy ctor 1148 1202 3 asgt 1149 1203 4 dtor 1150 3 asgt, which asks for T to have1151 1 parameterlessctor1204 3 asgt, using float's 1205 1 default ctor 1152 1206 2 copy ctor 1153 1207 3 asgt 1154 1208 4 dtor 1155 4 dtor, which asks for T to have1156 1 parameterlessctor1209 4 dtor, using float's 1210 1 default ctor 1157 1211 2 copy ctor 1158 1212 3 asgt 1159 1213 4 dtor 1160 1214 1161 array5(float) has 1162 1 parameterless ctor, which gets float's 1163 1 parameterless ctor 1164 2 copy ctor 1165 3 asgt 1166 4 dtor 1167 2 copy ctor, which gets float's 1168 1 parameterless ctor 1169 2 copy ctor 1170 3 asgt 1171 4 dtor 1172 3 asgt, which gets float's 1173 1 parameterless ctor 1174 2 copy ctor 1175 3 asgt 1176 4 dtor 1177 4 dtor, which gets float's 1178 1 parameterless ctor 1179 2 copy ctor 1180 3 asgt 1181 4 dtor 1182 1215 1216 1217 1218 1219 1220 1221 1222 \end{cfa} 1223 & 1224 \begin{cfa}[deletekeywords={default}] 1183 1225 array5(array5(float)) has 1184 1 parameterless ctor, which getsarray5(float)'s1185 1 parameterless ctor, which getsfloat's1186 1 parameterlessctor1226 1 default ctor, using array5(float)'s 1227 1 default ctor, using float's 1228 1 default ctor 1187 1229 2 copy ctor 1188 1230 3 asgt 1189 1231 4 dtor 1190 2 copy ctor, which getsfloat's1191 1 parameterlessctor1232 2 copy ctor, using float's 1233 1 default ctor 1192 1234 2 copy ctor 1193 1235 3 asgt 1194 1236 4 dtor 1195 3 asgt, which getsfloat's1196 1 parameterlessctor1237 3 asgt, using float's 1238 1 default ctor 1197 1239 2 copy ctor 1198 1240 3 asgt 1199 1241 4 dtor 1200 4 dtor, which getsfloat's1201 1 parameterlessctor1242 4 dtor, using float's 1243 1 default ctor 1202 1244 2 copy ctor 1203 1245 3 asgt 1204 1246 4 dtor 1205 2 copy ctor, which getsarray5(float)'s1247 2 copy ctor, using array5(float)'s 1206 1248 ... 4 children, 16 leaves 1207 3 asgt, which getsarray5(float)'s1249 3 asgt, using array5(float)'s 1208 1250 ... 4 children, 16 leaves 1209 4 dtor, which getsarray5(float)'s1251 4 dtor, using array5(float)'s 1210 1252 ... 4 children, 16 leaves 1211 1253 (64 leaves) 1212 1213 array5(array5(array5(float))) has 1214 ...(256 leaves) 1215 \end{ cfa}1254 \end{cfa} 1255 \end{tabular} 1256 \caption{write a caption} 1257 \end{figure} 1216 1258 1217 1259 Each xxx corresponds to a generated thunk. 1218 So the otype-recursion pattern generates a quantity of thunks that is exponential in the number of dimensions. 1219 Anecdotal experience with this solutuion found the resulting compile times annoying at three dimensions, and unusable at four. 1220 1221 But the otype-recursion pattern uses more assertions than are really needed. 1222 Consider how @array5(float)@'s parameterless constructor is getting all four lifecycle assertions about the element type, @float@. 1223 It only needs @float@'s parameterless constructor; it is getting a destructor (among others) and never using it. 1224 The same idea applies to the copy constructor and the destructor. 1225 For the assignment operator, it turns out that today's RAII pattern has it using a copy constructor, assignment operator and destructor. 1226 Concurrent work on the \CFA team aims to improve this whole situation. 1227 A workaround is needed for now. 1228 1229 The workaround is to provide parameterless constructor, copy constructor and destructor, defined with lean, bespoke assertions: 1230 1231 \noindent 1260 So the @otype@-recursion pattern generates a quantity of thunks that is exponential in the number of dimensions. 1261 Anecdotal experience with this solution found the resulting compile times annoyingly slow at three dimensions, and unusable at four. 1262 1263 The issue is that the otype-recursion pattern uses more assertions than needed. 1264 Consider how @array5(float)@'s default constructor is getting all four lifecycle assertions about the element type, @float@. 1265 It only needs @float@'s default constructor; 1266 all the operations are never used. 1267 %For the assignment operator, it turns out that today's RAII pattern has it using a copy constructor, assignment operator and destructor. 1268 Current by the \CFA team aims to improve this situation. 1269 Therefore, a workaround is needed for now. 1270 1271 The workaround is to provide a default constructor, copy constructor and destructor, defined with lean, bespoke assertions: 1272 \begin{cquote} 1232 1273 \begin{tabular}{@{}l@{\hspace{0.5in}}l@{}} 1233 1274 \begin{cfa} … … 1263 1304 \end{cfa} 1264 1305 \end{tabular} 1306 \end{cquote} 1265 1307 Moreover, the assignment operator is skipped, because array assignment is not a common operation. 1266 1308 This way, the critical lifecycle functions are available, with no growth in thunk creation. 1267 1309 1268 Finally, the intuition that a programmer using an array always wants element constructorscalled \emph{automatically} is simplistic.1310 Finally, the intuition that a programmer using an array always wants element's default constructor called \emph{automatically} is simplistic. 1269 1311 Arrays exist to store different values at each position. 1270 So, an array initialization, in general, needs to let the prorammer provide different constructor arguments to each element. 1271 \begin{cfa} 1272 thread worker; 1273 void ?{}( worker & ) = void; 1274 void ?{}( worker &, int worker_num ); 1275 array(worker, 5) ws @= ???@; 1276 for (i; 5) ( ws[i] ){ @i@ }; 1277 \end{cfa} 1312 So, array initialization needs to let the programmer provide different constructor arguments to each element. 1313 \begin{cfa} 1314 thread worker { int id; }; 1315 void ?{}( worker & ) = void; // remove default constructor 1316 void ?{}( worker &, int id ); 1317 array( worker, 5 ) ws = {}; // no initialization 1318 for (i; 5) (ws[i]){ @i@ }; // explicitly initialize each thread's id 1319 \end{cfa} 1320 Note the ability to explicitly call a constructor in \CFA, unlike in \CC. 1278 1321 1279 1322 Two \CFA features may, at first, seem viable for initializing the array @ws@, but on closer inspection, they are not. 1280 1323 \begin{itemize} 1281 1324 \item 1282 Empty initializer / default constructor: \lstinline{array(worker, 5) ws;}.1283 Th is option does not work with the semantics of a thread: it has forked as soon as the constructor returns.1284 The type does not provide a parameterless constructor because it is not possible to fork the thread until the @worker_num@ parameter is received.1285 \item 1286 C initialization: \lstinline {array(worker, 5) ws AT EQUALS OPEN CLOSE;} (TODO: typeset at-equals).1325 Empty initializer / elided default constructor. 1326 The problem is that a thread type needs to fork a thread at the \emph{end} of each constructor and join with a thread at the \emph{start} of each destructor. 1327 Therefore, there is a conflict between the implicit actions by a builtin @thread@ type and a user's desire to remove actions with respect to allocation/deallocation. 1328 \item 1329 C initialization: \lstinline|array(worker, 5) ws @= {};|, which ignores all \CFA initialization and uses C empty initialization. 1287 1330 This option does achieve the desired semantics on the construction side. 1288 But destruction side, the desired semantics is for implicit destructor calls to continue, because with threads, the destructor is ajoin operation.1289 C initialization disables \emph{all} implicit lifecycle management; here, the goal is to disable only the implicit construction.1331 But on destruction side, the desired semantics is for implicit destructor calls to continue for the join operation. 1332 C initialization disables \emph{all} implicit lifecycle management; whereas, the goal is to disable only the implicit construction. 1290 1333 \end{itemize} 1291 1334 1292 So, I enhanced the \CFA standard library to provide the missing semantics, available in either form:1293 \begin{cfa} 1294 array( @uninit@(worker), 5) ws1;1295 array( worker, 5) ws2 = { @delay_init@ };1335 To fix this problem, I enhanced the \CFA standard library to provide the missing semantics, available in either form: 1336 \begin{cfa} 1337 array( @uninit@(worker), 5 ) ws1; 1338 array( worker, 5) ws2 = { @delay_init@ }; 1296 1339 \end{cfa} 1297 1340 Both cause the @ws@-construction-time implicit call chain to stop before reaching a @worker@ constructor, while leaving the implicit destruction calls intact. 1298 1341 Two forms are available, to parallel the development of this feature in \uCpp. 1299 Originally \uCpp offered only the @ws1@ form, there with the class-tempate @uNoCtor@ replacing\CFA's @uninit@.1300 More recently, \uCpp got a declaration-helping macro, @uArray@, whoseusage similar to the @ws2@ case.1301 Based on results of piloting @uArray@ (as a replacement of @uNoCtor@), future work may choose to remove one of the options.1342 Originally \uCpp offered only the @ws1@ form, using the class-template @uNoCtor@ equivalent to \CFA's @uninit@. 1343 More recently, \uCpp was extended with declaration macro, @uArray@, with usage similar to the @ws2@ case. 1344 Based on experience piloting @uArray@ as a replacement of @uNoCtor@, it might be possible to remove the first option. 1302 1345 1303 1346 % note to Mike, I have more fragments on some details available in push24\fragments\uNoCtor.txt
Note: See TracChangeset
for help on using the changeset viewer.