为什么没有使用页表映射实现线程本地存储?

tro*_*acl 5 c++ performance multithreading thread-local-storage c++11

我希望将C++ 11 thread_local关键字用于每线程布尔标志,该标志将非常频繁地访问.

但是,大多数编译器似乎都使用一个表来实现线程本地存储,该表将整数ID(槽)映射到当前线程上的变量地址.此查找将在性能关键的代码路径中发生,因此我对其性能有一些担忧.

我希望实现线程本地存储的方式是通过分配由不同物理页面支持的虚拟内存范围,具体取决于线程.这样,访问该标志将与任何其他存储器访问的成本相同,因为MMU负责映射.

为什么没有一个主流编译器以这种方式利用页表映射?

我想我可以mmap在Linux和VirtualAllocWin32 上实现我自己的"特定于线程的页面" ,但这似乎是一个非常常见的用例.如果有人知道现有或更好的解决方案,请指出他们.

我还考虑std::atomic<std::thread::id>在每个对象中存储一个代表活动线程,但是分析显示检查std::this_thread::get_id() == active_thread非常昂贵.

Bas*_*tch 6

在Linux/x86-64线程上,本地存储通过特殊的段寄存器实现%fs(每x86-64 ABI第23页......)

所以下面的代码(我使用C + GCC扩展__thread语法,但它与C++ 11相同thread_local)

__thread int x;
int f(void) { return x; }
Run Code Online (Sandbox Code Playgroud)

编译(带gcc -O -fverbose-asm -S)到:

         .text
 .Ltext0:
         .globl  f
         .type   f, @function
 f:
 .LFB0:
         .file 1 "tl.c"
         .loc 1 3 0
         .cfi_startproc
         .loc 1 3 0
         movl    %fs:x@tpoff, %eax       # x,
         ret
         .cfi_endproc
 .LFE0:
         .size   f, .-f
         .globl  x
         .section        .tbss,"awT",@nobits
         .align 4
         .type   x, @object
         .size   x, 4
 x:
         .zero   4
Run Code Online (Sandbox Code Playgroud)

因此,与您的恐惧相反,在Linux/x86-64上访问TLS非常快.它并没有完全实现为表(而是内核和运行时管理%fs段寄存器指向特定于线程的内存区域,编译器和链接器在那里管理偏移).但是,旧的pthread_getspecific确实通过了一个表,但是一旦你有TLS就几乎没用了.

BTW,顾名思义,所有的线程在同一进程共享相同的地址空间的虚拟内存,因为一个进程都有自己单独的地址空间.(参见/proc/self/mapsetc ... 有关更多信息 ,请参阅proc(5)/proc/,以及mmap(2) ; C++ 11线程库基于使用clone(2)实现的pthreads).因此"特定于线程的内存映射"是一个矛盾:一旦一个任务(由内核调度程序运行的东西)有自己的地址空间,它就被称为进程(而不是一个线程).同一进程中线程的定义特征是共享公共地址空间(以及一些其他实体,如文件描述符).