Библиотека сайта rus-linux.net
Что каждый программист должен знать о памяти.
Часть 9: Приложения и библиография
Назад | Оглавление | Вперед |
9.3 Измерение издержек, связанных с общим использованием кэш-строк
В этом разделе приводится тестовая программа, которая используется для измерения дополнительных затрат, связанных с использованием одной и той же кэш-строки, в сравнении с случаем, когда используются различные кэш-строк.
#include <error.h> #include <pthread.h> #include <stdlib.h> #define N (atomic ? 10000000 : 500000000) static int atomic; static unsigned nthreads; static unsigned disp; static long **reads; static pthread_barrier_t b; static void * tf(void *arg) { long *p = arg; if (atomic) for (int n = 0; n < N; ++n) __sync_add_and_fetch(p, 1); else for (int n = 0; n < N; ++n) { *p += 1; asm volatile("" : : "m" (*p)); } return NULL; } int main(int argc, char *argv[]) { if (argc < 2) disp = 0; else disp = atol(argv[1]); if (argc < 3) nthreads = 2; else nthreads = atol(argv[2]) ?: 1; if (argc < 4) atomic = 1; else atomic = atol(argv[3]); pthread_barrier_init(&b, NULL, nthreads); void *p; posix_memalign(&p, 64, (nthreads * disp ?: 1) * sizeof(long)); long *mem = p; pthread_t th[nthreads]; pthread_attr_t a; pthread_attr_init(&a); cpu_set_t c; for (unsigned i = 1; i < nthreads; ++i) { CPU_ZERO(&c); CPU_SET(i, &c); pthread_attr_setaffinity_np(&a, sizeof(c), &c); mem[i * disp] = 0; pthread_create(&th[i], &a, tf, &mem[i * disp]); } CPU_ZERO(&c); CPU_SET(0, &c); pthread_setaffinity_np(pthread_self(), sizeof(c), &c); mem[0] = 0; tf(&mem[0]); if ((disp == 0 && mem[0] != nthreads * N) || (disp != 0 && mem[0] != N)) error(1,0,"mem[0] wrong: %ld instead of %d", mem[0], disp == 0 ? nthreads * N : N); for (unsigned i = 1; i < nthreads; ++i) { pthread_join(th[i], NULL); if (disp != 0 && mem[i * disp] != N) error(1,0,"mem[%u] wrong: %ld instead of %d", i, mem[i * disp], N); } return 0; }
Код, представляемый здесь, следует, большей частью, рассматривать в качестве иллюстрации создания программы, в которой измеряются такие эффекты, как затраты, связанные с использованием кэш-строк. Интерес представляет тело циклов в tf
. Внутренняя инструкция __sync_add_and_fetch
, известная компилятору, создает атомарную инструкцию добавления. Во втором цикле мы должны "употребить" результат приращения (с помощью инлайновой инструкции asm
). Реальный код вместо инструкции asm
не подставляется; благодаря этой инструкции компилятор не удаляет из цикла счетчик цикла.
Вторая интересная особенность в том, что программа размещает потоки на конкретных процессорах. В программе предполагается, что процессоры пронумерованы от 0 и до 3, что обычно бывает в случае, если в машине используется четыре или большее число логических процессоров. В программе можно было бы для определения числа используемых процессоров воспользоваться интерфейсами из libNUMA, но это программа-тест предназначена для широкого применения без добавления указанной зависимости. Так или иначе, это достаточно легко исправить.
Назад | Оглавление | Вперед |