Проверка блокировки для изменения глобального общего состояния в C с использованием выравнивания Cache-Line

Изменить: ST не позволяет публиковать более двух ссылок для новичков. Извините за недостающие ссылки.

Я пытаюсь уменьшить накладные расходы на блокировку в приложении C, где обнаружение изменений в глобальном состоянии имеет отношение к производительности. Несмотря на то, что в последнее время я много читаю в этой теме (например, от Х. Саттера и многих других), я не уверен в своей реализации. Я хотел бы использовать комбинацию операции типа CAS и DCL для проверки глобальной переменной Cache-Line Aligned, что позволяет избежать ложного обмена, чтобы обновлять локальные данные потоков из данных, разделяемых несколькими потоками. Недостаточная уверенность в основном связана с

  • Мне не удалось интерпретировать документацию GNU на Type-Attributes
  • Кажется, я не могу найти какую-либо литературу и примеры, которые я мог бы легко перевести на C, такие как выравнивание-к-кеш-строка-и-знание-кэш-размер строки на ST или 1 (хотя я, кажется, несколько отвечаю на мой вопрос, я не уверен в своей реализации)
  • мой опыт работы с C ограничен.

Мои вопросы:

  • Документация Type-Attributes гласит:

    Этот атрибут определяет минимальное выравнивание (в байтах) для переменных указанного тип. Например, объявления:

    (см. документацию по атрибутам Type-Attributes для декларации)

    заставляет компилятор застраховать (насколько это возможно), что каждая переменная, тип которой struct Sили more_aligned_int будет выделено и выровнено по крайней мере на границе 8-byte. На SPARC, имеющий все переменные типа struct S, выровненные с границами 8-byte, позволяет компилятор для использования инструкций ldd и std (doubleword load and store) при копировании одного переменная типа struct S на другую, что повышает эффективность во время выполнения.

    Означает ли это, что начало struct S или more_aligned_int всегда будет выровнено с границей 8-byte? Это не значит, что данные будут заполнены, чтобы использовать ровно 64 байта, правильно?

  • Предполагая, что 1. истинно, что каждый экземпляр struct cache_line_aligned (см. пример Пример 1 ниже) выравнивается по границам 64-byte и использует ровно одну строку кэша (предполагая, что строки кэша 64 bytes в длину)

  • Использование typedef для объявления типа не изменяет семантику __attribute__ ((aligned (64))) (см. пример Пример 2 ниже)

  • Мне не нужно использовать aligned_malloc при создании экземпляра struct, если struct объявлена ​​с помощью __attribute__ ...

// Example 1
struct cache_line_aligned {
  int version;
  char padding[60];
} __attribute__ ((aligned (64)));

// Example 2
typedef struct {
  int version;  
  // place '__attribute__ ((aligned (64)))' after 'int version'
  // or at the end of the declaration 
  char padding[60];
} cache_line_aligned2 __attribute__ ((aligned (64)));

И, наконец, эскиз функции, использующей подход с выравниванием по линии с кешем, чтобы эффективно проверить, было ли изменено глобальное состояние каким-либо другим потоком:

void lazy_update_if_changed(int &t_version, char *t_data) {
  // Assuming 'g_cache_line_aligned' is an instance of 
  // 'struct cache_line_aligned' or 'struct cache_line_aligned2' 
  // and variables prefixed with 't_' being thread local 
  if(g_cache_line_aligned.version == t_version) {
    // do nothing and return
  } else {
    // enter critical section (acquire lock e.g. with pthread_mutex_lock) 
    t_version = g_cache_line_aligned.version
    // read other data that requires locking where changes are notified 
    // by modifying 'g_cache_line_aligned.version', e.g. t_data
    // leave critical section
  }
} 

Извините за длинный пост.

Спасибо!

Ответ 1

Когда вы определяете выравниваемый тип, скажем, выровненный по 8-байтным границам, компилятор должен сделать тип, кратный выравниванию (здесь, по 8 байт) по размеру.

Обоснование этого просто. Предположим, вы хотите определить массив этого выровненного типа. Естественно, каждый элемент его также должен быть выровнен. Вот почему может быть отступы.

Вот небольшая демонстрация:

#include <stdio.h>

struct cache_line_aligned {
  int version;
//  char padding[60];
} __attribute__ ((aligned (64)));

int main(void)
{
  struct cache_line_aligned s;
  struct cache_line_aligned a[2];
  printf("sizeof(struct cache_line_aligned) = %d\n", (int)sizeof(struct cache_line_aligned));
  printf("sizeof(s) = %d\n", (int)sizeof(s));
  printf("sizeof(a[0]) = %d\n", (int)sizeof(a[0]));
  printf("sizeof(a) = %d\n", (int)sizeof(a));
  return 0;
}

Выход (ideone):

sizeof(struct cache_line_aligned) = 64
sizeof(s) = 64
sizeof(a[0]) = 64
sizeof(a) = 128

Если вы создаете экземпляр struct cache_line_aligned нединамически (IOW, а не через malloc() и т.д.), как и в приведенном выше коде, он будет выровнен.

Стандарт C (с 1999 года) указывает на malloc(), calloc() и realloc():

The pointer returned if the allocation succeeds is suitably aligned so that
it may be assigned to a pointer to any type of object and then used to
access such an object or an array of such objects in the space allocated
(until the space is explicitly deallocated).

Где any type of object не включает искусственно выровненные/дополненные типы, подобные описанной выше структуре, потому что в стандарте C нет ничего подобного __attribute__ ((aligned (64))). Это расширение GNU. Для динамически выделенных объектов с произвольным выравниванием вы должны использовать соответствующую функцию распределения памяти или выполнить выравнивание вручную (путем выделения большего объема памяти и затем "выравнивания" значения указателя).