Использование расширений GCC __sync для переносимой библиотеки C

Я разрабатываю библиотеку C для OS X (10.10.x, которая поставляется с GCC 4.2.x). Эта библиотека должна быть максимально переносимой и не относится к OS X.

Я бы хотел, чтобы у конечных пользователей было меньше головной боли при сборке из исходников. Таким образом, несмотря на то, что проект кодируется как std=c11, чтобы получить некоторые преимущества самого современного C, кажется, что необязательные элементы, такие как atomics, не поддерживаются этой версией GCC.

Я предполагаю, что GNU-Linux и различные BSD конечные пользователи имеют либо (а) более позднюю версию GCC, либо (б) возможность установить самую последнюю и лучшую версию.

Является ли правильным решением полагаться на расширения __sync GCC для требуемой семантики CAS (и т. д.)?


person alphazero    schedule 24.10.2015    source источник
comment
В OS X вам, вероятно, следует выбрать clang, а не gcc. gcc 4.2 действительно устарел. Современные версии clang и gcc имеют расширения __atomic, которые ближе к тому, что предоставляет C11, а начиная с gcc 4.9 или около того у вас действительно есть C11 atomics.   -  person Jens Gustedt    schedule 24.10.2015
comment
Посмотрите trac.mpich.org/projects/openpa/wiki/FAQ или preshing.com/20130505/.   -  person Jeff Hammond    schedule 25.10.2015
comment
Спасибо @Jeff - посмотрю.   -  person alphazero    schedule 27.10.2015


Ответы (1)


Я думаю, вам нужно сделать шаг назад и сначала определить все ваши варианты использования. Достоинства __sync по сравнению с C11 atomics в стороне, лучше сначала определить ваши потребности (т.е. __sync/atomics - это решения, которые не нужны).

Ядро Linux является одним из самых тяжелых и изощренных пользователей блокировок, атомарности и т. д., и атомарность C11 недостаточно мощна для этого. См. https://lwn.net/Articles/586838/.

Например, вам может быть гораздо лучше обернуть вещи парами pthread_mutex_lock/pthread_mutex_unlock. Объявление структуры C11 атомарной не гарантирует атомарный доступ ко всей структуре, а только к ее частям. Итак, если вам нужно, чтобы следующее было атомарным:

glob.x = 5;
glob.y = 7;
glob.z = 9;

Вам лучше обернуть это в пару pthread_mutex_*. Для сравнения, внутри ядра Linux это будут спин-блокировки или RCU. Фактически, вы также можете использовать RCU. Обратите внимание, что выполнение:

CAS(glob.x,5)
CAS(glob.y,7)
CAS(glob.z,9)

не совпадает с парой мьютексов, если вы хотите обновить все или ничего.

Я бы обернул вашу реализацию тонким слоем. Например, лучшим способом может быть __sync на одной арке [скажем, BSD] и атомарность на другой. Абстрагируя это в файл .h с макросами/встроенными строками, вы можете везде писать «общий код» без большого количества #ifdef's.

Я написал структуру/объект кольцевой очереди. Его программа обновления может использовать CAS [для этого я написал свой собственный встроенный ассемблер], pthread_mutex_*, блокировку вращения ядра и т. д. Фактический выбор которых контролировался одним или двумя #ifdef's внутри my_ring_queue.h

Еще одно преимущество абстракции: вы можете передумать в будущем. Предположим, вы сделали ранний выбор __sync или atomics. Вы кодируете это в 200 местах в 30 файлах. Затем наступает «большой упс», когда вы понимаете, что это был неправильный выбор. Происходит много редактирования. Поэтому никогда не помещайте голый [скажем] __sync_val_compare_and_swap ни в один из ваших файлов .c. Поместите его один раз в my_atomics.h как что-то вроде #define MY_CAS_VAL(...) __sync_val_compare_and_swap(...) и используйте MY_CAS_VAL

Вы также можете уменьшить количество мест, требующих блокировки между потоками, используя локальное хранилище потока для определенных вещей, таких как выделение/освобождение подпула.

Вы также можете использовать сочетание CAS и блокировки. Некоторые конкретные виды использования лучше использовать с низкоуровневым CAS, а другие будут более эффективными с парами мьютексов. Опять же, полезно, если вы можете сначала определить свои потребности.

Кроме того, рассмотрите окончательный сценарий катастрофы: компилятор не поддерживает атомарность, а __sync недоступен [или не работает] для архитектуры, в которую вы компилируете. Что тогда?

В этом случае обратите внимание, что все операции __sync могут быть реализованы с использованием пар pthread_mutex. Это ваш аварийный запасной вариант.

person Craig Estey    schedule 24.10.2015
comment
Тот факт, что C11 atomic не работает для Linux, не означает, что вы должны препятствовать его использованию. Linux особенный по сравнению почти со всем остальным и, конечно же, по сравнению с кодом пользовательского пространства, написанным типом пользователя SO, который задает этот вопрос. - person Jeff Hammond; 25.10.2015
comment
Стоит ли мьютекс pthread меньше, чем три атомарных хранилища на любой используемой вами платформе? - person Jeff Hammond; 25.10.2015
comment
@Jeff Джефф, когда C11 вообще делает это, плохо, потому что у него никогда не может быть достаточно глобальной информации, чтобы не выполнять плохую оптимизацию движения кода. Вот что подробно описано по ссылке. Это правильно сделано в lib, потому что вам нужно указать ограничения с явными барьерами, которые компилятор не может определить. И в примере с тройным CAS [рассмотрите возможность изменения 50 элементов в структуре] это не то же самое, потому что вы хотите атомарное обновление struct. С отдельными CAS читатель увидит несогласованное состояние (например, он получает новое значение для x, но старое значение для y/z, потому что данные были привязаны между cas(x) и cas(y)) - person Craig Estey; 25.10.2015
comment
Я прочитал всю эту ссылку и многие ссылки из нее, и я не разделяю вашего параноидального взгляда, особенно в отношении встроенных функций GCC, которые могут быть столь же подвержены плохой оптимизации. - person Jeff Hammond; 25.10.2015
comment
и вы правы насчет обновления структуры, если, конечно, кто-то не использует, например, 16b CAS для одновременного выполнения всех 12-байтовых обновлений :-) - person Jeff Hammond; 25.10.2015
comment
@Джефф Не параноик. Я реализовал то, что OP хочет сделать в коммерческих продуктах [в пользовательском пространстве]. Когда атомные атомы C11 были впервые предложены, MS разбавил их до уровня игрушек [преднамеренный саботаж]. Я занимаюсь блокировкой потоков более 30 лет, так что это голос опыта. Я зарабатываю на жизнь поиском плохих блокировок, условий гонки, проблем с синхронизацией и т. д. Вы предотвращаете неверные движения кода, вставляя asm volatile ::: memory в ключевые точки, но это делает программист. Поместите все это в atomics.h, но оставьте вне языка — это не то место, где это нужно делать. - person Craig Estey; 25.10.2015
comment
Во-первых, давайте проясним одну вещь: встроенный ассемблер не является частью какого-либо языка ISO, и мы полностью полагаемся на благотворительность разработчиков компиляторов — предполагаемых злодеев, которых опасается Линус, — чтобы они работали вообще. Во-вторых, атомарные числа C11, безусловно, являются частью языка через ключевое слово _Atomic, которого должно быть достаточно, чтобы удержать компиляторы от злых дел. Даже если ISO C11 явно не запрещает некоторые извращенные оптимизации, разумно ожидать, что их следует избегать — даже Линус говорит об этом — потому что в противном случае люди найдут лучшие компиляторы. - person Jeff Hammond; 25.10.2015
comment
@Jeff Джефф Вы упустили мою мысль об обновлении 50 вещей, поэтому 16b cas не работает [и нуждается в выравнивании]. Вы серьезно думаете, что я никогда не использовал его раньше? Я запускаю свои собственные примитивы с помощью встроенных функций и встроенного ассемблера. А у ARM нет 16b cas. У него есть только ldex/stex. - person Craig Estey; 25.10.2015
comment
Я не пропустил это. Я согласился с этим и не чувствовал необходимости комментировать, чтобы подтвердить это. Что касается того, что ARM не поддерживает полезные функции, я как сотрудник Intel склонен сказать, что это их проблема, а не моя :-) - person Jeff Hammond; 25.10.2015
comment
@Jeff Как насчет чего-то сильно оспариваемого (например, 200 ядер, борющихся за блокировку или атомарность). В ядре у них есть механизм справедливости (см. спин-блокировки в очереди). В пользовательском пространстве, если вы не можете [неоднократно] получить блокировку или что-то еще, что вы делаете? В Linux вы выполняете системный вызов фьютекса. БСД что-то другое. Но компилятор не должен выбирать это, потому что вам может понадобиться фьютекс для блокировки A, но вы хотите что-то другое для блокировки B. stdio.h поставляется с gcc/glibc, но хотите ли вы, чтобы FILE был встроенным типом компилятора? Поместите все это в atomics.h, который поставляется с gcc/glibc, но оставьте вне lang - person Craig Estey; 25.10.2015
comment
Давайте продолжим обсуждение в чате. - person Jeff Hammond; 25.10.2015
comment
Я ценю комментарий @CraigEstey. Я надеялся избежать зависимости от платформы с помощью макросов препроцессора. - person alphazero; 25.10.2015
comment
@CraigEstey просто p.s. относительно ссылок, которые я, наконец, прочитал. Спасибо, очень информативно. - person alphazero; 29.10.2015
comment
@alphazero Вам это точно будет интересно. Это горячо из прессы: stackoverflow.com/ вопросы/33083270/ - person Craig Estey; 30.10.2015
comment
@CraigEstey Только что увидел это. Спасибо за предупреждение, Крейг. - person alphazero; 02.12.2015
comment
@alphazero Он стал красным или вы только что заметили, просматривая старый список? Причина, по которой я спрашиваю, заключается в том, что я только что получил +10 за несвязанную страницу с той же датой 29 октября. Мне интересно, просто ли это совпадение или, может быть, у SO был сбой в тот день. - person Craig Estey; 02.12.2015
comment
Красный. (Глядя на liburcu и CK, кстати.) - person alphazero; 04.12.2015
comment
@alphazero Также взгляните на документацию ядра Linux по rcu - очень информативно - у них есть сложные потребности в RCU - может дать вам некоторые идеи. Ссылка на видео [youtube] для выступления на cppcon — этот парень знал свое дело. - person Craig Estey; 05.12.2015