aboutsummaryrefslogtreecommitdiff
path: root/thesis/parts/design.tex
diff options
context:
space:
mode:
authorAria Shrimpton <me@aria.rip>2024-02-09 12:24:21 +0000
committerAria Shrimpton <me@aria.rip>2024-02-09 12:24:21 +0000
commiteecdde83e1bcccee7cdfa4af8191ec1aa727acb7 (patch)
tree6715a8b654550964ed7bac11ec4a2c3244c2e283 /thesis/parts/design.tex
parente43fef5b73b74849e0f85df48a62b29be52534b3 (diff)
more writing
Diffstat (limited to 'thesis/parts/design.tex')
-rw-r--r--thesis/parts/design.tex76
1 files changed, 69 insertions, 7 deletions
diff --git a/thesis/parts/design.tex b/thesis/parts/design.tex
index 0a93836..03e281d 100644
--- a/thesis/parts/design.tex
+++ b/thesis/parts/design.tex
@@ -1,18 +1,80 @@
-\todo{Introduction}
-\todo{Aims / expected input}
+This chapter outlines the design of our container selection system (Candelabra), and the justification behind these design decisions.
-\section{Overview of approach}
+We first describe our aims and priorities for the system, and illustrate its usage with an example.
+
+We then provide an overview of the container selection process, and each part in it.
+
+\section{Aims \& Usage}
+
+We aim to create a program for container selection that can select based on both functional and non-functional requirements.
+Flexibility is a high priority: It should be easy to add new container implementations, and to integrate our system into existing applications.
+Our system should also be able to scale to larger programs, and remain convenient for developers to use.
+
+We chose to implement our system as a Rust CLI, and limit it to selecting containers for Rust programs.
+We require the user to provide their own benchmarks, which should be representative of a typical application run.
+
+The user can specify their functional requirements by listing the required traits, and specifying properties that must always hold in a lisp-like language.
+This part of the process is handled by Primrose\parencite{qin_primrose_2023}, with only minor modifications to integrate with the rest of our system.
+
+For example, take the below code from our test case based on the sieve of Eratosthenes (\code{src/tests/prime\_sieve} in the source artifacts).
+Here we request two container types: \code{Sieve} and \code{Primes}.
+The first must implement the \code{Container} and \code{Stack} traits, and must satisfy the \code{lifo} property. This property is defined at the top as only being applicable to \code{Stack}s, and requires that for any \code{x}, pushing \code{x} then popping from the container returns \code{x}.
+
+The second container type, \code{Primes}, must only implement the \code{Container} trait, and must satisfy the \code{ascending} property.
+This property requires that at any point, for all consecutive \code{x, y} pairs in the container, \code{x $\leq$ y}.
+
+\begin{lstlisting}
+/*SPEC*
+property lifo<T> {
+ \c <: (Stack) -> (forall \x -> ((equal? (pop ((push c) x))) x))
+}
+
+property ascending<T> {
+ \c -> ((for-all-consecutive-pairs c) leq?)
+}
+
+
+type Sieve<S> = {c impl (Container, Stack) | (lifo c)}
+type Primes<S> = {c impl (Container) | (ascending c)}
+*ENDSPEC*/
+\end{lstlisting}
+
+Once we've specified our functional requirements and provided a benchmark (\code{src/tests/prime\_sieve/benches/main.rs}), we can simply run candelabra to select a container:
+
+\todo{Show selection process}
+
+Our tool integrates with Rust's packaging system (Cargo) to discover the information it needs about our project, then runs Primrose to find a list of implementations satsifying our functional requirements.
+
+Once it has this list, it estimates a 'cost' for each candidate, which is an upper bound on the total time taken for all container operations.
+At this point, it also checks if an 'adaptive' container would be better, by checking if one implementation is better performing at a lower n, and another at a higher n.
+
+Finally, it picks the container with the minimum cost, and creates a new Rust file where the chosen container type is exported.
+
+Our tool requires little user intervention, integrates well with existing workflows, and the time it takes scales linearly with the number of container types in a given project.
+
+\section{Selection Process}
+
+We now describe the design of our selection process in detail, and justify our choices.
+
+As mentioned above, the first stage of our process is to satisfy functional requirements, which we do using code from Primrose\parencite{qin_primrose_2023}.
+The exact internals are beyond the scope of this paper, but in brief this works by:
+\begin{itemize}
+\item Finding all implementations in the container library that implement all required traits
+\item Translate any specified properties to a Rosette expression
+\item For each implementation, model the behaviour of each operation in Rosette, and check that the required properties always hold
+\end{itemize}
+
+We use the code provided with the Primrose paper, with minor modifications elaborated on in \ref{chap:implementation}.
Once a list of functionally close enough implementations have been found, selection is done by:
\begin{itemize}
-\item Get a list of implementations that satisfy the program's functional requirements
-\item Estimating the cost of each operation, for each implementation, for any given n value
-\item Profiling the program to rank operation 'importance',
+\item For each operation of each implementation, build a cost model which can estimate the 'cost' of that operation at any given container size $n$
+\item Profile the program, tracking operation frequency and container sizes
\item Combining the two to create an estimate of the relative cost of each implementation
\end{itemize}
-\subsection{Cost Estimation}
+\subsection{Cost Models}
We use an approach similar to CollectionSwitch\parencite{costa_collectionswitch_2018}, which assumes that the main factor in how long an operation takes is the current size of the collection.