# Request for prioritization: fast thread locals

**URL:** <https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982>\
**Category:** language design\
**Created:** [February 4, 2021, 11:12am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982 "2021-02-04T11:12:11Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [February 4, 2021, 11:12am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/1 "2021-02-04T11:12:11Z")

</div>

Rust stable thread-locals (thread\_local! macro) are not zero cost -- they support destructors, and so they must have "is this thread-local destroyed?" check at runtime on every access.

This is perfectly fine for once use-case of thread locals -- provide a cheap way to pass application context around without actually threading the ctx parameter everywhere.

However, this I think (and here I am a bit out of my depth) is unsatisfactory for another use-case -- speeding up concurrent algorithms. For example, the attempt to port mimalloc allocator to Rust immediately hits the need for fast no frills thread locals. [Counting things](https://arxiv.org/abs/1701.00854) also needs fast tls.

This is a pretty frustrating limitation, as you can't just work around it by just writing more, less elegant code. This is an expressiveness gap in the kind of machine code you can produce, and not in the shape of the source code that type-checks.

At the same time, it is a relatively niche and obscure use-case. I feel that some top-down nudging from the lang team to solve this problem would benefit the language quite a bit, hence the suggestions to up priority on this one 🙂 . As far as I know, this isn't blocked on anything external to the problem itself.

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [February 4, 2021, 11:44am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/2 "2021-02-04T11:44:46Z")

</div>

I would love to see this happen, if someone is interested in working on it.

In addition, I'd also like to see the `tls-model` option stabilized, so that applications can use a faster mechanism for thread-local storage.

---

<div class="post-metadata">

**Author:** ![bluss](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bluss/32/2264_2.png) [@bluss](https://internals.rust-lang.org/u/bluss)\
**Post date:** [February 4, 2021, 12:04pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/3 "2021-02-04T12:04:27Z")

</div>

Could the stable thread local support be improved without a breaking change? It would need to avoid using Box and could avoid registering a destructor - but without box only certain size values can be supported(?)

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [February 4, 2021, 12:25pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/4 "2021-02-04T12:25:42Z")

</div>

This seems unlikely. Unlike with statics, we need to run destructors for thread locals -- otherwise that's a memory leak. So well need to somehow specialize on "needs drop?", and at that level it seems better to have the difference visible in the surface syntax?

---

<div class="post-metadata">

**Author:** ![chrisd](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/chrisd/32/7232_2.png) [@chrisd](https://internals.rust-lang.org/u/chrisd)\
**Post date:** [February 4, 2021, 1:44pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/5 "2021-02-04T13:44:27Z")

</div>

Forgive me if I'm misunderstanding but I think there are (broadly speaking) two ways of initializing thread locals:

- statically initialized variables where the initial value can simply be copied to a new thread or directly set (e.g. a counter that starts at zero).
- lazily initialized variables that need to call a function to recreate them for a new thread.

And on thread exit a variable can either require a function to run (e.g. `drop`) or not.

So in total there's four basic ways of handling a thread local. I think efficiently encapsulating these different uses will require a different API to the one currently in std, no?

---

<div class="post-metadata">

**Author:** ![Amanieu](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/amanieu/32/4095_2.png) [@Amanieu](https://internals.rust-lang.org/u/Amanieu)\
**Post date:** [February 4, 2021, 6:36pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/6 "2021-02-04T18:36:46Z")

</div>

There are 2 ways to go about this:

- Improve `thread_local!` to better handle no-drop types and constant initializers.
- Stabilize `#[thread_local]` statics (with some design work to fix outstanding issues).

---

<div class="post-metadata">

**Author:** ![programmerjake](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/programmerjake/32/5893_2.png) [@programmerjake](https://internals.rust-lang.org/u/programmerjake)\
**Post date:** [February 4, 2021, 7:32pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/7 "2021-02-04T19:32:17Z")

</div>

+1 for `#[thread_local]` they're useful for FFI too.

---

<div class="post-metadata">

**Author:** ![bluss](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bluss/32/2264_2.png) [@bluss](https://internals.rust-lang.org/u/bluss)\
**Post date:** [February 4, 2021, 11:14pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/8 "2021-02-04T23:14:55Z")

</div>

Special casing on needs drop is not very _special_, we even have an old function called `needs_drop` which lets you do it dynamically if that's the right fit.

---

<div class="post-metadata">

**Author:** ![comex](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/comex/32/2587_2.png) [@comex](https://internals.rust-lang.org/u/comex)\
**Post date:** [February 5, 2021, 5:46am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/9 "2021-02-05T05:46:52Z")

</div>

Note that the instruction sequences used for plain-old-data thread-locals in C differ _greatly_ depending on OS and even the type of binary.

On x86-64 Linux, in an executable, it's a single load instruction:

```rust
        mov eax, dword ptr fs:[x@TPOFF]

```

But in a dynamic library (`-fPIC`), [it's a call](https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-60/index.html):

```rust
        lea rdi, [rip + x@TLSGD]
        data16
        data16
        rex64
        call __tls_get_addr@PLT

```

And on macOS, whether executable or library, it's… an indirect call!?

```rust
        movq	_x@TLVP(%rip), %rdi
        callq	*(%rdi)

```

(As far as I can tell, this call always goes to [`tlv_get_addr`](https://opensource.apple.com/source/dyld/dyld-832.7.3/src/threadLocalHelpers.s.auto.html).)

So even if Rust fixes thread-locals to be zero-cost, compared to the underlying OS mechanism, that doesn't necessarily mean they're _fast_. Unfortunately.

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [February 5, 2021, 4:38pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/10 "2021-02-05T16:38:12Z")

</div>

Oh wow, it appears that mimalloc is doing something exceptionally cursed on mac:

> <https://github.com/microsoft/mimalloc/blob/15220c684331d1c486550d7a6b1736e0a1773816/include/mimalloc-internal.h#L277-L289>

---

<div class="post-metadata">

**Author:** ![atagunov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/atagunov/32/5877_2.png) [@atagunov](https://internals.rust-lang.org/u/atagunov)\
**Post date:** [February 5, 2021, 10:21pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/11 "2021-02-05T22:21:13Z")

</div>

> [@comex](#):
>
> Note that the instruction sequences used for plain-old-data thread-locals in C differ _greatly_ depending on OS and even the type of binary

On x86-64 Linux [apparently](https://chao-tic.github.io/blog/2018/12/25/tls):

- TLS defined in `.exe`/used in `.exe` - no extra cost  
`mov eax, dword ptr fs:[x@TPOFF]`  
Local Executable (LE) model

- TLS defined in `.so` ~~/used in `.exe`~~ never loaded with `dlopen()` - one extra 64 bit memory read, uses an extra register  
`mov 0x200893(rip), rax`  
`mov eax, dword ptr fs:(rax)`  
Initial Executable (IE) model

- TLS ~~used in `.so`~~ defined in `.so` which can be loaded with `dlopen()` - lots of extra cost  
`__tls_get_addr()` does a number of reads  
Local Dynamic (LD) model  
General Dynamic (GD) model  
the difference is tiny  
with LE `ld.so` has less work to do on loading the library  
no difference at run-time

Are the expenses relevant for Rust though?  
Isn't a monolithic executable the main use case?  
Is anybody really compiling Rust code into `.so`-s?

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [February 5, 2021, 11:04pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/12 "2021-02-05T23:04:12Z")

</div>

> [@atagunov](#):
>
> Isn't a monolithic executable the main use case? Is anybody really compiling Rust code into `.so` -s?

I don't know how well supported this is right now, but I sure would like to be able to use Rust to implement a shared library that presents an interface that's ABI-compatible with an existing C shared library.

---

<div class="post-metadata">

**Author:** ![atagunov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/atagunov/32/5877_2.png) [@atagunov](https://internals.rust-lang.org/u/atagunov)\
**Post date:** [February 6, 2021, 12:00am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/13 "2021-02-06T00:00:29Z")

</div>

> [@zackw](#):
>
> I sure would like to be able to use Rust to implement a shared library that presents an interface that's ABI-compatible with an existing C shared library

The most expensive Linux/x86-64 TLS access models (LD, GD) seem necessary inside an `.so` that can be loaded with `dlopen()`.

If your `.so` can only be loaded before `main()` then `IE` _should_ suffice. Theoretically. [LLVM LangRef](https://llvm.org/docs/LangRef.html#thread-local-storage-models) says:

> [@](#):
>
> A variable may be defined as `thread_local` , which means that it will not be shared by threads (each thread will have a separated copy of the variable). Not all targets support thread-local variables. Optionally, a TLS model may be specified:
> 
> `localdynamic`
> 
> For variables that are only used within the current shared library.
> 
> `initialexec`
> 
> For variables in modules that will not be loaded dynamically.
> 
> `localexec`
> 
> For variables defined in the executable and only used within it.

This seems to confirm the hypothesis that you can use IE from an `.so`. And IE is just one extra read from GOT + an extra register. Not _that_ expensive.

Seems `rustc` could take some flags to control this.. And make it real cheap for monolithic executables by using LE.

---

<div class="post-metadata">

**Author:** ![comex](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/comex/32/2587_2.png) [@comex](https://internals.rust-lang.org/u/comex)\
**Post date:** [February 6, 2021, 12:01am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/14 "2021-02-06T00:01:52Z")

</div>

> [@atagunov](#):
>
> Seems `rustc` could take some flags to control this.

That's what the [`tls_model` option](https://doc.rust-lang.org/beta/unstable-book/compiler-flags/tls-model.html) @josh mentioned does, though it's not stable.

---

<div class="post-metadata">

**Author:** ![comex](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/comex/32/2587_2.png) [@comex](https://internals.rust-lang.org/u/comex)\
**Post date:** [February 6, 2021, 12:03am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/15 "2021-02-06T00:03:15Z")

</div>

> [@zackw](#):
>
> I don't know how well supported this is right now, but I sure would like to be able to use Rust to implement a shared library that presents an interface that's ABI-compatible with an existing C shared library.

It is well-supported enough. Examples of Rust used to reimplement existing C APIs:

- [librsvg](https://people.gnome.org/~federico/blog/exposing-c-and-rust-apis.html) mostly ported to Rust
- [mesalink](https://github.com/mesalock-linux/mesalink) which emulates OpenSSL's API (and maybe ABI, not sure)

Examples of crates with their own C APIs:

- [hyper](https://daniel.haxx.se/blog/2020/10/09/rust-in-curl-with-hyper/)
- [regex-capi](https://github.com/rust-lang/regex/tree/master/regex-capi)

---

<div class="post-metadata">

**Author:** ![H2CO3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/h2co3/32/2849_2.png) [@H2CO3](https://internals.rust-lang.org/u/H2CO3)\
**Post date:** [February 6, 2021, 8:19am UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/16 "2021-02-06T08:19:12Z")

</div>

> [@matklad](#):
>
> mimalloc is doing something exceptionally cursed

Re-cursed!

[/off]

---

<div class="post-metadata">

**Author:** ![tcsc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/tcsc/32/3806_2.png) [@tcsc](https://internals.rust-lang.org/u/tcsc)\
**Post date:** [February 8, 2021, 10:50pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/17 "2021-02-08T22:50:39Z")

</div>

> [@matklad](#):
>
> Oh wow, it appears that mimalloc is doing something exceptionally cursed on mac:

Note: this is only used if you try to globally override malloc on mac, otherwise it uses the equivalent of `#[thread_local]` (although it can also use pthread\_getspecific)

These hacks are needed for that case, but doing so inadvisable in general... Even though the known issues with mimalloc on mac when used this way should be fixed, still requires setting `DYLD_FORCE_FLAT_NAMESPACE=1` in your environment (or setting the `MH_FORCE_FLAT` flag in the header of the MachO executable) or dylibs you link/load will get a different allocator.

Unfortunately, disabling the flat namespace tends to break a lot of things in weird and confusing ways, including in system libraries.

Also note that the slot that mimalloc uses is probably not ideal. Slots 6 and 11 are only used by software running targetting WIN64 ABI (e.g. code running under something like WINE): [darwin-libpthread/private/tsd\_private.h at 03c4628c8940cca6fd6a82957f683af804f62e7f · apple/darwin-libpthread · GitHub](https://github.com/apple/darwin-libpthread/blob/03c4628c8940cca6fd6a82957f683af804f62e7f/private/tsd_private.h#L93-L96), which is probably safe to not support (although, maybe WINE wants to RIIR 🤔). I believe golang's runtime uses one of these also though...

* * *

Anyway I did a benchmark about some of this stuff a while ago (includes benches for per-cpu stuff, since really what i wanted was a way to heuristically reduce atomic contention, but where the algo would still be safe) [GitHub - thomcc/threadidbench](https://github.com/thomcc/threadidbench) (this is an older version of what I used for the numbers in [Proposal `std:: thread ::ThreadId::current()`](https://internals.rust-lang.org/t/proposal-std-thread-current/14009)). It includes a reimplementation of mimalloc's hackery, both in inline asm and called separately.

(Major caveat with that repo: That code has major bugs and is just intended to get a feel for the ballpark of what the various performance options would be, and not actually be usable — also that I don't think I pushed my fixes that make it compile on the non-mac targets).

Note that there's a similarly evil trick for windows too, which has the benefit of using the values handed out by TlsAlloc - discussed here [Thread Local Storage, part 2: Explicit TLS « Nynaeve](http://www.nynaeve.net/?p=181)

At one point I ported the x86\_64 equivalent to Rust, eliminating the checks that aren't needed if you're sure the index came from there. I don't know if how much of a benefit there is to using this over using #[thread\_local] but I know there's sure a lot of downsides! I don't think this is remotely stable to rely on, beyond it being a defacto part of the ABI

```rust
// Equivalent to TlsGetValue. Requires `slot` be a
// value given to you by TlsAlloc or you get massive UB
#[inline]
#[cfg(target_arch = "x86_64")]
unsafe fn tls_get_value_fast(slot: DWORD) -> u64 {
    // NB: __readgsqword is https://docs.rs/ntapi/0.3.6/src/ntapi/winapi_local/um/winnt.rs.html#35-44
    if slot < 64 {
        __readgsqword(0x1480 + slot * 8)
    } else {
        (__readgsqword(0x1780) as *const u64).add(slot - 64).read()
    }
}

```

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [May 9, 2021, 10:51pm UTC](https://internals.rust-lang.org/t/request-for-prioritization-fast-thread-locals/13982/18 "2021-05-09T22:51:04Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
