Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Аппаратное обеспечение

Что каждый программист должен знать о памяти.

Часть 9: Приложения и библиография


Назад Оглавление Вперед

12 Введение в libNUMA

Хотя большая часть информации, которая нужна программистам для оптимального планирования потоков, соответствующего распределения памяти и т. д., имеется в наличии, но получить ее крайне тяжело. Без всякого сомнения, доступ к необходимым для этого функциям можно получить с помощью имеющейся библиотеки поддержки NUMA (libnuma в пакете numactl системы RHEL/Fedora).

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

Для того, чтобы следовать советам, предлагаемым в настоящем документе, крайне необходимы, функциональные возможности этой новой библиотеки. Это единственная причина, почему она здесь упоминается. Библиотека (на момент написания статьи) еще не закончена, отсутствует подробное ее писание, она не доведена до хорошего общего состояния и еще (широко) не распространяется. В будущем она может существенно измениться. В настоящее время она доступна по ссылке http://people.redhat.com/drepper/libNUMA.tar.bz2.

Интерфейсы этой библиотеки в значительной степени зависят от информации, экспортируемой файловой системой /sys. Если файловая система не смонтирована, многие функции просто не будут работать, либо они будут предоставлять неточную информацию. Это, в частности, важно помнить в случае, если процесс выполняется в изолированной среде chroot.

В настоящее время интерфейсный заголовок библиотеки содержит следующие определения:

typedef memnode_set_t;

#define MEMNODE_ZERO_S(setsize, memnodesetp)
#define MEMNODE_SET_S(node, setsize, memnodesetp)
#define MEMNODE_CLR_S(node, setsize, memnodesetp)
#define MEMNODE_ISSET_S(node, setsize, memnodesetp)
#define MEMNODE_COUNT_S(setsize, memnodesetp)

#define MEMNODE_EQUAL_S(setsize, memnodesetp1, memnodesetp2)

#define MEMNODE_AND_S(setsize, destset, srcset1, srcset2)
#define MEMNODE_OR_S(setsize, destset, srcset1, srcset2)
#define MEMNODE_XOR_S(setsize, destset, srcset1, srcset2)

#define MEMNODE_ALLOC_SIZE(count)
#define MEMNODE_ALLOC(count)
#define MEMNODE_FREE(memnodeset)

int NUMA_cpu_system_count(void);
int NUMA_cpu_system_mask(size_t destsize, cpu_set_t *dest);

int NUMA_cpu_self_count(void);
int NUMA_cpu_self_mask(size_t destsize, cpu_set_t *dest);

int NUMA_cpu_self_current_idx(void);
int NUMA_cpu_self_current_mask(size_t destsize, cpu_set_t *dest);

ssize_t NUMA_cpu_level_mask(size_t destsize, cpu_set_t *dest,
                            size_t srcsize, const cpu_set_t *src,
                            unsigned int level);

int NUMA_memnode_system_count(void);
int NUMA_memnode_system_mask(size_t destsize, memnode_set_t *dest);

int NUMA_memnode_self_mask(size_t destsize, memnode_set_t *dest);

int NUMA_memnode_self_current_idx(void);
int NUMA_memnode_self_current_mask(size_t destsize, memnode_set_t *dest);

int NUMA_cpu_to_memnode(size_t cpusetsize, const cpu_set_t *cpuset,
                        size_t __memnodesize, memnode_set_t *memnodeset);
int NUMA_memnode_to_cpu(size_t memnodesize, const memnode_set_t *memnodeset,
                        size_t cpusetsize, cpu_set_t *cpuset);

int NUMA_mem_get_node_idx(void *addr);
int NUMA_mem_get_node_mask(void *addr, size_t size,
                           size_t destsize, memnode_set_t *dest);

Макросы MEMNODE_* похожи по форме и функциональным возможностям на макросы CPU_*, которые приведены в разделе 6.4.3. Есть макросы, в которых нет вариантов _S; для них всех требуется параметр, указывающий размер. Тип memnode_set_t эквивалентен типу cpu_set_t, но на этот раз это узлы памяти. Заметим, что число узлов памяти может не иметь ничего общего с числом процессоров, и наоборот. В каждом узле памяти может быть много процессоров, либо процессоров может вообще не быть. Поэтому размер битовых наборов динамически выделяемых узлов памяти не должен определяться числом процессоров.

Вместо этого необходимо воспользоваться интерфейсом NUMA_memnode_system_count. Он возвращает количество узлов, зарегистрированных в настоящее время. С течением времени это число может увеличиваться или уменьшаться. Однако чаще всего оно будет оставаться постоянным и, следовательно, интерфейс хорошо использовать для определения размеров битовых наборов узла памяти. Распределение памяти, осуществляемое макросами MEMNODE_ALLOC_SIZE, MEMNODE_ALLOC и MEMNODE_FREE, опять же похоже на то, что делают макросы CPU_.

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

Функции NUMA_cpu_* позволяют работать с наборами процессоров. В частности, интерфейсы лишь делают существующие функциональные возможности доступными под новым именем. NUMA_cpu_system_count возвращает количество процессоров в системе, вариант NUMA_CPU_system_mask возвращает битовую маску с соответствующим битовым набором — функцию, к которой иначе добраться невозможно.

NUMA_cpu_self_count и NUMA_cpu_self_mask возвращают информацию о процессорах, на которых может работать текущий поток. NUMA_cpu_self_current_idx возвращает индекс процессора, используемого в настоящее время. Эта информация в момент, когда возвращается, может быть уже устаревшей из-за решений, касающихся планирования, которые могут быть приняты в ядре; нужно всегда учитывать, что она может быть неточной. NUMA_cpu_self_current_mask возвращает ту же самую информацию и устанавливает соответствующий бит в битовом наборе.

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

Еще более специализированной информацию возвращают функции NUMA_memnode_self_current_idx и NUMA_memnode_self_current_mask. Возвращаемой информацией является узел памяти, подключенный к процессору, на котором в текущий момент работает поток. Точно также, как и для функций NUMA_cpu_self_current_*, когда эта информация возвращается, она может быть уже устаревшей; ее можно использовать только во вспомогательных целях.

Функцию NUMA_cpu_to_memnode можно использовать для отображения множества процессоров в набор непосредственно подключенных узлов памяти. Если в наборе процессоров установлен только один бит, можно определить, какой узел памяти принадлежит какому процессору. В настоящее время в Linux не поддерживаются варианты, когда одному процессору принадлежит более одного узла памяти; в будущем это, теоретически, может измениться. Чтобы получить отображение в другом направлении, можно воспользоваться функцией NUMA_memnode_to_cpu.

Если память уже распределена, то иногда полезно знать, где память размещена. Это то, что программист может определить с помощью функций NUMA_mem_get_node_idx и NUMA_mem_get_node_mask. Первая функция возвращает индекс узла памяти, на котором размещена страница, соответствующая адресу, указанному в качестве параметра, или, если память под страницу еще не выделена, будет размещена в соответствие с установленной политикой. Вторая функция может выполнять работу для целого диапазона адресов; она возвращает информацию в виде битового набора. Значение, возвращаемое функцией, указывает число используемых узлов памяти.

В оставшейся части настоящего раздела мы рассмотрим несколько примеров использования этих интерфейсов. Во всех случаях мы пропускаем обработку ошибок и не рассматриваем случай, когда количество процессоров и/или узлов памяти слишком велико для типов cpu_set_t и memnode_set_t, соотвественно. Создание надежного кода мы оставим в качестве упражнения для читателя.


Назад Оглавление Вперед