Không __thread
biến, không chậm.
Tôi sẽ sử dụng hiệu suất của thử nghiệm này làm cơ sở.
#include "stdio.h"
#include "math.h"
double tlvar;
//following line is needed so get_value() is not inlined by compiler
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;
}
Đây là mã lắp ráp của get_value()
Dump of assembler code for function get_value:
=> 0x0000000000400560 <+0>: movsd 0x200478(%rip),%xmm0 # 0x6009e0 <tlvar>
0x0000000000400568 <+8>: retq
End of assembler dump.
Đây là nhanh như thế nào nó chạy:
$ time ./inet_test_no_thread
f = 1000000000.000000
real 0m5.169s
user 0m5.137s
sys 0m0.002s
Có __thread
biến trong một thực thi (không có trong thư viện chia sẻ) , vẫn không có sự chậm trễ.
#include "stdio.h"
#include "math.h"
__thread double tlvar;
//following line is needed so get_value() is not inlined by compiler
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;
}
Đây là mã lắp ráp của get_value()
(gdb) disassemble get_value
Dump of assembler code for function get_value:
=> 0x0000000000400590 <+0>: movsd %fs:0xfffffffffffffff8,%xmm0
0x000000000040059a <+10>: retq
End of assembler dump.
Đây là nhanh như thế nào nó chạy:
$ time ./inet_test
f = 1000000000.000000
real 0m5.232s
user 0m5.158s
sys 0m0.007s
Vì vậy, nó là khá rõ ràng rằng khi __thread
var là trong thực thi nó nhanh như biến toàn cục thông thường.
Có biến số __thread
và nằm trong thư viện được chia sẻ, có độ trễ.
Executable:
$ cat inet_test_main.c
#include "stdio.h"
#include "math.h"
int test();
int main()
{
test();
return 1;
}
Thư viện dùng chung:
$ cat inet_test_lib.c
#include "stdio.h"
#include "math.h"
static __thread double tlvar;
//following line is needed so get_value() is not inlined by compiler
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;
}
Đây là mã lắp ráp của get_value(), xem cách khác nhau đó là - nó gọi __tls_get_addr()
:
Dump of assembler code for function get_value:
=> 0x00007ffff7dfc6d0 <+0>: lea 0x200329(%rip),%rdi # 0x7ffff7ffca00
0x00007ffff7dfc6d7 <+7>: callq 0x7ffff7dfc5c8 <[email protected]>
0x00007ffff7dfc6dc <+12>: movsd 0x0(%rax),%xmm0
0x00007ffff7dfc6e4 <+20>: retq
End of assembler dump.
(gdb) disas __tls_get_addr
Dump of assembler code for function __tls_get_addr:
0x0000003c40a114d0 <+0>: push %rbx
0x0000003c40a114d1 <+1>: mov %rdi,%rbx
=> 0x0000003c40a114d4 <+4>: mov %fs:0x8,%rdi
0x0000003c40a114dd <+13>: mov 0x20fa74(%rip),%rax # 0x3c40c20f58 <_rtld_local+3928>
0x0000003c40a114e4 <+20>: cmp %rax,(%rdi)
0x0000003c40a114e7 <+23>: jne 0x3c40a11505 <__tls_get_addr+53>
0x0000003c40a114e9 <+25>: xor %esi,%esi
0x0000003c40a114eb <+27>: mov (%rbx),%rdx
0x0000003c40a114ee <+30>: mov %rdx,%rax
0x0000003c40a114f1 <+33>: shl $0x4,%rax
0x0000003c40a114f5 <+37>: mov (%rax,%rdi,1),%rax
0x0000003c40a114f9 <+41>: cmp $0xffffffffffffffff,%rax
0x0000003c40a114fd <+45>: je 0x3c40a1151b <__tls_get_addr+75>
0x0000003c40a114ff <+47>: add 0x8(%rbx),%rax
0x0000003c40a11503 <+51>: pop %rbx
0x0000003c40a11504 <+52>: retq
0x0000003c40a11505 <+53>: mov (%rbx),%rdi
0x0000003c40a11508 <+56>: callq 0x3c40a11200 <_dl_update_slotinfo>
0x0000003c40a1150d <+61>: mov %rax,%rsi
0x0000003c40a11510 <+64>: mov %fs:0x8,%rdi
0x0000003c40a11519 <+73>: jmp 0x3c40a114eb <__tls_get_addr+27>
0x0000003c40a1151b <+75>: callq 0x3c40a11000 <tls_get_addr_tail>
0x0000003c40a11520 <+80>: jmp 0x3c40a114ff <__tls_get_addr+47>
End of assembler dump.
Nó chạy gần gấp đôi!:
$ time ./inet_test_main
f = 1000000000.000000
real 0m9.978s
user 0m9.906s
sys 0m0.004s
Và cuối cùng - đây là những gì perf
báo cáo - __tls_get_addr - 21% sử dụng CPU:
$ 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 [.] [email protected]
4.82% inet_test_main libinet_test_lib.so [.] [email protected]
0.23% inet_test_main [kernel.kallsyms] [k] 0xffffffffa0165b75
Vì vậy, như bạn có thể nhìn thấy khi một biến địa phương chủ đề là trong một thư viện được chia sẻ (được khai báo tĩnh và chỉ được sử dụng trong một thư viện được chia sẻ) nó khá chậm. Nếu một biến cục bộ thread trong một thư viện được chia sẻ hiếm khi được truy cập, thì nó không phải là một vấn đề đối với performace. Nếu nó được sử dụng khá thường xuyên như trong bài kiểm tra này thì chi phí sẽ rất đáng kể.
những gì đang xảy ra đằng sau hậu trường: http://www.akkadia.org/drepper/tls.pdf .. có ai cảm thấy động cơ để đọc điều này và tóm tắt câu trả lời trong câu trả lời ngắn không? : D –
"Câu chuyện kinh dị" có lẽ là từ TSS (Chủ đề lưu trữ cụ thể) thông qua pthreads_setspecific. TSS chậm hơn TLS, nhưng nếu được thực hiện đúng cách không phải là toàn bộ. –
Tôi có thể cung cấp cho bạn một câu chuyện kinh dị về sự chậm chạp của biến cục bộ _non_ thread (bộ đếm số nguyên đơn giản), được sửa đổi thông qua một số luồng và làm chậm hệ thống xuống để thu thập thông tin do bộ nhớ cache bị rình mò. Làm cho nó thread địa phương và làm một tổng kết của tất cả các địa phương thread ở cuối đã cho tôi một tăng tốc của một yếu tố 100 hoặc tương tự. – hirschhornsalz