Применяется ли модель памяти C / ++ к самой операции atomi c? - PullRequest
0 голосов
/ 02 августа 2020

Я не понимаю, когда актуальна модель памяти C / ++, даже после чтения G CC wiki .

Мой код - это библиотека ввода-вывода, которая позволяет получение / возврат буфера из пула и использование его для asyn c IO. Однако даже после того, как буфер возвращен в пул, он не является бесплатным, если не завершена фактическая операция ввода-вывода.

Каждый буфер имеет структуру с флагами состояния:

#define IO_FLAG_IN_USE    1 // a consumer has taken ownership of the buffer
#define IO_FLAG_IN_FLIGHT 2 // the buffer is in use by the system for async IO

Потребитель запрашивает буфер с io_getbuf и ждет с sem_wait. Есть два способа, которыми буфер может стать доступным:

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

void io_completion(struct bufinfo *buf) {
    if(!__atomic_or_fetch(&buf->flags, ~IO_FLAG_IN_FLIGHT, ...))
        sem_post(semaphore);
}

void io_putbuf(struct bufinfo *buf) {
    if(!__atomic_or_fetch(&buf->flags, ~IO_FLAG_IN_USE, ...))
        sem_post(semaphore);
}

Но я не уверен, какую модель памяти указать - имеет ли это значение?

tl; dr

Применяется ли модель памяти к самим операциям atomi c (load->or->return) или только для операций, предшествующих / следующих за встроенными модулями atomi c?

Ответы [ 2 ]

0 голосов
/ 03 августа 2020

Что касается самой переменной atomi c , то модели памяти __ATOMIC_RELAXED всегда достаточно между двумя операциями atomi c. Это гарантирует, что между ними будет какой-то фиксированный порядок и что они не будут использовать кешированные значения .

Вот почему вы можете использовать __ATOMIC_RELAXED для простого увеличения счетчика, см. запись Relaxed ordering на cppreference.com

В приведенном примере, если буферы, задействованные в коде примера, являются «простыми» буферами, которые не имеют состояния (они имеют фиксированный размер и т. c.), А потребляющий поток (io_getbuf) не заботится о содержимом буфера (он просто использует его для нового чтения), то других зависимостей нет, и вы можете использовать __ATOMIC_RELAXED.

Потребность в моделях памяти возникает тогда, когда задействованы другие зависимости - если буферы содержат метаданные, такие как буфер size, и , этот размер мог быть изменен (например, realloc) на освобождающий поток тогда __ATOMIC_RELAXED не гарантирует, что потребляющий поток увидит обновление поля size. Точно так же, если это был не просто пул потоков, а настройка производителя / потребителя, то потребитель должен быть уверен, что содержимое буфера действительно синхронизировано и было записано до использования буфера - для этого потребуется другая память. модель.

0 голосов
/ 03 августа 2020

Я полагаю, вы спрашиваете, какое свойство (-а) порядок памяти использовать, и спрашиваете, в частности, о G CC builtin __atomic_and_fetch() (обратите внимание на написание), который атомарно изменяет указанный участок памяти с помощью побитовой операции and над скаляром, имеющим тип не - _Atomic, и возвращает результат (atomi c чтение / изменение / запись). Альтернативы порядка памяти и результирующее поведение соответствуют модели памяти C ++.

Обратите внимание, что это G CC -изм. C имеет типы atomi c и операции atomi c с C11, с тем же набором альтернатив порядка памяти, что и C ++, но встроенный __atomic_or_fetch() и его братья и сестры отделены от этого и G CC -specifi c.

Я не уверен, какую модель памяти указать - имеет ли значение?

Да, конечно, иначе не было бы быть альтернативными вариантами на выбор.

Применяется ли модель памяти к самим операциям atomi c (load-> or-> return) или только для операций, предшествующих / следующих за atomi c встроенные?

Свойство порядка памяти описывает отношения, если таковые имеются, между чтением и записью, выполняемым операцией atomi c с одной стороны и необязательно atomi c читает и записывает одни и те же и другие ячейки памяти на другом. Если вы не уверены, какой порядок памяти использовать, тогда вы должны использовать __ATOMIC_SEQ_CST. Это обеспечивает самые строгие ограничения и соответствует порядку памяти по умолчанию для операций C ++ atomi c.

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

Я не уверен, что было представлено достаточно информации для полного выполнения этого анализа, но мы можем видеть, по крайней мере, что каждая из операций atomi c должна наблюдать эффект другой, поэтому каждая из них требует семантики получения и выпуска с относительно пораженной области памяти. То есть вам нужно как минимум __ATOMIC_ACQ_REL заказ, что всего на один шаг ниже __ATOMIC_SEQ_CST. Используйте последнее, потому что это безопаснее, особенно с учетом того, что вы не уверены в связанных с этим тонкостях. Это для подсистемы ввода-вывода, поэтому, даже если первого будет достаточно, любое увеличение производительности, которое вы можете увидеть от его использования, вряд ли будет заметно.

ОБНОВЛЕНИЕ:

Поскольку, очевидно, вышеизложенное не совсем понятно, еще раз:

Применяется ли модель памяти к самим операциям atomi c (load-> or-> return) или актуально только для операций, предшествующих / следующих за встроенными модулями atomi c?

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

То есть выбранный порядок памяти Параметр влияет на

  • , существуют ли отношения «происходит до» между записью в затронутую область памяти и чтением этой ячейки atomi c op другими потоками;

  • существуют ли отношения «происходит до» между записью atomi c op в затронутое место хранения и другими чтениями из этого места другими потоками;

  • есть ли отношения «происходит до» между чтением atomi c op затронутого места хранения и другими действиями, выполняемыми тем же потоком; и

  • существуют ли отношения «происходит до» между записью atomi c op затронутого места хранения и другими действиями, выполняемыми тем же потоком.

Обратите внимание, что я говорят: «влияет», а не «определяет». На эти факторы также влияет порядок памяти других операций atomi c, использование объектов и функций синхронизации, детали других выполняемых операторов и выражений, оцениваемых всеми потоками, а также капризы планирования потоков во время каждого конкретного run (по крайней мере).

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

Полный объем требуемых вами гарантий неясен, но вам, по крайней мере, нужно, чтобы отношения между чтениями и записями затронутого местоположения одной функцией и данными другой функцией были связаны между собой, без указания порядка c, налагаемого вызовы этих функций. Для этого требуется как минимум __ATOMIC_ACQ_REL порядок, но, опять же, один шаг до __ATOMIC_SEQ_CST, вероятно, лучший выбор, не в последнюю очередь потому, что он может быть необходимым в свете другого программного кода, который не показан.

...