Как работает 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, а не макрос.)

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


person xiver77    schedule 27.08.2015    source источник
comment
__thread реализуется по-разному на разных платформах, на некоторых (вы не сказали, для какой из них вы программируете) это может быть реализовано с помощью pthread_getspecific.   -  person fuz    schedule 27.08.2015
comment
Пожалуйста, дайте нам больше информации! Я бы очень хотел решить вашу проблему, но сейчас я недостаточно знаю о том, какую платформу вы используете / как вы компилируете свой код, чтобы дать вам ответ о том, как повысить производительность локального хранилища потоков.   -  person fuz    schedule 27.08.2015


Ответы (2)


Последние 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, скорее всего, будет быстрее.

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

А _thread и thread_local (часто) не волшебным образом переводятся в вызовы pthread_getspecific. Обычно они включают определенный режим адресации и/или регистр (подробности зависят от реализации и связаны с ABI). ; в Linux, я думаю, поскольку x86-64 имеет больше регистров и адресных режимов, его реализация TLS быстрее, чем на i386), с помощью компилятор, компоновщик и система выполнения. Напротив, может случиться так, что некоторые реализации 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:data@ntpoff, %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).

person Basile Starynkevitch    schedule 27.08.2015
comment
Выполняет ли pthread_setspecific больше дополнительной работы, чем встроенный TLS? - person xiver77; 27.08.2015
comment
__thread — это расширение gcc до C11, имеющее ту же семантику, что и _Thread_local C11, на самом деле оно гарантирует немного больше, чем _Thread_local. pthread_getspecific не обязательно включает вызов функции, его можно реализовать как макрос. - person fuz; 27.08.2015
comment
@FUZxxl: это может быть реализовано с помощью макроса (но я думаю, что стандарт требует, чтобы вы могли использовать его через указатель функции), но обычно это не реализуется как макрос - person Basile Starynkevitch; 27.08.2015
comment
@BasileStarynkevitch Standard говорит, что это может быть макрос, и странно, что glibc не реализует его как таковой. - person fuz; 27.08.2015
comment
@xiver77 Не указано, как реализованы __thread и _Thread_local (т.е. встроенный TLS). Реализация может очень хорошо использовать pthread_getspecific и pthread_setspecific для реализации, хотя в обычных UNIX-подобных операционных системах это не так. - person fuz; 27.08.2015
comment
TLS реализован как на i386, так и на amd64 Linux с сегментным регистром (%fs для i386, %gs для amd64). Разница в скорости незначительна. - person fuz; 27.08.2015

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

person fuz    schedule 27.08.2015
comment
Я использую процессор Intel. То есть вы имеете в виду, что gcc использует специальный регистр, такой как регистр указателя стека, но предназначенный для TLS? Делает ли pthread_getspecific то же самое? - person xiver77; 27.08.2015
comment
@xiver77 «я использую процессор Intel» недостаточно информации. Для какой операционной системы и архитектуры вы программируете? Intel производит процессоры с разными архитектурами. На платформах i386, где ABI поддерживает это, регистр сегмента %fp устанавливается на ненулевой базовый адрес, который указывает на локальные данные потока. Я не могу сказать вам, может ли gcc сделать это на вашей платформе, поскольку вы не даете мне достаточно информации. Не могли бы вы также дать мне версию gcc, вызов gcc и выходные данные сборки (используйте переключатель -S)? - person fuz; 27.08.2015
comment
Извините за поздний ответ. Моя платформа — Ubuntu 15.10 i386 gcc 4.9.2. Я также проверю и посмотрю вывод сборки для __thread прямо сейчас. - person xiver77; 27.08.2015
comment
@xiver77 Как вы вызываете gcc? В i386 Linux gcc должен компилировать доступ к __thread переменным, не вызывая pthread_getspecific. Либо другая часть вашего кода вызывает pthread_getspecific, либо происходит что-то странное. - person fuz; 27.08.2015
comment
@xiver77 Эта сборка вообще не вызывает pthread_getspecific. Я предполагаю, что звонки исходят откуда-то еще. - person fuz; 27.08.2015
comment
Кроме того, забавно, что компилятор генерирует %gs относительных доступов, тогда как он должен генерировать %fs относительных доступов. Как вы вызываете gcc? - person fuz; 27.08.2015
comment
Я думаю, это потому, что я установил 32-битный Linux. Кстати, вы, возможно, немного неправильно поняли мой вопрос. Я явно вызывал pthread_getspecific в своей исходной программе. Вот почему мой второй абзац здесь. Я отредактирую свой вопрос, чтобы избежать путаницы. - person xiver77; 27.08.2015
comment
@xiver77 А, понятно. Простите за это. В таком случае я с радостью могу сказать: да, __thread даст вам приличный прирост производительности. - person fuz; 27.08.2015