diff options
author | Aria Shrimpton <me@aria.rip> | 2024-02-09 12:24:21 +0000 |
---|---|---|
committer | Aria Shrimpton <me@aria.rip> | 2024-02-09 12:24:21 +0000 |
commit | eecdde83e1bcccee7cdfa4af8191ec1aa727acb7 (patch) | |
tree | 6715a8b654550964ed7bac11ec4a2c3244c2e283 /thesis/parts/design.tex | |
parent | e43fef5b73b74849e0f85df48a62b29be52534b3 (diff) |
more writing
Diffstat (limited to 'thesis/parts/design.tex')
-rw-r--r-- | thesis/parts/design.tex | 76 |
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. |