Squeak.ru - шаблоны программирования

Лучший способ атомарно обновить два члена структуры?

У меня есть структура, содержащая два элемента данных:

struct A{
    short b;
    short c;
};

и вектор, содержащий многие из этих структурных объектов:

std::vector<A> vec;

во время выполнения элементы данных A.b и A.c из объектов структуры устанавливаются в ноль / значение x. Однако b и c необходимо изменять одновременно - одно нельзя обновлять отдельно друг от друга. Я планировал использовать атомарный compare_exchange_weak() для обновления.

Я не уверен, следует ли мне представлять каждую структуру как std::atomic<A> в векторе, или я должен поместить объединение внутри структуры, чтобы объединить два коротких замыкания в один uint32_t, а затем изменить это:

union A {
    struct {
         short b;
         short c;
    };

    uint32_t d;
};

Какое было бы лучшее решение? Стоит ли хранить вектор:

std::vector<uint32_t>

и после доступа к каждому элементу переинтерпретируйте_приведение к A, чтобы получить d?

Я хочу, чтобы блокировка была как можно менее навязчивой.

Переносимость не требуется, это будет для Linux, 64-разрядная версия, x86, компилятор GCC 4.8+

02.12.2014

  • Я бы объявил b и c частными и написал одну функцию, которая меняет их обоих. 02.12.2014
  • Вы не можете использовать std::atomic<> внутри контейнеров (атомики нельзя копировать или перемещать). Если количество элементов в векторе фиксировано и известно заранее, вы можете построить вектор с правильным количеством элементов, если вы никогда не пытаетесь вставлять / стирать элементы. 02.12.2014
  • Почему reinterpret_cast из uint32_t (что было бы неопределенным поведением) вместо сохранения типа объединения в векторе и непосредственного чтения / записи членов? Это зависит от каламбура типов, но это поддерживается многими компиляторами. Это не сделает обновления атомарными, для этого вам нужно использовать атомарные операции. 02.12.2014
  • Определены ли struct без заполнения, если они являются частью union? В противном случае ваше предложение выглядит довольно рискованным ... 02.12.2014
  • Лично я бы объединил b и c в один атомарный интегральный тип правильного размера. Используйте побитовые операции для установки различных частей этого типа. Ваш союз не будет переносным из-за структурной упаковки. 02.12.2014
  • @Bathsheba, я могу не выровнять два 16-битных члена по 2-байтовым границам? 02.12.2014
  • Не переносным способом, нет. Если вы жертвуете портативностью, вы также можете прибегнуть к сборке и использовать замок шины. 02.12.2014
  • @filmor, не допускается наличие отступов в начале стандартной структуры макета, и очень маловероятно, что между двумя шортами на любой общей платформе есть отступы. Чтобы это проверить, можно использовать static_assert. 02.12.2014
  • Что происходит при изменении размера vector? Он будет копировать, и атомарность не гарантируется. Почти наверняка вам не всегда нужна гарантия, о которой вы просите: когда вам нужна эта гарантия? Кто еще и как получает доступ к данным? 02.12.2014

Ответы:


1

Если оборудование, на которое вы ориентируетесь, не поддерживает двойное сравнение и замену (что, вероятно, не так), я думаю, у вас есть только два портативных решения:

  1. Введите блокировку более высокого уровня (мьютекс или спин-блокировку в зависимости от ваших предпочтений) и перенесите все операции на b и c в рамках полученной блокировки. Мьютекс тяжелый, но std::atomic_flag свободен от блокировок и очень легкий даже в ситуациях с высоким уровнем конкуренции.

  2. Объедините оба элемента в один std::atomic<int> и разделите этот int на short с помощью битовой маскировки. Обратите внимание, что для этого требуется sizeof(int) >= 2 * sizeof(short). Используйте целочисленные типы фиксированного размера, если вам нужно это сделать.

Чтобы определить, какое решение самое быстрое, конечно же, тесты.

Если вы знаете, сколько struct A вам понадобится во время компиляции, я бы посоветовал поместить их в std::array. Если вы этого не сделаете, std::vector в порядке, пока это число остается постоянным на протяжении всего времени существования вектора. В противном случае, поскольку std::atomic<T> нельзя копировать или перемещать, вам нужно написать свой собственный конструктор копирования / перемещения для struct A.

02.12.2014
  • Я должен был сказать - переносимости не требуется. 02.12.2014
  • В справочнике CPP даже есть пример спин-блокировки, реализованной с использованием atomic_flag. 02.12.2014
  • @Park а что, если я помещу структуру A в необработанный массив в стиле C? 02.12.2014
  • Если вы знаете количество элементов во время компиляции, я бы предложил вместо этого использовать std::array. Если вы этого не сделаете, std::vector в порядке, пока это число остается постоянным на протяжении всего времени существования вектора. В противном случае вам придется написать конструктор копирования или перемещения. 02.12.2014

  • 2

    Я рекомендую заключить переменные в класс с помощью геттера и сеттера, защищенного мьютексом, и сделать переменные приватными.

    Использование объединения может вызвать непредвиденные функциональные возможности, основанные на архитектуре компьютера и флагах компилятора.

    РЕДАКТИРОВАТЬ Результаты выполнения простой программы, хранящей значения данного типа структуры (Linux 32bit, x86):

    • Простой магазин (вообще без защиты) -> ~ 4000 мкс
    • Охраняемый магазин Mutex -> ~ 12000 мкс
    • Использование объединения с атомным полем агрегации -> ~ 21000 мкс
    02.12.2014
  • Mutex слишком тяжелый. Это должно быть очень быстро :) 02.12.2014
  • Вы проверяли его в своей установке на удар? Простой тест (1000 000 хранилищ для переменной данного типа) я получил: -простое хранилище (также известное как отсутствие защиты) - ›~ 4000 долларов США - охраняемое хранилище мьютекса -› ~ 12000 долларов США - атомное хранилище с использованием объединения - ›~ 21000 мы тестировали на Linux 32 бит, x86 02.12.2014

  • 3

    Просто сделайте union достаточно большого атомарного типа. Это то, что я использую (фрагмент кода не идеально переносим, ​​использование типов <cstdint> вместо short и int, безусловно, было бы предпочтительнее, но для меня это достаточно хорошо, как есть), и он отлично работает хорошо и надежно, так как ... практически навсегда:

    union A {
        struct {
             short b;
             short c;
        };
    
        std::atomic<int> d;
    };
    

    (На самом деле, моя реализация немного сложнее: я по привычке оборачиваю все это в другой struct, поэтому A - это struct, содержащий union, а не union. Традиционно union имел странные ограничения на конструкторы, моя первоначальная реализация предшествует C ++ 0x, и моему A нужен конструктор. Но, конечно, при использовании <atomic> C ++ 11 эти соображения полностью устаревают, поскольку этих искусственных ограничений больше не существует)

    Обратите внимание, что std::atomic может быть свободным от блокировки, но не гарантируется (кроме bool). На практике для чего-либо размером int или short он не блокируется на каждой "серьезной, без шуток" архитектуре, а на большинстве современных архитектур он не блокируется и для чего-то размером с указатель (хотя существуют исключения, в частности, самое первое поколение чипов x86_64 от AMD).

    02.12.2014
  • Я должен был сказать - переносимости не требуется. 02.12.2014
  • @user: Если вы точно знаете, какой размер int и short на вашей платформе, и не планируете переносить код на другую, то это прекрасно (поэтому я использую int, а не int32_t) . Но даже если это соображение или требование, изменения незначительны :-) 02.12.2014
  • Дэймон, как бы вы использовали это с compare_and_exchange? Вы бы сохранили вектор ‹A›, а затем вызывали compare_and_exchange на d? Вам нужно будет загрузить неатомарную версию d в качестве аргумента в compare_and_exchange? 02.12.2014
  • Да, вы бы прочитали значение, например, vec[i].d (или iter->d) во временный (expected), а затем выполните что-то вроде vec[i].d.compare_exchange_weak(expected, desired);. Вы также можете использовать сильную версию, но на самом деле это не принесет особой пользы. Поскольку он может потерпеть неудачу в любом случае, вы должны проверить результат и выполнить цикл в любом случае, поэтому, хотя слабая версия может ложно выйти из строя, на самом деле нет большой разницы (но слабая может быть быстрее). Также обратите внимание, что вы должны восстановить expected в случае сбоя операции (она будет перезаписана!). Забыть об этом - распространенный источник боли. 02.12.2014
  • Я почти уверен, что std::atomic<int *> свободен от блокировок в базовой версии x86-64. Вы думаете о том, что CMPXCHG16B не является базовым (потому что он отсутствовал в K8)? Это не нужно для compare_exchange_weak одиночного указателя; 64-битный CMPXCHG отлично подходит для этого, и компиляторы выдают это даже без -mcx16. (См. этот ответ для аналогичного трюка с объединением для отдельно атомарного или атомарный доступ в целом к ​​паре указателей (или указатель + флаг), например atomic<short> b,c. 26.10.2016
  • Также обратите внимание, что в отличие от C, в C ++ не гарантируется переносимость записи одного члена объединения, а затем чтения другого, поэтому это переносимо только для компиляторов, где это нормально (например, любой вариант GNU C и, возможно, многие другие). 26.10.2016
  • @PeterCordes: Сказать, что нет гарантии, действительно очень снисходительно. Программа, выполняющая это, вызывает неопределенное поведение (с исключением общего префикса, которое, однако, скорее всего, здесь не применяется). Тем не менее, это прагматическое решение, которое отлично работает при очевидных предположениях, сделанных здесь, например, два short не имеют дополнительных отступов между ними, а два short имеют размер одного int. Как правило, для объединений только POD или в основном типов POD, таких как атомарное целое число, этот тип UB просто работает. Очевидно, что он выйдет из строя и сгорит, учитывая тип, который выделяет ресурсы. 26.10.2016
  • @Damon: потенциальные проблемы, о которых я говорил, того же рода, что вы можете получить при нарушении строгого псевдонима (например, прочитав второй short с помощью (reinterpret_cast<atomic<short> *>(&d)+1)->load(). Программа может показаться нарушающей причинно-следственную связь, и в конечном итоге может произойти сохранение одного из shorts , но не отражаться при чтении atomic<int> сразу после этого. Очевидно, что макеты должны перекрываться так, как вы ожидаете, но это еще не все, что вам нужно, чтобы гарантировать, что набирание текста через объединение работает. 26.10.2016

  • 4

    Поскольку переносимость не является проблемой, блокировка настолько навязчива, насколько вы этого хотите («навязчивый» означает петлю CAS или чрезмерную блокировку).

    В вашем случае вы можете напрямую использовать атомарные встроенные функции.

    AFAIK, вы должны иметь возможность смешивать размеры слов, чтобы работать непосредственно с b и c, а также с a. Я никогда этого не делал, поэтому не могу сказать, будет ли это работать надежно на всех x86. Как всегда: прототип и тест, тест, тест!

    02.12.2014
  • std::atomic<uint32_t> нельзя скопировать, поэтому будет сложно вставить A в vector<A> 02.12.2014
  • Вы можете скопировать atomic с помощью загрузки в его конструктор. Если вам нужно выполнить пакетное копирование одного вектора в другой, вы можете использовать reinterpret_cast, при условии, что вы можете гарантировать, что записи будут сброшены до повторного использования любых атомарных операций. 02.12.2014
  • Ребят просто обновите - переносимости не требуется. См. Нижнюю часть Q. 02.12.2014
  • Встроенные команды, на которые вы ссылаетесь, являются устаревшими, они были заменены gcc. gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html 02.12.2014
  • Новые материалы

    Угловая структура архитектуры
    Обратите внимание, что эта статья устарела, я решил создать новую с лучшей структурой и с учетом автономных компонентов: https://medium.com/@marekpanti/angular-standalone-architecture-b645edd0d54a..

    «Данные, которые большинство людей используют для обучения своих моделей искусственного интеллекта, поставляются со встроенным…
    Первоначально опубликовано HalkTalks: https://hacktown.com.br/blog/blog/os-dados-que-a-maioria-das-pessoas-usa-para-treinar-seus-modelos-de-inteligencia-artificial- ja-vem-com-um-vies-embutido/..

    Сильный ИИ против слабого ИИ: различия парадигм искусственного интеллекта
    В последние годы изучению и развитию искусственного интеллекта (ИИ) уделяется большое внимание и прогресс. Сильный ИИ и Слабый ИИ — две основные парадигмы в области искусственного интеллекта...

    Правильный способ добавить Firebase в ваш проект React с помощью React Hooks
    React + Firebase - это мощная комбинация для быстрого и безопасного создания приложений, от проверки концепции до массового производства. Раньше (знаете, несколько месяцев назад) добавление..

    Создайте API с помощью Python FastAPI
    Создание API с помощью Python становится очень простым при использовании пакета FastAPI. После установки и импорта вы можете создать приложение FastAPI и указать несколько конечных точек. Каждой..

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

    Получить бесплатный хостинг для разработчиков | Разместите свой сайт за несколько шагов 🔥
    Статические веб-сайты — это веб-страницы с фиксированным содержанием и его постоянным содержанием. Но теперь статические сайты также обрабатывают динамические данные с помощью API и запросов...