восстановление памяти без блокировки с помощью 64-битных указателей
Вопрос
Книга Херлихи и Шавита (Искусство многопроцессорного программирования) решение для восстановления памяти использует Java AtomicStampedReference<T>;
.
Чтобы написать его на C ++ для x86_64, я полагаю, требуется по крайней мере 12-байтовая операция подкачки - 8 для 64-битного указателя и 4 для int.
Существует ли аппаратная поддержка x86 для этого, и если нет, какие-либо указания на то, как выполнить восстановление памяти без ожидания без этого?
Решение
Да, есть аппаратная поддержка, хотя я не знаю, доступна ли она библиотекам C ++.В любом случае, если вы не возражаете проделать несколько низкоуровневых трюков с непереносимым языком ассемблера - посмотрите инструкцию CMPXCHG16B в руководствах Intel.
Другие советы
Windows предоставляет вам множество Взаимосвязанные функции они являются атомарными и, вероятно, могут быть использованы для выполнения того, что вы хотите.Аналогичные функции существуют и для других платформ, и я полагаю, что Boost также имеет взаимосвязанную библиотеку.
Ваш вопрос не совсем ясен, и у меня нет ни одной копии "Херлихи и Шавит".Возможно, если вы разработаете или предоставите псевдокод, описывающий, что вы хотите сделать, мы сможем дать вам более конкретный ответ.
Ладно, надеюсь, книга у меня есть,
Для других, которые могут дать ответы, смысл в том, чтобы реализовать этот класс :
class AtomicReference<T>{
public:
void set(T *ref, int stamp){ ... }
T *get(int *stamp){ ... }
private:
T *_ref;
int _stamp;
};
без блокировки, чтобы :
- set() обновляет ссылку и штамп атомарно.
- get() возвращает ссылку и устанавливает *stamp на штамп, соответствующий ссылке.
JDonner, пожалуйста, поправьте меня, если я ошибаюсь.
Теперь мой ответ :Я не думаю, что вы можете сделать это где-то без блокировки (блокировка может быть while(test_and_set() != ..)).Поэтому для этого не существует алгоритма без блокировки.Это означало бы, что можно построить N-регистр без блокировки для любого N.
Если вы посмотрите на книгу pragma 9.8.1, AtomicMarkableReference, которая является такой же, с одним битом вместо целочисленной метки.Автор предлагает "украсть" немного из указателя, чтобы извлечь метку и указатель из одного слова (большинство в кавычках) Это, очевидно, означает, что они хотят использовать для этого один атомарный регистр.
Однако, возможно, есть способ отключить восстановление памяти без ожидания и без этого.Я не знаю.
Да, x64 поддерживает это;вам нужно использовать CMPXCHG16B.
Вы можете немного сэкономить память, полагаясь на тот факт, что указатель будет использовать менее 64 бит.Во-первых, определите compare&set
функция (этот ASM работает в GCC и ICC):
inline bool CAS_ (volatile uint64_t* mem, uint64_t old_val, uint64_t new_val)
{
unsigned long old_high = old_val >> 32, old_low = old_val;
unsigned long new_high = new_val >> 32, new_low = new_val;
char res = 0;
asm volatile("lock; cmpxchg8b (%6);"
"setz %7; "
: "=a" (old_low), // 0
"=d" (old_high) // 1
: "0" (old_low), // 2
"1" (old_high), // 3
"b" (new_low), // 4
"c" (new_high), // 5
"r" (mem), // 6
"m" (res) // 7
: "cc", "memory");
return res;
}
Затем вам нужно будет создать тип помеченного указателя.Я предполагаю 40-битный указатель с шириной строки кэша 128 байт (например, Nehalem).Выравнивание по строке кэша значительно улучшит скорость за счет уменьшения ложного обмена данными, конфликтов и т.д.;это имеет очевидный компромисс с использованием лот в некоторых ситуациях больше памяти.
template <typename pointer_type, typename tag_type, int PtrAlign=7, int PtrWidth=40>
struct tagged_pointer
{
static const unsigned int PtrMask = (1 << (PtrWidth - PtrAlign)) - 1;
static const unsigned int TagMask = ~ PtrMask;
typedef unsigned long raw_value_type;
raw_value_type raw_m_;
tagged_pointer () : raw_m_(0) {}
tagged_pointer (pointer_type ptr) { this->pack(ptr, 0); }
tagged_pointer (pointer_type ptr, tag_type tag) { this->pack(ptr, tag); }
void pack (pointer_type ptr, tag_type tag)
{
this->raw_m_ = 0;
this->raw_m_ |= ((ptr >> PtrAlign) & PtrMask);
this->raw_m_ |= ((tag << (PtrWidth - PtrAlign)) & TagMask);
}
pointer_type ptr () const
{
raw_value_type p = (this->raw_m_ & PtrMask) << PtrAlign;
return *reinterpret_cast<pointer_type*>(&p);
}
tag_type tag () const
{
raw_value_type t = (this->raw_m_ & TagMask) >> (PtrWidth - PtrAlign_;
return *reinterpret_cast<tag_type*>(&t);
}
};
У меня не было возможности отладить этот код, так что вам нужно будет это сделать, но такова общая идея.
Обратите внимание, что в архитектуре x86_64 и gcc вы можете включить 128-битный CAS.Это может быть включено с помощью -mcx16
опция gcc.
int main()
{
__int128_t x = 0;
__sync_bool_compare_and_swap(&x,0,10);
return 0;
}
Скомпилировать с помощью:
gcc -mcx16 file.c
Операция cmpxchg16b обеспечивает ожидаемую операцию, но имейте в виду, что некоторые старые процессоры x86-64 не имеют этой инструкции.
Затем вам просто нужно создать объект со счетчиком, указателем и встроенным в asm кодом.Я написал сообщение в блоге на эту тему здесь:Реализация универсального Двойного сравнения слов и Подкачки
Тем не менее, вам не нужна эта операция, если вы просто хотите предотвратить проблемы с досрочным освобождением и ABA.Указатель опасности более прост и не требует специального asm-кода (при условии, что вы используете атомарные значения C ++ 11). У меня есть репозиторий на bitbucket с экспериментальными реализациями различных алгоритмов без блокировки: Эксперимент без блокировки (имейте в виду, что все эти реализации являются игрушками для экспериментов, а не надежным и проверенным кодом для производства.)