Стоимость thread_local

Теперь, когда С++ добавляет хранилище thread_local в качестве языковой функции, мне интересно несколько вещей:

  • Какова будет стоимость thead_local?
    • В памяти?
    • Для операций чтения и записи?
  • Связанный с этим: как Операционные системы обычно реализуют это? Казалось бы, все, что объявлено thread_local, должно быть предоставлено для конкретного потока данных для каждого созданного потока.

Ответы

Ответ 1

Объем памяти: размер переменной * количество потоков или, возможно, (sizeof (var) + sizeof (var *)) * количество потоков.

Существует два основных способа реализации локального хранилища потоков:

  • Используя какой-то системный вызов, который получает информацию о текущем потоке ядра. Sloooow.

  • Использование некоторого указателя, возможно, в регистре процессора, который устанавливается корректно при каждом переключении контекста потока в ядре - одновременно с другими регистрами. Дешевые.

На платформах Intel вариант 2 обычно реализуется через некоторый сегментный регистр (FS или GS, я не помню). Оба GCC и MSVC поддерживают это. Таким образом, время доступа примерно так же быстро, как и для глобальных переменных.

Это также возможно, но я еще не видел его на практике, поскольку это можно реализовать с помощью существующих функций библиотеки, таких как pthread_getspecific. Производительность тогда будет равна 1. или 2., плюс накладные расходы на библиотеку. Имейте в виду, что накладные расходы в варианте 2. + библиотеки все еще намного быстрее, чем вызов ядра.

Ответ 2

Описание того, как это работает на Linux Ули Дреппера (сторонник glibc), можно найти здесь: www.akkadia.org/drepper/tls.pdf

Требование обращаться с динамически загруженными модулями и т.д. делает весь механизм немного запутанным, что, возможно, частично объясняет, почему документ весит на 79 страницах (!).

Использование памяти, каждая переменная per-thread, очевидно, нуждается в ее собственной памяти для каждого потока (хотя в некоторых случаях это можно сделать лениво, так что пространство выделяется только после того, как переменная будет сначала доступна), а затем некоторые дополнительные данные, которые необходимы для таблиц смещения и т.д.

По производительности, дополнительные затраты на доступ к переменной TLS в основном вращаются вокруг получения адреса переменной. На x86 Linux регистр GS используется как начало для получения идентификатора потока на x86-64 FS. Обычно существует несколько разметок указателя и вызов функции (__tls_get_addr) для динамически загружаемого кода. Там также затраты, связанные с созданием нового потока, медленнее, потому что реализация должна выделять пространство и, возможно, инициализировать все TLS-вары (если не делать лениво).

TLS хорош для того, чтобы легко сделать некоторые старые небезопасные шаблоны кода без потоков (думаю, errno), но для нового кода, разработанного с самого начала для многопоточного мира, это очень редко нужно.