TLS是一種在多線程時使用的技術(shù),它可以使你的全局變量泪电、靜態(tài)變量以及局部靜態(tài)般妙、靜態(tài)成員變量成為線程獨立的變量,即每個線程的TLS變量之間互不影響相速。例如linux下的全局變量 errno碟渺,windows下的GetLastError ,線程A在設(shè)置了一個錯誤信息后突诬,線程B又設(shè)置了一個錯誤信息苫拍,前一個線程設(shè)置的信息就被覆蓋了。解決方法就是將這個全局變量設(shè)置為TLS變量攒霹,這樣在用戶看來errno是一個全局變量怯疤,實際上它是每個線程獨立的。
visual c++聲明
__declspec(thread) int number;
linux下聲明
__thread int number;
關(guān)于一些對TLS過度消耗的分析文章網(wǎng)上有不少催束,根本原因在于取TLS變量的方法集峦。我們普通的全局變量可以直接通過變量名取到變量地址,而TLS則是通過線程id去一個底層維護的數(shù)組中取到地址抠刺。大體實現(xiàn)方式如下:
void* __tls_get_addr(size_t m, size_t offset)
{
char* tls_block = dtv[thread_id][m];
return tls_block + offset;
}
這里是TLS的實現(xiàn)方式塔淤,感興趣的朋友可以看一下
正常使用TLS只會帶來細微的額外消耗,代碼如下:
#include "stdio.h"
#include "math.h"
double tlvar;
//get_value被設(shè)置為禁止編譯器內(nèi)聯(lián)
double get_value() __attribute__ ((noinline));
double get_value()
{
return tlvar;
}
int main()
{
int i;
double f=0.0;
tlvar = 1.0;
for(i=0; i<1000000000; i++)
{
f += sqrt(get_value());
}
printf("f = %f\n", f);
return 1;
}
不使用TLS的運行情況
$ time ./test_no_thread
f = 1000000000.000000
real 0m5.169s
user 0m5.137s
sys 0m0.002s
下面使用TLS測試
#include "stdio.h"
#include "math.h"
__thread double tlvar;
//get_value被設(shè)置為禁止編譯器內(nèi)聯(lián)
double get_value() __attribute__ ((noinline));
double get_value()
{
return tlvar;
}
int main()
{
int i;
double f=0.0;
tlvar = 1.0;
for(i=0; i<1000000000; i++)
{
f += sqrt(get_value());
}
printf("f = %f\n", f);
return 1;
}
使用TLS的運行情況
$ time ./test
f = 1000000000.000000
real 0m5.232s
user 0m5.158s
sys 0m0.007s
以上測試都禁止編譯器內(nèi)聯(lián)get_value函數(shù)速妖。因為get_value被內(nèi)聯(lián)后高蜂,編譯器會發(fā)現(xiàn)它的返回值一直是不變的,然后將get_value提到循環(huán)之前罕容。這樣會導(dǎo)致對tlval的地址獲取只有一次备恤,失去循環(huán)測試的意義。
如果在你使用的動態(tài)庫中有大量使用TLS變量的情況锦秒,那將是一種災(zāi)難露泊。因為動態(tài)庫中獲取TLS變量地址使用不同的方法,會多消耗一倍的時間旅择。
$ cat test_main.c
#include "stdio.h"
#include "math.h"
int test();
int main()
{
test();
return 1;
}
動態(tài)庫:
$ cat test_lib.c
#include "stdio.h"
#include "math.h"
static __thread double tlvar;
//get_value被設(shè)置為禁止編譯器內(nèi)聯(lián)
double get_value() __attribute__ ((noinline));
double get_value()
{
return tlvar;
}
int test()
{
int i;
double f=0.0;
tlvar = 1.0;
for(i=0; i<1000000000; i++)
{
f += sqrt(get_value());
}
printf("f = %f\n", f);
return 1;
}
動態(tài)庫中循環(huán)使用TLS變量運行情況:
$ time ./test_main
f = 1000000000.000000
real 0m9.978s
user 0m9.906s
sys 0m0.004s
$ perf record ./test_main
$ perf report --stdio
#
# Events: 10K cpu-clock
#
# Overhead Command Shared Object Symbol
# ........ .............. ................... ..................
#
58.05% inet_test_main libinet_test_lib.so [.] test
21.15% inet_test_main ld-2.12.so [.] __tls_get_addr
10.69% inet_test_main libinet_test_lib.so [.] get_value
5.07% inet_test_main libinet_test_lib.so [.] get_value@plt
4.82% inet_test_main libinet_test_lib.so [.] __tls_get_addr@plt
0.23% inet_test_main [kernel.kallsyms] [k] 0xffffffffa0165b75
如上惭笑,__tls_get_addr占據(jù)了20%多的消耗。解決辦法其實很簡單,就是把get_value函數(shù)設(shè)置為內(nèi)聯(lián)沉噩,原因上面已經(jīng)講過捺宗。