Встроенные функции WinAPI _Interlocked* для char, короткие

Мне нужно использовать функцию _Interlocked*** для char или short, но в качестве входных данных требуется длинный указатель. Вроде есть функция _InterlockedExchange8, документации по ней не вижу. Похоже, это недокументированная функция. Также компилятор не смог найти функцию _InterlockedAdd8. Буду признателен за любую информацию об этих функциях, рекомендации использовать/не использовать, а также другие решения.

обновление 1

Попробую упростить вопрос. Как я могу заставить это работать?

struct X
{
    char data;
};

X atomic_exchange(X another)
{
    return _InterlockedExchange( ??? );
}

Я вижу два возможных решения

  1. Используйте _InterlockedExchange8
  2. Приведите another к длинному, сделайте обмен и приведите результат обратно к X

Первое, очевидно, плохое решение. Второй выглядит лучше, но как его реализовать?

обновление 2

Что вы думаете о чем-то подобном?

template <typename T, typename U>
class padded_variable
{
public:
    padded_variable(T v): var(v) {}
    padded_variable(U v): var(*static_cast<T*>(static_cast<void*>(&v))) {}
    U& cast()
    {
        return *static_cast<U*>(static_cast<void*>(&var));
    }
    T& get()
    {
        return var;
    }
private:
    T var;
    char padding[sizeof(U) - sizeof(T)];
};

struct X
{
    char data;
};

template <typename T, int S = sizeof(T)> class var;
template <typename T> class var<T, 1>
{
public:
    var(): data(T()) {}
    T atomic_exchange(T another)
    {
        padded_variable<T, long> xch(another);
        padded_variable<T, long> res(_InterlockedExchange(&data.cast(), xch.cast()));
        return res.get();
    }
private:
    padded_variable<T, long> data;
};

Спасибо.


person ledokol    schedule 22.02.2011    source источник
comment
Не могли бы вы рассказать нам, почему вам нужно использовать функции Interlocked*? Очень сложно предлагать решения, не зная, в чем проблема. Даже могучий Интернет не может найти _InterlockedExchange8, за исключением одного поста о Windows DDK.   -  person molbdnilo    schedule 22.02.2011
comment
Что ты имеешь в виду? Мне нужно использовать Interlocked функции для той цели, для которой они созданы - атомарная операция RMW с переменной. _InterlockedExchange8 не может быть найден в Интернете, это то, что я сказал, но компилятор все еще может его найти.   -  person ledokol    schedule 22.02.2011
comment
Если нигде нет документации по функции, это явный признак того, что вам нужно другое решение. Никто не может предложить другие решения, кроме Interlocked*, если вы не скажете, какую проблему вы пытаетесь решить с их помощью. А мне нужно сделать атомный RMW это не ваша проблема, это ваше решение. Если вы объясните, почему вам это нужно, вы, скорее всего, получите предложения.   -  person molbdnilo    schedule 22.02.2011
comment
@molbdnilo: это моя проблема, мне нужно выполнить атомарную операцию RMW с указанной переменной. Я действительно не понимаю, что вы хотите услышать. У меня есть переменная, которую я хочу изменить атомарно из разных потоков. Функция, которая изменяет эту переменную, должна быть потокобезопасной.   -  person ledokol    schedule 22.02.2011
comment
вы написали - did your boss come to you and say "you need to do an atomic RMW operation on a variable". Да, моя задача — реализовать класс, который ведет себя именно так. Это именно та проблема, которую нужно решить, здесь ничего нельзя изменить. Предположим, у меня есть функция swap, которая должна атомарно поменять внутреннюю переменную на другую и вернуть старую. Это класс шаблона, аргумент шаблона может быть int, short, long или какой-либо структурой POD с размером меньше, чем sizeof(long), в противном случае он не реализован.   -  person ledokol    schedule 22.02.2011
comment
@ledokol: значит, если это невозможно, тебя уволят? ;) Дело в том, что вам нужно сделать это, чтобы заставить работать что-то еще. Так что важен не сам атомарный обмен, а другая проблема, которую он решает.   -  person jalf    schedule 22.02.2011


Ответы (4)


Довольно легко создавать 8-битные и 16-битные взаимосвязанные функции, но причина, по которой они не включены в WinAPI, связана с переносимостью IA64. Если вы хотите поддерживать Win64, ассемблер не может быть встроенным, поскольку MSVC больше не поддерживает его. Как внешние функциональные блоки, использующие MASM64, они не будут такими быстрыми, как встроенный код или встроенные функции, поэтому вам будет разумнее изучить продвижение алгоритмов для использования вместо этого 32-битных и 64-битных атомарных операций.

Пример реализации взаимосвязанного API: intrin.asm

person Steve-o    schedule 04.04.2011

Почему вы хотите использовать меньшие типы данных? Таким образом, вы можете разместить их кучу в небольшом пространстве памяти? Это просто приведет к ложному совместному использованию и конфликту строк кэша.

Независимо от того, используете ли вы алгоритмы с блокировкой или без блокировки, идеально иметь ваши данные в блоках размером не менее 128 байт (или любого размера строки кэша на вашем ЦП), которые используются только одним потоком за раз.

person Ben Voigt    schedule 22.02.2011
comment
Существуют функции пользовательского интерфейса, которые возвращают этот тип данных. Если бы я хранил long внутри, мне нужно было бы использовать приведение в каждой функции, которая возвращает эту переменную. Конечно, это тоже решение, но тогда мне нужно подумать, как сделать переносимое преобразование из long в char (порядок байтов может вызвать проблемы с простым преобразованием или memcpy). - person ledokol; 22.02.2011
comment
Да, забыл упомянуть - мне нужно использовать memcpy, так как класс является шаблонным классом, а тип возвращаемого значения может быть нецелочисленным. Это может быть любая структура T с sizeof(T) == 2 или sizeof(T) == 1, поэтому здесь вместо static_cast следует использовать memcpy :( - person ledokol; 22.02.2011
comment
Это звучит как отличная причина не использовать memcpy. memcpy небезопасно использовать для произвольных типов в C++. - person jalf; 22.02.2011
comment
Да, я так и говорю :) Я не могу использовать static_cast, потому что он не будет работать для структур POD, и я не могу использовать memcpy, потому что это небезопасно. Вот почему в моем вопросе я предпочитаю функцию, которая выполняет реальную операцию с char. - person ledokol; 22.02.2011
comment
@ Бен 128 байт? Что ж, если у вас гораздо больше элементов, чем потоков, то вам не понадобятся такие большие элементы для многих алгоритмов. - person David Heffernan; 22.02.2011
comment
@David: Если у вас есть менее 128 байтов данных для обработки потока, было бы быстрее (исключая действительно плотные вычисления) просто обрабатывать их последовательно и избегать затрат на запуск потока / заимствование одного из пула потоков. И да, это может быть один элемент, но, скорее всего, это будет набор небольших элементов. Пока размер блока и выравнивание кратны 128 байтам. - person Ben Voigt; 22.02.2011
comment
@Ben Вот пример, о котором я думаю. Рассмотрим очень длинный массив целых чисел (не имеет значения, насколько он широк). Предположим, вы хотите увеличить каждый элемент. Вы делите массив на блоки одинакового размера и позволяете потокам работать с каждым блоком. - person David Heffernan; 22.02.2011
comment
@David: вы не выбираете блоки одинакового размера, вы выбираете размеры блоков, кратные 128 байтам, и выровненные. По крайней мере, если вы хотите увидеть какое-либо ускорение от распараллеливания. Время, необходимое одному потоку для обработки лишних записок на концах, намного меньше, чем время, которое было бы потрачено впустую в условиях конкуренции за кэш. - person Ben Voigt; 22.02.2011
comment
@Ben Если массив достаточно велик, чтобы задействовать потоки, то производительность для записок на конце становится неактуальной. Более того, здесь нет разногласий, потому что, как правило, вы не получаете один поток в конце, в то время как другой начинается. - person David Heffernan; 22.02.2011
comment
@David: Поскольку обрывки в конце незначительны, нет причин НЕ делить работу на границах строк кэша. - person Ben Voigt; 22.02.2011
comment
@ Бен Ну, я согласен с этим. Особенно, если у вас есть более сложный пример, где более вероятно возникновение разногласий. - person David Heffernan; 22.02.2011

Что ж, придется довольствоваться имеющимися функциями. _InterlockedIncrement и `_InterlockedCompareExchange доступны в 16- и 32-разрядном вариантах (последний также в 64-разрядном варианте), и, возможно, несколько других взаимосвязанных встроенных функций также доступны в 16-разрядных версиях, но InterlockedAdd, похоже, не быть, и, кажется, вообще нет встроенных функций/функций Interlocked размером в байт.

Итак... Вам нужно сделать шаг назад и выяснить, как решить вашу проблему без IntrinsicAdd8.

Почему вы работаете с отдельными байтами в любом случае? Придерживайтесь объектов размером int, если у вас нет действительно веской причины использовать что-то меньшее.

person jalf    schedule 22.02.2011
comment
Why are you working with individual bytes in any case? Stick to int-sized objects unless you have a really good reason to use something smaller. У меня есть функция, которая возвращает char (на самом деле это шаблон T›, но она должна возвращать значение параметра шаблона), я не могу изменить эту функцию, мне просто нужно сделать ее атомарной. Актерский состав - это проблема, как описано выше. Есть ли другой способ сделать char из длинного? - person ledokol; 22.02.2011

Создание нового ответа, потому что ваше редактирование немного изменило ситуацию:

  • Используйте _InterlockedExchange8
  • Приведите другой к длинному, сделайте обмен и приведите результат обратно к X

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

Второй тоже не работает, если только X не является типом POD размером long. (и если он не выровнен по границе sizeof(long), и если он не имеет того же размера, что и long)

Чтобы решить эту проблему, вам нужно сузить список возможных типов X. Во-первых, конечно, гарантированно ли это будет тип POD? Если нет, у вас совершенно другая проблема, поскольку вы не можете безопасно обрабатывать типы, отличные от POD, как необработанные байты памяти.

Во-вторых, какие размеры может иметь X? Блокированные функции могут работать с разрядностью 16, 32 и, в зависимости от обстоятельств, 64 или даже 128 бит.

Охватывает ли это все случаи, с которыми вы можете столкнуться?

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

person jalf    schedule 22.02.2011
comment
X — тип POD. Размер ограничен от 1 до 4 байт (умещается в длину). - person ledokol; 22.02.2011
comment
И что именно нужно с этим делать? Поменять местами его с другим как атомарную операцию? Вы также упомянули InterlockedAdd в своем вопросе. - person jalf; 22.02.2011
comment
Только InterlockedExchange (с этим типом объектов). - person ledokol; 22.02.2011
comment
Тогда 1-байтовый случай является единственным действительно сложным. Для других случаев я бы написал шаблонную функцию, которая специализирована с помощью SFINAE для вызова 16- или 32-разрядных версий _InterlockedExchange. Проблема с использованием более широкой версии (скажем, _InterlockedExchange16) для одного байта заключается в том, что его параметр должен быть выровнен по 16-битной границе, которой может не быть байт. Но если вы учтете это и включите либо следующий, либо предыдущий байт в 16-битный фрагмент, то я полагаю, что это, вероятно, сработает, даже если это уродливо. - person jalf; 22.02.2011
comment
Переменная выровнена по границе 4 байт с помощью __declspec (align(4)), но я не понял, что вы предложили использовать для копирования long обратно в X? - person ledokol; 22.02.2011
comment
Я не предлагал вам скопировать его как таковой, а просто написать шаблонную функцию, которая, если тип ввода имеет ширину 16 бит, создает short* и вызывает _InterlockedExchange16, а если она имеет ширину 32 бита, создает long* и звонит _InterlockedExchange32. Что-то вроде boost::enable_if можно использовать для реализации этого. Для фактического преобразования указателей вы можете использовать reinterpret_cast (должно работать на практике, но немного выходит за рамки того, что на самом деле гарантирует стандарт) или static_cast в указатель void, а затем снова из указателя void в длинный/короткий указатель. - person jalf; 22.02.2011
comment
Конечно, если вы используете 16-битную подкачку для данных размером char, то в конечном итоге вы также подкачаете еще один байт памяти. Это гарантированно приемлемо? - person jalf; 22.02.2011
comment
Of course, if you use 16-bit swap on char sized data, then you'll end up swapping another byte of memory as well. Is that guaranteed to be acceptable?: В том-то и проблема... но я думаю, что справлюсь. Просто добавление еще одной фиктивной переменной в класс, я думаю, решит эту проблему. Позвольте мне немного подумать... - person ledokol; 22.02.2011
comment
@jalf: добавлен некоторый код, я думаю, он должен быть достаточно переносимым. Что вы думаете об этом? - person ledokol; 22.02.2011