使用ThreadLocal storage来避免lock带来的性能损耗问题

在jvm的垃圾回收章节中有讲到一个问题,就是关于young-generation中对新生对象的内存分配的优化涉及到的两个技术点,一个是Bump-the-pointer,此技术追踪了当前young-generation区的edan区域最新分配内存的对象,此对象放置在edan区域的top位置,最容易被访问到,然后当再次需要分配内存给新的对象的时候,都会检测edan区剩余的内存空间是否足以分配给new出来的对象,如果空间足够的话那么新生成的对象再次取代上一个new出来的对象的top位置(应该是一个链表的结构).不够的话自然触发minor gc了。

以上看起来很不错,但是前提是在单线程中很好用;万一是多线程环境呢?第一反应肯定是在alloc内存之前加锁呗…然而这样带来的问题就是性能低下,毕竟new那么频繁,对性能的影响肯定是极大的。

所以对应的技术就是Thread-local allocation buffers,避免加锁也能实现内存的高效分配。

参考

其中有一个example code如下:

1
2
3
4
5
6
7
8
9
10
int count=0;
#pragma omp parallel shared(count)
{
. . .
if (event_happened) {
#pragma omp atomic
count++;
}
. . .
}

这段代码必须要保证在多线程环境下对count的自加行为是同步发生的,所以解决的思路是:让每一个线程在自己的内存空间中对这个count进行自加,然后再统一sum各个线程中的count值,最终得到的value就是正确的结果。据此改善后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int count=0;
int tcount=0;//线程独立内存空间中的count值,其特点就是只能被当前线程内部的所有函数操作,但是其他的线程无法操作。
#pragma omp threadprivate(tcount)
omp_set_dynamic(0);
#pragma omp parallel
{
. . .
if (event_happened) {
tcount++;
}
. . .
}
#pragma omp parallel shared(count)
{
#pragma omp atomic
count += tcount;//最终的sum操作
}

那么实现一个thread-local storage的原理是怎样的呢?
通常的做法是建立一个全局表,这张表里记录着threadID->thread-data-addr,也就是这张表需要记录的就是每一个thread的id映射到各自的数据内存地址即可。

具体实现,参考java层实现的thread-local,在此博文中有讲到,可供参考ThreadLocal的工作原理

—–update
threadID->thread-data是最早的设计,目前已经使用一个Values类来描述存储数据结构,实质上是一个Object[],并且是一个比较有意思的数组,体现在threadLocal对象作为key,独立变量参数作为value;可是这个数据结构不就是一个Obejct[]数组吗?哪来的key->value映射呢?(肯定需要一个map吧),然而设计的是这个obj[]的偶数index存放的是threadlcoal对象而相邻的奇数index存放的就是对应的value.