diff options
author | Aria Shrimpton <me@aria.rip> | 2024-03-06 16:15:23 +0000 |
---|---|---|
committer | Aria Shrimpton <me@aria.rip> | 2024-03-06 16:15:23 +0000 |
commit | bbfdd5ec7404a28ae735884db3d98f2eebc48fa1 (patch) | |
tree | 55dfab6192a80d19e51768008a8e1660062df420 /thesis/parts/implementation.tex | |
parent | 7d1f4550b99783c9c09383f4807e5a4b32b42cb3 (diff) |
most of rest of implementation chapter
Diffstat (limited to 'thesis/parts/implementation.tex')
-rw-r--r-- | thesis/parts/implementation.tex | 76 |
1 files changed, 72 insertions, 4 deletions
diff --git a/thesis/parts/implementation.tex b/thesis/parts/implementation.tex index 3576447..b1e156a 100644 --- a/thesis/parts/implementation.tex +++ b/thesis/parts/implementation.tex @@ -71,14 +71,82 @@ Although it has some amount of overhead, it's not important as we aren't measuri \section{Selection and Codegen} %% Selection Algorithm incl Adaptiv +Selection is done per container site. +For each candidate implementation, we calculate its cost on each partition in the profiler output, then sum these values to get the total estimated cost for each implementation. +This provides us with estimates for each singular candidate. -%% Generated code (opaque types) +In order to try and suggest an adaptive container, we use the following algorithm: -%% Implementation w/ const generics +\begin{enumerate} +\item Calculate the cost for each candidate and for each partition +\item For each partition, find the best candidate and store it in the array \code{best}. Note that we don't sum across all partitions this time. +\item Find the lowest index \code{i} where \code{best[i] != best[0]} +\item Check that \code{i} partitions the list properly: For all \code{j < i}, \code{best[j] == best[0]} and for all \code{j>=i}, \code{best[j] == best[i]}. +\item Let \code{before} be the name of the candidate in \code{best[0]}, \code{after} be the name of the candidate in \code{best[i]}, and \code{threshold} be the maximum n value of partition \code{i}. +\item Calculate the cost of switching as: + $$ + C_{\textrm{before,clear}}(\textrm{threshold}) + \textrm{threshold} * C_{\textrm{after,insert}}(\textrm{threshold}) + $$ +\item Calculate the cost of not switching: The sum of the difference in cost between \code{before} and \code{after} for all partitions with index \code{> i}. +\item If the cost of not switching is less than the cost of switching, we can't make a suggestion. +\item Otherwise, suggest an adaptive container which switches from \code{before} to \code{after} when $n$ gets above \code{threshold}. Its estimated cost is the cost for \code{before} up to partition \code{i}, plus the cost of \code{after} for all other partitions. +\end{enumerate} -\section{Misc Concerns} +Selection is implemented in \code{src/crates/candelabra/src/profiler/info.rs} and \code{src/crates/candelabra/src/select.rs}. -\todo{Justify Rust as language} +%% Generated code (opaque types) +As mentioned above, the original Primrose code would generate code as in Listing \ref{lst:primrose_codegen}. +In order to ensure that users specify all of the traits they need, this code only exposes methods on the implementation that are part of the trait bounds given. +However, it does this by using a \code{dyn} object, Rust's mechanism for dynamic dispatch. + +Although this approach works, it adds an extra layer of indirection to every call: The caller must use the dyn object's vtable to find the method it needs to call. +This also prevents the compiler from optimising across this boundary. + +In order to avoid this, we make use of Rust's support for existential types: Types that aren't directly named, but are inferred by the compiler. +Existential types only guarantee their users the given trait bounds, therefore they accomplish the same goal of forcing users to specify all of their trait bounds upfront. + +Figure \ref{lst:new_codegen} shows our equivalent generated code. +The type alias \code{Stack<S>} only allows users to use the \code{Container<S>}, \code{Stack<S>}, and \code{Default} traits. +Our unused 'dummy' function \code{_StackCon} has the return type \code{Stack<S>}. +Rust's type inference step sees that its actual return type is \code{Vec<S>}, and therefore sets the concrete type of \code{Stack<S>} to \code{Vec<S>} at compile time. + +Unfortunately, this feature is not yet in stable Rust, meaning we have to opt in to it using an unstable compiler flag (\code{feature(type_alias_impl_trait)}). +At time of writing, the main obstacle to stabilisation appears to be design decisions that only apply to more complicated use-cases, therefore we are confident that this code will remain valid and won't encounter any compiler bugs. + +\begin{figure}[h] + \begin{lstlisting}[caption=Code generated by original Primrose project,label={lst:primrose_codegen},language=Rust] +pub trait StackTrait<T> : Container<T> + Stack<T> {} +impl<T: 'static + Ord + std::hash::Hash> StackTrait<T> for <Stack<T> as ContainerConstructor>::Impl {} + + +pub struct Stack<T> { + elem_t: core::marker::PhantomData<T>, +} + +impl<T: 'static + Ord + std::hash::Hash> ContainerConstructor for Stack<T> { + type Impl = Vec<T>; + type Bound = dyn StackTrait<T>; + fn new() -> Box<Self::Bound> { + Box::new(Self::Impl::new()) + } +} +\end{lstlisting} +\end{figure} + +\begin{figure}[h] + \begin{lstlisting}[caption=Code generated with new method,label={lst:new_codegen},language=Rust] +pub type StackCon<S: PartialEq + Ord + std::hash::Hash> = impl Container<S> + Stack<S> + Default; + +#[allow(non_snake_case)] +fn _StackCon<S: PartialEq + Ord + std::hash::Hash>() -> StackCon<S> { + std::vec::Vec::<S>::default() +} +\end{lstlisting} +\end{figure} + +\section{Miscellaneous concerns} + +In this section, we highlight some other design decisions we made, and justify them. \todo{Explain cargo's role in rust projects \& how it is integrated} |