В настоящее время у меня проблема с распараллеливанием программы на c ++ с использованием openMP. Я внедряю систему рекомендаций с пользовательским методом совместной фильтрации. Для этого я реализовал класс sparse_matrix в качестве словаря словарей (где я имею в виду своего рода словарь Python). В моем случае, поскольку вставка выполняется только в начале алгоритма, когда данные считываются из файла, я реализовал словарь как вектор библиотеки std пары объектов (ключ, значение) с флагом, указывающим, отсортирован ли вектор. если вектор отсортирован, ключ ищется с помощью бинарного поиска. в противном случае вектор сначала сортируется, а затем ищется. Альтернативно, можно сканировать записи словаря линейно, например, в циклах по всем ключам словаря. Соответствующая часть кода, которая вызывает проблемы, заключается в следующем
void compute_predicted_ratings_omp (sparse_matrix &targets,
sparse_matrix &user_item_rating_matrix,
sparse_matrix &similarity_matrix,
int k_neighbors)
{
// Auxiliary private variables
int user, item;
double predicted_rating;
dictionary<int,double> target_vector, item_rating_vector, item_similarity_vector;
#pragma omp parallel shared(targets, user_item_rating_matrix, similarity_matrix)\
private(user, item, predicted_rating, target_vector, item_rating_vector, item_similarity_vector)
{
if (omp_get_thread_num() == 0)
std::cout << " - parallelized on " << omp_get_num_threads() << " threads: " << std::endl;
#pragma omp for schedule(dynamic, 1)
for (size_t iter_row = 0; iter_row < targets.nb_of_rows(); ++iter_row)
{
// Retrieve target user
user = targets.row(iter_row).get_key();
// Retrieve the user rating vector.
item_rating_vector = user_item_rating_matrix[user];
for (size_t iter_col = 0; iter_col < targets.row(iter_row).value().size(); ++iter_col)
{
// Retrieve target item
item = targets.row(iter_row).value().entry(iter_col).get_key();
// retrieve similarity vector associated to the target item
item_similarity_vector = similarity_matrix[item];
// Compute predicted rating
predicted_rating = predict_rating(item_rating_vector,
item_similarity_vector,
k_neighbors);
// Set result in targets
targets.row(iter_row).value().entry(iter_col).set_value(predicted_rating);
}
}
}
}
В этой функции я вычисляю прогнозируемый рейтинг для ряда целевых пар (пользователь, элемент) (это просто средневзвешенное значение). Для этого я выполняю внешний цикл для целевых пользователей (которые находятся в строках разреженной матрицы целей) и извлекаю вектор рейтинга для текущего пользователя, выполняя двоичный поиск по строкам user_item_rating_matrix. Затем для каждого столбца в текущей строке (т. Е. Для каждого элемента) я извлекаю другой вектор, связанный с текущим элементом, из разреженной матрицы Similarity_matrix. С этими двумя векторами я вычисляю прогноз как средневзвешенное значение их элементов (для подмножества элементов, общих для двух векторов).
Моя проблема заключается в следующем: я хочу распараллелить внешний цикл с использованием openMP. В серийной версии эта функция занимает около 3 секунд. С openMP в 2 потоках это занимает около 2 секунд (что неплохо, так как у меня все еще есть некоторые рабочие дисбалансы во externalloop). При использовании 4 потоков это занимает 7 секунд. Я не могу понять, что является причиной этого замедления. Есть ли у вас какие-либо идеи?
Я уже думал о проблеме и делюсь своими соображениями с вами:
Более подробная информация о функции предиката: Эта функция работает следующим образом. Наименьшее между item_rating_vector и item_simility_vector сканируется линейно, и я выполняю двоичный поиск по самому длинному из двух. Если рейтинг / сходство является положительным, оно считается средневзвешенным.
double predict_rating (dictionary<int, double> &item_rating_vector,
dictionary<int, double> &item_similarity_vector)
{
size_t size_item_rating_vector = item_rating_vector.size();
size_t size_item_similarity_vector = item_similarity_vector.size();
if (size_item_rating_vector == 0 || size_item_similarity_vector == 0)
return 0.0;
else
{
double s, r, sum_s = 0.0, sum_sr = 0.0;
int temp_item = 0;
if (size_item_rating_vector < size_item_similarity_vector)
{
// Scan item_rating_vector and search in item_similarity_vector
for (dictionary<int,double>::const_iterator iter = item_rating_vector.begin();
iter != item_rating_vector.end();
++iter)
{
// scan the rating vector forwards: iterate until the whole vector has
// been scanned.
temp_item = (*iter).get_key();
// Retrieve rating that user gave to temp_item (0.0 if not given)
try { s = item_similarity_vector[temp_item]; }
catch (const std::out_of_range &e) { s = 0.0; }
if (s > 0.0)
{
// temp_item is positively similar to the target item. consider it in the average
// Retrieve rating that the user gave to temp_item
r = (*iter).get_value();
// increment the sums
sum_s += s;
sum_sr += s * r;
}
}
}
else
{
// Scan item_similarity_vector and search in item_rating_vector
for (dictionary<int,double>::const_iterator iter = item_similarity_vector.begin();
iter != item_similarity_vector.end();
++iter)
{
// scan the rating vector forwards: iterate until the whole vector has
// been scanned.
temp_item = (*iter).get_key();
s = (*iter).get_value();
if (!(s > 0.0))
continue;
// Retrieve rating that user gave to temp_item (0.0 if not given)
try { r = item_rating_vector[temp_item]; }
catch (const std::out_of_range &e) { r = 0.0; }
if (r > 0.0)
{
// temp_item is positively similar to the target item: increment the sums
sum_s += s;
sum_sr += s * r;
}
}
}
if (sum_s > 0.0)
return sum_sr / sum_s;
else
return 0.0;
}
}
Более подробная информация об оборудовании: Я запускаю эту программу на Dell XPS15 с четырехъядерным процессором i7 и 16 ГБ ОЗУ. Я выполняю код на виртуальной коробке Linux (я устанавливаю виртуальную машину на использование 4 процессоров и 4 ГБ ОЗУ).
Заранее спасибо,
Pierpaolo
Похоже, у вас может быть ложная проблема с вашей целевой переменной. Ложный общий доступ — это когда разные потоки часто пишут в расположениях рядом (одна и та же строка кэша). Явно устанавливая динамическое расписание с размером порции 1, вы говорите OpenMP, чтобы каждый поток выполнял задачи только по одному элементу за раз, что позволяет различным потокам работать с данными, которые могут находиться рядом друг с другом в целях.
Я бы порекомендовал удалить директиву schedule только для того, чтобы увидеть, как работают планировщик по умолчанию и размер чанка. Затем я бы попробовал как статические, так и динамические графики, существенно меняя размер чанка. Если ваша рабочая нагрузка или аппаратная платформа не сбалансированы, вероятно, победит динамическая, но я все равно попробую статическую.
Ну, я нашел решение проблемы сам: я публикую объяснение для сообщества. В функции предиката я использовал try / catch для обработки ошибок out_of_range, генерируемых моей структурой словаря при поиске ключа, который не содержится в словаре. Я читаю на Являются ли исключения в C ++ действительно медленными эта обработка исключений требует большого объема вычислений в случае, если выбрасывается исключение. В моем случае для каждого вызова предиката я генерировал и обрабатывал несколько ошибок out_of_range. Я просто удалил блок try / catch и написал функцию, которая ищет в словаре и возвращает значение по умолчанию, если этот ключ не существует. Эта модификация произвела ускорение примерно в 2000 раз, и теперь программа хорошо масштабируется относительно количества потоков даже на виртуальной машине.
Спасибо всем вам, и если у вас есть другие предложения, не стесняйтесь!
Pierpaolo