ned Productions Consulting

Technology musings by Niall Douglas
ned Productions Consulting
(an expert advice and services company based in Ireland)

Wednesday 8th July 2015 12.42pm

As lightweight C++ futures have matured it became painfully obvious that a ground up refactor of its design was required. The code base until very recently employed lots of "safe" undefined behaviour to save me writing code to deal with future to shared_future conversion etc, and of course making use of undefined behaviour always produces faster code, so specifically speaking what I was doing was to use reinterpret_cast to have the compiler not concern itself with whether the future the promise was talking to was really a future or a shared_future state. This worked because both had identical layout.

A problem emerged when I realised that I couldn't get a shared_future continuation to not double shared_ptr manage the same shared state unless I brought a std::enable_shared_from_this into the mix. That of course changes the layout for the shared_future state, and my undefined behaviour is no longer feasible - besides, to be honest I always knew my use of undefined behaviour was more one of laziness than it being absolutely necessary. That meant a design refactor, and you can see my current progress on the whiteboard diagram below which reflects the codebase as of this morning whereby there is a base class hierarchy of vtabled but concrete types, with templated types laid upon the top. This allows future and promise to have no type awareness of what implementation its linked opposite actually is because their formal interactions are all wrapped into a set of virtual member functions which abstract out the details. In other words, promise can set state and detach knowing nothing about the future whose state is being set.

Now, the design below still has many flaws. Firstly, we are using vtables at all, and they can be eliminated by keeping all storage in concrete base types as by definition those must be the same in both future and promise, and replacing the vtabled continuations invocation with a type erased callable. Secondly, we are composing far too many layers, and I can eliminate about half of those. Thirdly, the std::enable_shared_from_this needs to be on the basic_future, not before the basic_monad as basic_monad is inherited by protected inheritance, and that breaks automatic deduction by std::shared_ptr. Fourthly, right now future is consuming a shocking 80 bytes and we need that under a cache line, so within 64 bytes - by removing a vptr, packing the bools, and merging continuation_future * into continuation storage, we can claw back 24 bytes of that.

Nevertheless, the bulk of the work is actually done - I was up till 5am sprinting on remove the last vestiges of all reinterpret_cast - they proliferate surprisingly quickly once you allow yourself to use one once.  The current codebase as of this morning is now free of all undefined behaviour that I know, and it has cost me approx. 25 hours for me to make it so :(

With the AFIO review beginning just eight days away, I am beginning to wonder if I can make it in time. The AFIO API refactor to make a final API is pretty minimal once you have these futures, but it looks like I won't have the time to rewrite the example code in the docs to make proper use of custom futures, and I should worry if that might be a problem.