Следующая программа является по существу тем же как тем, описанным здесь. Когда я выполняю и компилирую программу с помощью двух потоков (NTHREADS == 2), я добираюсь следующие разы:
real 0m14.120s
user 0m25.570s
sys 0m0.050s
Когда это выполняется со всего одним потоком (NTHREADS == 1), я получаю время выполнения значительно лучше даже при том, что это только использует одно ядро.
real 0m4.705s
user 0m4.660s
sys 0m0.010s
Моя система является двухъядерной, и я знаю, что random_r ориентирован на многопотоковое исполнение, и я вполне уверен, это не блокируется. Когда та же программа запущена без random_r, и вычисление косинусов и синусов используется в качестве замены, поточных двойным образом выполнений версии приблизительно в 1/2 время как ожидалось.
#include
#include
#include
#define NTHREADS 2
#define PRNG_BUFSZ 8
#define ITERATIONS 1000000000
void* thread_run(void* arg) {
int r1, i, totalIterations = ITERATIONS / NTHREADS;
for (i = 0; i < totalIterations; i++){
random_r((struct random_data*)arg, &r1);
}
printf("%i\n", r1);
}
int main(int argc, char** argv) {
struct random_data* rand_states = (struct random_data*)calloc(NTHREADS, sizeof(struct random_data));
char* rand_statebufs = (char*)calloc(NTHREADS, PRNG_BUFSZ);
pthread_t* thread_ids;
int t = 0;
thread_ids = (pthread_t*)calloc(NTHREADS, sizeof(pthread_t));
/* create threads */
for (t = 0; t < NTHREADS; t++) {
initstate_r(random(), &rand_statebufs[t], PRNG_BUFSZ, &rand_states[t]);
pthread_create(&thread_ids[t], NULL, &thread_run, &rand_states[t]);
}
for (t = 0; t < NTHREADS; t++) {
pthread_join(thread_ids[t], NULL);
}
free(thread_ids);
free(rand_states);
free(rand_statebufs);
}
Я смущен, почему при генерации случайных чисел поточная версия двух работает намного хуже, чем единственная потоковая версия, рассматривая random_r предназначена, чтобы использоваться в многопоточных приложениях.
Очень простое изменение для размещения данных в памяти:
struct random_data* rand_states = (struct random_data*)calloc(NTHREADS * 64, sizeof(struct random_data));
char* rand_statebufs = (char*)calloc(NTHREADS*64, PRNG_BUFSZ);
pthread_t* thread_ids;
int t = 0;
thread_ids = (pthread_t*)calloc(NTHREADS, sizeof(pthread_t));
/* create threads */
for (t = 0; t < NTHREADS; t++) {
initstate_r(random(), &rand_statebufs[t*64], PRNG_BUFSZ, &rand_states[t*64]);
pthread_create(&thread_ids[t], NULL, &thread_run, &rand_states[t*64]);
}
приводит к гораздо более быстрому времени работы на моей двухъядерной машине.
Это подтвердило бы подозрение, что оно предназначалось для проверки - что вы изменяете значения в одной и той же строке кэша в двух отдельных потоках, что приводит к конфликту с кешем. Машинная архитектура Херба Саттера - то, что ваш язык программирования никогда не говорил вам, стоит посмотреть , если у вас есть время, если вы еще не знаете об этом, он демонстрирует ложное совместное использование, начиная примерно с 1: 20.
Определите размер строки кэша и создайте данные каждого потока, чтобы они были согласованы с ними.
Немного проще поместить все данные потока в структуру и выровнять ее:
#define CACHE_LINE_SIZE 64
struct thread_data {
struct random_data random_data;
char statebuf[PRNG_BUFSZ];
char padding[CACHE_LINE_SIZE - sizeof ( struct random_data )-PRNG_BUFSZ];
};
int main ( int argc, char** argv )
{
printf ( "%zd\n", sizeof ( struct thread_data ) );
void* apointer;
if ( posix_memalign ( &apointer, sizeof ( struct thread_data ), NTHREADS * sizeof ( struct thread_data ) ) )
exit ( 1 );
struct thread_data* thread_states = apointer;
memset ( apointer, 0, NTHREADS * sizeof ( struct thread_data ) );
pthread_t* thread_ids;
int t = 0;
thread_ids = ( pthread_t* ) calloc ( NTHREADS, sizeof ( pthread_t ) );
/* create threads */
for ( t = 0; t < NTHREADS; t++ ) {
initstate_r ( random(), thread_states[t].statebuf, PRNG_BUFSZ, &thread_states[t].random_data );
pthread_create ( &thread_ids[t], NULL, &thread_run, &thread_states[t].random_data );
}
for ( t = 0; t < NTHREADS; t++ ) {
pthread_join ( thread_ids[t], NULL );
}
free ( thread_ids );
free ( thread_states );
}
с CACHE_LINE_SIZE
64:
refugio:$ gcc -O3 -o bin/nixuz_random_r src/nixuz_random_r.c -lpthread
refugio:$ time bin/nixuz_random_r
64
63499495
944240966
real 0m1.278s
user 0m2.540s
sys 0m0.000s
Или вы можете использовать двойной размер строки кэша и использовать malloc - дополнительное заполнение гарантирует, что измененная память находится в отдельных строках, поскольку malloc имеет выравнивание по 16 (IIRC), а не по 64 байтам.
(Я уменьшил ИТЕРАЦИИ в десять раз вместо того, чтобы иметь дурацко быструю машину)