source: doc/theses/andrew_beach_MMath/performance.tex @ 6aa84e0

Last change on this file since 6aa84e0 was 6aa84e0, checked in by Andrew Beach <ajbeach@…>, 2 months ago

Andrew MMath: Removed (updated one) the remaining \todo items.

  • Property mode set to 100644
File size: 19.2 KB
4Performance is of secondary importance for most of this project.
5Instead, the focus was to get the features working. The only performance
6requirement is to ensure the tests for correctness run in a reasonable
7amount of time. Hence, only a few basic performance tests were performed to
8check this requirement.
10\section{Test Set-Up}
11Tests were run in \CFA, C++, Java and Python.
12In addition there are two sets of tests for \CFA,
13one with termination and one with resumption.
15GCC C++ is the most comparable language because both it and \CFA use the same
16framework, libunwind.
17In fact, the comparison is almost entirely in quality of implementation.
18Specifically, \CFA's EHM has had significantly less time to be optimized and
19does not generate its own assembly. It does have a slight advantage in that
20\Cpp has to do some extra bookkeeping to support its utility functions,
21but otherwise \Cpp should have a significant advantage.
23Java, a popular language with similar termination semantics,
24is implemented in a very different environment, a virtual machine with
25garbage collection.
26It also implements the finally clause on try blocks allowing for a direct
27feature-to-feature comparison.
28As with \Cpp, Java's implementation is mature, has more optimizations
29and extra features as compared to \CFA.
31Python is used as an alternative comparison because of the \CFA EHM's
32current performance goals, which is to not be prohibitively slow while the
33features are designed and examined. Python has similar performance goals for
34creating quick scripts and its wide use suggests it has achieved those goals.
36Unfortunately, there are no notable modern programming languages with
37resumption exceptions. Even the older programming languages with resumption
38seem to be notable only for having resumption.
39Instead, resumption is compared to its simulation in other programming
40languages: fixup functions that are explicitly passed into a function.
42All tests are run inside a main loop that repeatedly performs a test.
43This approach avoids start-up or tear-down time from
44affecting the timing results.
45The number of times the loop is run is configurable from the command line;
46the number used in the timing runs is given with the results per test.
47The Java tests run the main loop 1000 times before
48beginning the actual test to ``warm up" the JVM.
49% All other languages are precompiled or interpreted.
51Timing is done internally, with time measured immediately before and
52after the test loop. The difference is calculated and printed.
53The loop structure and internal timing means it is impossible to test
54unhandled exceptions in \Cpp and Java as that would cause the process to
56Luckily, performance on the ``give up and kill the process" path is not
59The exceptions used in these tests are always based off of
60the base exception for the language.
61This requirement minimizes performance differences based
62on the object model used to represent the exception.
64All tests are designed to be as minimal as possible, while still preventing
65excessive optimizations.
66For example, empty inline assembly blocks are used in \CFA and \Cpp to
67prevent excessive optimizations while adding no actual work.
69% We don't use catch-alls but if we did:
70% Catch-alls are done by catching the root exception type (not using \Cpp's
71% \code{C++}{catch(...)}).
73When collecting data, each test is run eleven times. The top three and bottom
74three results are discarded and the remaining five values are averaged.
75The test are run with the latest (still pre-release) \CFA compiler,
76using gcc-10 10.3.0 as a backend.
77g++-10 10.3.0 is used for \Cpp.
78Java tests are complied and run with Oracle OpenJDK version 11.0.11.
79Python used CPython version 3.8.10.
80The machines used to run the tests are:
82\item ARM 2280 Kunpeng 920 48-core 2$\times$socket
83      \lstinline{@} 2.6 GHz running Linux v5.11.0-25
84\item AMD 6380 Abu Dhabi 16-core 4$\times$socket
85      \lstinline{@} 2.5 GHz running Linux v5.11.0-25
87These represent the two major families of hardware architecture.
90The following tests were selected to test the performance of different
91components of the exception system.
92They should provide a guide as to where the EHM's costs are found.
94\paragraph{Stack Traversal}
95This group of tests measures the cost of traversing the stack
96(and in termination, unwinding it).
97Inside the main loop is a call to a recursive function.
98This function calls itself F times before raising an exception.
99F is configurable from the command line, but is usually 100.
100This builds up many stack frames, and any contents they may have,
101before the raise.
102The exception is always handled at the base of the stack.
103For example the Empty test for \CFA resumption looks like:
105void unwind_empty(unsigned int frames) {
106        if (frames) {
107                unwind_empty(frames - 1);
108        } else {
109                throwResume (empty_exception){&empty_vt};
110        }
113Other test cases have additional code around the recursive call adding
114something besides simple stack frames to the stack.
115Note that both termination and resumption have to traverse over
116the stack but only termination has to unwind it.
118% \item None:
119% Reuses the empty test code (see below) except that the number of frames
120% is set to 0 (this is the only test for which the number of frames is not
121% 100). This isolates the start-up and shut-down time of a throw.
122\item Empty:
123The repeating function is empty except for the necessary control code.
124As other traversal tests add to this, it is the baseline for the group
125as the cost comes from traversing over and unwinding a stack frame
126that has no other interactions with the exception system.
127\item Destructor:
128The repeating function creates an object with a destructor before calling
130Comparing this to the empty test gives the time to traverse over and
131unwind a destructor.
132\item Finally:
133The repeating function calls itself inside a try block with a finally clause
135Comparing this to the empty test gives the time to traverse over and
136unwind a finally clause.
137\item Other Handler:
138The repeating function calls itself inside a try block with a handler that
139does not match the raised exception, but is of the same kind of handler.
140This means that the EHM has to check each handler, and continue
141over all of them until it reaches the base of the stack.
142Comparing this to the empty test gives the time to traverse over and
143unwind a handler.
146\paragraph{Cross Try Statement}
147This group of tests measures the cost for setting up exception handling,
148if it is
149not used because the exceptional case did not occur.
150Tests repeatedly cross (enter, execute and leave) a try statement but never
151perform a raise.
153\item Handler:
154The try statement has a handler (of the appropriate kind).
155\item Finally:
156The try statement has a finally clause.
159\paragraph{Conditional Matching}
160This group measures the cost of conditional matching.
161Only \CFA implements the language level conditional match,
162the other languages mimic it with an ``unconditional" match (it still
163checks the exception's type) and conditional re-raise if it is not supposed
164to handle that exception.
166Here is the pattern shown in \CFA and \Cpp. Java and Python use the same
167pattern as \Cpp, but with their own syntax.
171try {
172        ...
173} catch (exception_t * e ;
174                should_catch(e)) {
175        ...
181try {
182        ...
183} catch (std::exception & e) {
184        if (!should_catch(e)) throw;
185        ...
190\item Match All:
191The condition is always true. (Always matches or never re-raises.)
192\item Match None:
193The condition is always false. (Never matches or always re-raises.)
196\paragraph{Resumption Simulation}
197A slightly altered version of the Empty Traversal test is used when comparing
198resumption to fix-up routines.
199The handler, the actual resumption handler or the fix-up routine,
200always captures a variable at the base of the loop,
201and receives a reference to a variable at the raise site, either as a
202field on the exception or an argument to the fix-up routine.
203% I don't actually know why that is here but not anywhere else.
205%\section{Cost in Size}
206%Using exceptions also has a cost in the size of the executable.
207%Although it is sometimes ignored
209%There is a size cost to defining a personality function but the later problem
210%is the LSDA which will be generated for every function.
212%(I haven't actually figured out how to compare this, probably using something
213%related to -fexceptions.)
216% First, introduce the tables.
220show the test results.
221In cases where a feature is not supported by a language, the test is skipped
222for that language and the result is marked N/A.
223There are also cases where the feature is supported but measuring its
224cost is impossible. This happened with Java, which uses a JIT that optimizes
225away the tests and cannot be stopped.\cite{Dice21}
226These tests are marked N/C.
227To get results in a consistent range (1 second to 1 minute is ideal,
228going higher is better than going low) N, the number of iterations of the
229main loop in each test, is varied between tests. It is also given in the
230results and has a value in the millions.
232An anomaly in some results came from \CFA's use of GCC nested functions.
233These nested functions are used to create closures that can access stack
234variables in their lexical scope.
235However, if they do so, then they can cause the benchmark's run time to
236increase by an order of magnitude.
237The simplest solution is to make those values global variables instead
238of function-local variables.
239% Do we know if editing a global inside nested function is a problem?
240Tests that had to be modified to avoid this problem have been marked
241with a ``*'' in the results.
243% Now come the tables themselves:
244% You might need a wider window for this.
248\caption{Termination Performance Results (sec)}
250\begin{tabular}{|r|*{2}{|r r r r|}}
252                       & \multicolumn{4}{c||}{AMD}         & \multicolumn{4}{c|}{ARM}  \\
254N\hspace{8pt}          & \multicolumn{1}{c}{\CFA} & \multicolumn{1}{c}{\Cpp} & \multicolumn{1}{c}{Java} & \multicolumn{1}{c||}{Python} &
255                         \multicolumn{1}{c}{\CFA} & \multicolumn{1}{c}{\Cpp} & \multicolumn{1}{c}{Java} & \multicolumn{1}{c|}{Python} \\
257Empty Traversal (1M)   & 23.0  & 9.6   & 17.6  & 23.4      & 30.6  & 13.6  & 15.5  & 14.7  \\
258D'tor Traversal (1M)   & 48.1  & 23.5  & N/A   & N/A       & 64.2  & 29.2  & N/A   & N/A   \\
259Finally Traversal (1M) & 3.2*  & N/A   & 17.6  & 29.2      & 3.9*  & N/A   & 15.5  & 19.0  \\
260Other Traversal (1M)   & 3.3*  & 23.9  & 17.7  & 32.8      & 3.9*  & 24.5  & 15.5  & 21.6  \\
261Cross Handler (1B)     & 6.5   & 0.9   & N/C   & 38.0      & 9.6   & 0.8   & N/C   & 32.1  \\
262Cross Finally (1B)     & 0.8   & N/A   & N/C   & 44.6      & 0.6   & N/A   & N/C   & 37.3  \\
263Match All (10M)        & 30.5  & 20.6  & 11.2  & 3.9       & 36.9  & 24.6  & 10.7  & 3.1   \\
264Match None (10M)       & 30.6  & 50.9  & 11.2  & 5.0       & 36.9  & 71.9  & 10.7  & 4.1   \\
271\caption{Resumption Performance Results (sec)}
276                        & AMD     & ARM  \\
278Empty Traversal (10M)   & 1.4     & 1.2  \\
279D'tor Traversal (10M)   & 1.8     & 1.0  \\
280Finally Traversal (10M) & 1.8     & 1.0  \\
281Other Traversal (10M)   & 22.6    & 25.8 \\
282Cross Handler (1B)      & 9.0     & 11.9 \\
283Match All (100M)        & 2.3     & 3.2  \\
284Match None (100M)       & 3.0     & 3.8  \\
292\caption{Resumption/Fixup Routine Comparison (sec)}
295\begin{tabular}{|r|*{2}{|r r r r r|}}
297            & \multicolumn{5}{c||}{AMD}     & \multicolumn{5}{c|}{ARM}  \\
299N\hspace{8pt}       & \multicolumn{1}{c}{Raise} & \multicolumn{1}{c}{\CFA} & \multicolumn{1}{c}{\Cpp} & \multicolumn{1}{c}{Java} & \multicolumn{1}{c||}{Python} &
300              \multicolumn{1}{c}{Raise} & \multicolumn{1}{c}{\CFA} & \multicolumn{1}{c}{\Cpp} & \multicolumn{1}{c}{Java} & \multicolumn{1}{c|}{Python} \\
302Resume Empty (10M)  & 1.4 & 1.4 & 15.4 & 2.3 & 178.0  & 1.2 & 1.2 & 8.9 & 1.2 & 118.4 \\
307% Now discuss the results in the tables.
308One result not directly related to \CFA but important to keep in mind is that,
309for exceptions, the standard intuition about which languages should go
310faster often does not hold.
311For example, there are a few cases where Python out-performs
312\CFA, \Cpp and Java.
313% To be exact, the Match All and Match None cases.
314The most likely explination is that,
315the generally faster languages have made ``common cases fast" at the expense
316of the rarer cases. Since exceptions are considered rare, they are made
317expensive to help speed up common actions, such as entering and leaving try
319Python on the other hand, while generally slower than the other languages,
320uses exceptions more and has not scarified their performance.
321In addition, languages with high-level representations have a much
322easier time scanning the stack as there is less to decode.
324As stated,
325the performance tests are not attempting to show \CFA has a new competitive
326way of implementing exception handling.
327The only performance requirement is to insure the \CFA EHM has reasonable
328performance for prototyping.
329Although that may be hard to exactly quantify, I believe it has succeeded
330in that regard.
331Details on the different test cases follow.
333\subsection{Termination \texorpdfstring{(\autoref{t:PerformanceTermination})}{}}
336\item[Empty Traversal]
337\CFA is slower than \Cpp, but is still faster than the other languages
338and closer to \Cpp than other languages.
339This result is to be expected,
340as \CFA is closer to \Cpp than the other languages.
342\item[D'tor Traversal]
343Running destructors causes a huge slowdown in the two languages that support
344them. \CFA has a higher proportionate slowdown but it is similar to \Cpp's.
345Considering the amount of work done in destructors is effectively zero
346(an assembly comment), the cost
347must come from the change of context required to run the destructor.
349\item[Finally Traversal]
350Performance is similar to Empty Traversal in all languages that support finally
351clauses. Only Python seems to have a larger than random noise change in
352its run time and it is still not large.
353Despite the similarity between finally clauses and destructors,
354finally clauses seem to avoid the spike that run time destructors have.
355Possibly some optimization removes the cost of changing contexts.
357\item[Other Traversal]
358For \Cpp, stopping to check if a handler applies seems to be about as
359expensive as stopping to run a destructor.
360This results in a significant jump.
362Other languages experience a small increase in run time.
363The small increase likely comes from running the checks,
364but they could avoid the spike by not having the same kind of overhead for
365switching to the check's context.
367\item[Cross Handler]
368Here, \CFA falls behind \Cpp by a much more significant margin.
369This is likely due to the fact that \CFA has to insert two extra function
370calls, while \Cpp does not have to execute any other instructions.
371Python is much further behind.
373\item[Cross Finally]
374\CFA's performance now matches \Cpp's from Cross Handler.
375If the code from the finally clause is being inlined,
376which is just an asm comment, than there are no additional instructions
377to execute again when exiting the try statement normally.
379\item[Conditional Match]
380Both of the conditional matching tests can be considered on their own.
381However, for evaluating the value of conditional matching itself, the
382comparison of the two sets of results is useful.
383Consider the massive jump in run time for \Cpp going from match all to match
384none, which none of the other languages have.
385Some strange interaction is causing run time to more than double for doing
386twice as many raises.
387Java and Python avoid this problem and have similar run time for both tests,
388possibly through resource reuse or their program representation.
389However, \CFA is built like \Cpp, and avoids the problem as well.
390This matches
391the pattern of the conditional match, which makes the two execution paths
392very similar.
396\subsection{Resumption \texorpdfstring{(\autoref{t:PerformanceResumption})}{}}
398Moving on to resumption, there is one general note:
399resumption is \textit{fast}. The only test where it fell
400behind termination is Cross Handler.
401In every other case, the number of iterations had to be increased by a
402factor of 10 to get the run time in an appropriate range
403and in some cases resumption still took less time.
405% I tried \paragraph and \subparagraph, maybe if I could adjust spacing
406% between paragraphs those would work.
408\item[Empty Traversal]
409See above for the general speed-up notes.
410This result is not surprising as resumption's linked-list approach
411means that traversing over stack frames without a resumption handler is
414\item[D'tor Traversal]
415Resumption does have the same spike in run time that termination has.
416The run time is actually very similar to Finally Traversal.
417As resumption does not unwind the stack, both destructors and finally
418clauses are run while walking down the stack during the recursive returns.
419So it follows their performance is similar.
421\item[Finally Traversal]
422Same as D'tor Traversal,
423except termination did not have a spike in run time on this test case.
425\item[Other Traversal]
426Traversing across handlers reduces resumption's advantage as it actually
427has to stop and check each one.
428Resumption still came out ahead (adjusting for iterations) but by much less
429than the other cases.
431\item[Cross Handler]
432The only test case where resumption could not keep up with termination,
433although the difference is not as significant as many other cases.
434It is simply a matter of where the costs come from:
435both termination and resumption have some work to set up or tear down a
436handler. It just so happens that resumption's work is slightly slower.
438\item[Conditional Match]
439Resumption shows a slight slowdown if the exception is not matched
440by the first handler, which follows from the fact the second handler now has
441to be checked. However, the difference is not large.
445\subsection{Resumption/Fixup \texorpdfstring{(\autoref{t:PerformanceFixupRoutines})}{}}
447Finally are the results of the resumption/fixup routine comparison.
448These results are surprisingly varied. It is possible that creating a closure
449has more to do with performance than passing the argument through layers of
451At 100 stack frames, resumption and manual fixup routines have similar
452performance in \CFA.
453More experiments could try to tease out the exact trade-offs,
454but the prototype's only performance goal is to be reasonable.
455It is already in that range, and \CFA's fixup routine simulation is
456one of the faster simulations as well.
457Plus, exceptions add features and remove syntactic overhead,
458so even at similar performance, resumptions have advantages
459over fixup routines.
Note: See TracBrowser for help on using the repository browser.