aboutsummaryrefslogtreecommitdiff
path: root/thesis/parts
diff options
context:
space:
mode:
authorAria Shrimpton <me@aria.rip>2024-03-06 16:15:23 +0000
committerAria Shrimpton <me@aria.rip>2024-03-06 16:15:23 +0000
commitbbfdd5ec7404a28ae735884db3d98f2eebc48fa1 (patch)
tree55dfab6192a80d19e51768008a8e1660062df420 /thesis/parts
parent7d1f4550b99783c9c09383f4807e5a4b32b42cb3 (diff)
most of rest of implementation chapter
Diffstat (limited to 'thesis/parts')
-rw-r--r--thesis/parts/implementation.tex76
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}