Как работает gcc `__thread`?

Как __thread в gcc реализовано? Это просто обертка над pthread_getspecific и pthread_setspecific?

С моей программой, которая использует posix API для TLS, я сейчас разочарован тем, что 30% моей исполняемой программы тратится на pthread_getspecific. Я вызвал его при вводе каждого вызова функции, которому нужен ресурс. Компилятор не оптимизирует pthread_getspecific после оптимизации наложения. Поэтому после того, как функции встроены, код в основном ищет правильный указатель TLS снова и снова, чтобы вернуть тот же самый указатель.

Будет ли __thread помочь мне в этой ситуации? Я знаю, что в C11 есть thread_local, но gcc у меня еще не поддерживается. (Но теперь я вижу, что мой gcc поддерживает _Thread_local, а не макрос.)

Я знаю, что могу просто проверить его и посмотреть. Но теперь мне нужно пойти куда-то еще, и мне хотелось бы узнать лучше об этой функции, прежде чем я попытаюсь довольно сильно переписать.

Ответ 1

Недавний GCC, например. GCC 5 поддерживают C11 и его thread_local (если компилировать, например, gcc -std=c11). Поскольку комментарий FUZxxl, вы можете использовать (вместо C11 thread_local) квалификатор __thread, поддерживаемый более старыми версиями GCC. Читайте о Локальное хранилище потоков.

pthread_getspecific действительно довольно медленный (он находится в библиотеке POSIX, поэтому не предоставляется GCC, но, например, GNU glibc или musl-libc), поскольку он включает вызов функции. Использование переменных thread_local, вероятно, будет быстрее.

Посмотрите исходный код файл MUSL thread/pthread_getspecific.c для примера реализации. Прочитайте этот ответ по соответствующему вопросу.

И _thread и thread_local (часто) не магически переведены на вызовы pthread_getspecific. Обычно они связаны с определенным режимом адреса и/или регистром (подробности специфичны для реализации, связанные с ABI, в Linux я предполагаю, что с x86 -64 имеет больше регистров и режимов адресов, его реализация TLS выполняется быстрее, чем на i386), с помощью compiler, a href= "https://en.wikipedia.org/wiki/Linker_%28computing%29" rel= "nofollow noreferrer" > linker и система времени выполнения. Наоборот, некоторые реализации pthread_getspecific используют некоторые внутренние переменные thread_local (в вашей реализации потоков POSIX).

В качестве примера, компиляция следующего кода

#include <pthread.h>

const extern pthread_key_t key;

__thread int data;

int
get_data (void) {
  return data;
}

int
get_by_key (void) {
  return *(int*) (pthread_getspecific (key));
}

с использованием GCC 5.2 (на Debian/Sid) с gcc -m32 -S -O2 -fverbose-asm приведен следующий код для get_data с использованием TLS:

  .type get_data, @function
get_data:
.LFB3:
  .cfi_startproc
  movl  %gs:[email protected], %eax   # data,
  ret
.cfi_endproc

и следующий код get_by_key с явным вызовом pthread_getspecific:

get_by_key:
 .LFB4:
  .cfi_startproc
  subl  $24, %esp   #,
  .cfi_def_cfa_offset 28
  pushl key # key
  .cfi_def_cfa_offset 32
  call  pthread_getspecific #
  movl  (%eax), %eax    # MEM[(int *)_4], MEM[(int *)_4]
  addl  $28, %esp   #,
  .cfi_def_cfa_offset 4
  ret
  .cfi_endproc

Следовательно, использование TLS с __thread (или thread_local в C11) должно быть, скорее, быстрее, чем при использовании pthread_getspecific (избегая накладных расходов на вызов).

Обратите внимание, что thread_local - это удобный макрос, определенный в <threads.h> (стандартный заголовок C11).

Ответ 2

gcc __thread имеет точно такую ​​же семантику, как C11 _Thread_local. Вы не говорите нам, какую платформу вы программируете, поскольку детали реализации различаются между платформами. Например, на x86 Linux gcc должен скомпилировать доступ к локальным переменным потока в качестве инструкций памяти с префиксом сегмента %fs вместо вызова pthread_getspecific.