Question regarding source code `OnceCell::get_or_try_init`

I am not sure if I understand below comments regarding the need of outlined_call in below source code.
Without outlined_call, what is the problem of inlining the closure f?

    pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
    where
        F: FnOnce() -> Result<T, E>,
    {
        if let Some(val) = self.get() {
            return Ok(val);
        }
        /// Avoid inlining the initialization closure into the common path that fetches
        /// the already initialized value
        #[cold]
        fn outlined_call<F, T, E>(f: F) -> Result<T, E>
        where
            F: FnOnce() -> Result<T, E>,
        {
            f()
        }
        let val = outlined_call(f)?;
        // Note that *some* forms of reentrant initialization might lead to
        // UB (see `reentrant_init` test). I believe that just removing this
        // `assert`, while keeping `set/get` would be sound, but it seems
        // better to panic, rather than to silently use an old value.
        assert!(self.set(val).is_ok(), "reentrant init");
        Ok(self.get().unwrap())
    }

The idea is that an OnceCell will be initialized most of the time, so you want to optimize the code for that case. For this reason you don't want all the code of f to be in the same assembly function because that will likely lead to an instruction cache miss. Thus you want it to be separated, and that's what the #[cold] attribute does.

4 Likes

Also, LLVM is smart about #[cold] -- it knows that any branches that lead to something cold are unlikely to be taken, and will arrange the code accordingly.

If you've ever looked at the assembly you get in functions that panic, you'll notice that all the panic blocks are at the bottom, where they're the least likely to get loaded into icache and where your branch predictor is more likely to guess correctly that the branch-to-the-panic-stuff isn't going to be taken. That's #[cold] in action.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.