C++ 为什么线程本地存储没有使用页表映射实现?
我希望使用C++11C++ 为什么线程本地存储没有使用页表映射实现?,c++,multithreading,performance,c++11,thread-local-storage,C++,Multithreading,Performance,C++11,Thread Local Storage,我希望使用C++11thread\u local关键字作为每个线程的布尔标志,该标志将被频繁访问 然而,大多数编译器似乎使用一个表来实现线程本地存储,该表将整数ID(插槽)映射到当前线程上变量的地址。这种查找将发生在性能关键的代码路径中,因此我对其性能有一些担心 我期望实现线程本地存储的方法是根据线程分配由不同物理页支持的虚拟内存范围。这样,访问标志的成本将与任何其他内存访问的成本相同,因为MMU负责映射 为什么主流编译器都没有以这种方式利用页表映射 我想我可以在Linux上用mmap和Win3
thread\u local
关键字作为每个线程的布尔标志,该标志将被频繁访问
然而,大多数编译器似乎使用一个表来实现线程本地存储,该表将整数ID(插槽)映射到当前线程上变量的地址。这种查找将发生在性能关键的代码路径中,因此我对其性能有一些担心
我期望实现线程本地存储的方法是根据线程分配由不同物理页支持的虚拟内存范围。这样,访问标志的成本将与任何其他内存访问的成本相同,因为MMU负责映射
为什么主流编译器都没有以这种方式利用页表映射
我想我可以在Linux上用mmap
和Win32上用VirtualAlloc
实现我自己的“线程特定页面”,但这似乎是一个非常常见的用例。如果有人知道现有的或更好的解决方案,请告诉我
我还考虑在每个对象中存储一个表示活动线程的
std::atomic
,但是分析表明,检查std::this_thread::get_id()==active_thread
非常昂贵。在Linux/x86-64线程上,本地存储是通过一个特殊的段寄存器实现的%fs
(参见第23页…)
下面的代码(我使用的是C+GCC扩展\uuuThread
语法,但它与C++11thread\uLocal
相同)
被编译(使用gcc-O-fverbose asm-S)为:
因此,与您的担心相反,在Linux/x86-64上访问TLS的速度非常快。它并不是完全作为表实现的(相反,内核和运行时管理%fs
段寄存器以指向特定于线程的内存区域,而编译器和链接器管理其中的偏移量).然而,old确实通过了一张桌子,但一旦你有了TLS,它几乎是无用的
顺便说一句,根据定义,同一个进程中的所有线程共享同一个,因为一个进程有自己的单线程(请参见
/proc/self/maps
等…有关/proc/
的更多信息,请参见;C++11线程库是基于它来实现的)。因此,“特定于线程的内存映射”是一个矛盾:一次任务(由内核调度器运行的东西)有自己的地址空间,称为进程(而不是线程)。同一进程中的定义特征是共享一个公共地址空间(和一些其他实体,如文件描述符).内存映射不是每个线程而是每个进程。所有线程都将共享相同的映射
内核可以提供每个线程的映射,但目前没有。主流操作系统,如Linux、OSX、Windows,使页面映射成为每个进程的属性,而不是每个线程。这有一个很好的理由,页面映射表存储在RAM中,读取它来计算有效的物理地址是多余的如果必须对每个指令都执行此操作,则成本将非常昂贵 因此,处理器不会,它会将最近使用的映射表项的副本保存在靠近执行核心的快速内存中 使TLB缓存失效非常昂贵,必须从RAM中重新加载,数据在其中一个内存缓存中可用的几率很低。需要时,处理器可能会暂停数千个周期
<> P>所以,假设一个操作系统支持它,你的建议方案可能是非常低效的,使用索引查找更便宜。处理器非常擅长简单的数学,在千兆赫兹发生,访问内存在兆赫。 < p>你使用C++。每个线程都有一个线程对象,用TH的工作过程。read和它调用的所有/大多数函数都是该对象的成员函数。然后,您可以将线程ID或任何其他线程特定的数据作为成员变量。该建议不起作用,因为它会阻止其他线程通过指针访问您的
线程\u本地
变量。这些线程最终将访问r拥有该变量的副本
例如,假设您有一个主线程和100个工作线程。工作线程将指向其自己的
thread\u local
变量的指针传递回主线程。主线程现在有100个指向这100个变量的指针。如果TLS内存按照建议映射到页表,则主线程将有100个指向s的相同指针主线程的TLS中只有一个未初始化的变量—当然不是预期的!当前的一个问题是硬件约束(不过,我确信这早于下面的情况)
在SPARC T5处理器上,每个硬件线程都有自己的MMU,但在同一个核心上与多达七个同级线程共享一个TLB,而且TLB可能会受到相当大的打击
在MIPS上,线程的不同内存映射可能会强制将它们序列化为单个虚拟线程执行上下文。这是因为硬件线程上下文共享一个MMU。内核已经无法在相邻线程上下文上运行多个进程,每个线程的单独内存映射将具有相同的限制。如果您是fraid关于查找的性能(即评测证明是这样的)您可以使用
auto&x=my_-thread\u-local;
缓存地址。不幸的是,缓存地址对我的用例不起作用,因为thread\u-local
变量实际上是一个标志,指示“我是此对象的活动线程吗?”还有许多对象是以异步方式执行操作的。因此,这些查找发生在调用堆栈中的深度嵌套上下文中。哦..那太糟糕了。幸运的是,我没有使用TLS,但我仍然想知道为什么它设计为
__thread int x;
int f(void) { return x; }
.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