Я реализовал шаблон рабочей очереди в C (в расширении Python), и я разочарован производительностью.
У меня есть симуляция со списком частиц («элементов»), и я измеряю время, затрачиваемое на выполнение всех вычислений, необходимых для временного шага, и записываю это вместе с количеством вовлеченных частиц. Я выполняю код на четырехъядерном Hyper-Threading i7, поэтому я ожидал, что производительность возрастет (время, необходимое для падения) с числом потоков до 8, но вместо этого в самой быстрой реализации нет рабочих потоков (функции просто выполняется вместо добавления в очередь,) и с каждым рабочим потоком код становится все медленнее и медленнее (на шаг больше, чем время для реализации без потоков для каждого нового потока!) Я быстро взглянул на использование моего процессора приложение, и кажется, что Python никогда не превышает 130% загрузки процессора, независимо от того, сколько потоков работает. Машина имеет достаточный запас мощности выше, общее использование системы составляет около 200%.
Теперь часть моей реализации очереди (показанной ниже) выбирает элемент случайным образом из очереди, поскольку выполнение каждого рабочего элемента требует блокировки двух элементов, и аналогичные элементы будут находиться рядом друг с другом в очередь. Таким образом, я хочу, чтобы потоки выбирали случайные индексы и атаковали разные биты очереди, чтобы минимизировать конфликты мьютексов.
Теперь я прочитал, что моя первоначальная попытка с rand()
будет медленной, потому что мои случайные числа не были потокобезопасными (имеет ли это предложение смысл? Не уверен ...)
Я пробовал реализацию как с random()
, так и с drand48_r
(хотя, к сожалению, последний, кажется, недоступен на OS X), но без статистики.
Возможно, кто-то еще может сказать мне, что может быть причиной проблемы? код (рабочая функция) приведен ниже, и вы можете кричать, если вы думаете, что какие-либо функции или конструкторы queue_add также могут быть полезны для просмотра.
void* worker_thread_function(void* untyped_queue) {
queue_t* queue = (queue_t*)untyped_queue;
int success = 0;
int rand_id;
long int temp;
work_item_t* work_to_do = NULL;
int work_items_completed = 0;
while (1) {
if (pthread_mutex_lock(queue->mutex)) {
// error case, try again:
continue;
}
while (!success) {
if (queue->queue->count == 0) {
pthread_mutex_unlock(queue->mutex);
break;
}
// choose a random item from the work queue, in order to avoid clashing element mutexes.
rand_id = random() % queue->queue->count;
if (!pthread_mutex_trylock(((work_item_t*)queue->queue->items[rand_id])->mutex)) {
// obtain mutex locks on both elements for the work item.
work_to_do = (work_item_t*)queue->queue->items[rand_id];
if (!pthread_mutex_trylock(((element_t*)work_to_do->element_1)->mutex)){
if (!pthread_mutex_trylock(((element_t*)work_to_do->element_2)->mutex)) {
success = 1;
} else {
// only locked element_1 and work item:
pthread_mutex_unlock(((element_t*)work_to_do->element_1)->mutex);
pthread_mutex_unlock(work_to_do->mutex);
work_to_do = NULL;
}
} else {
// couldn't lock element_1, didn't even try 2:
pthread_mutex_unlock(work_to_do->mutex);
work_to_do = NULL;
}
}
}
if (work_to_do == NULL) {
if (queue->queue->count == 0 && queue->exit_flag) {
break;
} else {
continue;
}
}
queue_remove_work_item(queue, rand_id, NULL, 1);
pthread_mutex_unlock(work_to_do->mutex);
pthread_mutex_unlock(queue->mutex);
// At this point, we have mutex locks for the two elements in question, and a
// work item no longer visible to any other threads. we have also unlocked the main
// shared queue, and are free to perform the work on the elements.
execute_function(
work_to_do->interaction_function,
(element_t*)work_to_do->element_1,
(element_t*)work_to_do->element_2,
(simulation_parameters_t*)work_to_do->params
);
// now finished, we should unlock both the elements:
pthread_mutex_unlock(((element_t*)work_to_do->element_1)->mutex);
pthread_mutex_unlock(((element_t*)work_to_do->element_2)->mutex);
// and release the work_item RAM:
work_item_destroy((void*)work_to_do);
work_to_do = NULL;
work_items_completed++;
success = 0;
}
return NULL;
}