Многопоточность медленнее, чем отсутствие потоков C ++ - PullRequest
1 голос
/ 30 января 2020

Я новичок в многопоточном программировании, и я знаю, что несколько подобных вопросов задавались ранее на SO, однако я хотел бы получить ответ, определяющий c мой код.

У меня есть два вектора объектов (v1 и v2), через которые я хочу l oop, и в зависимости от того, соответствуют ли они некоторым критериям, добавьте эти объекты в один вектор, например:

Не многопоточный регистр

std::vector<hobj> validobjs;
int length = 70;

for(auto i = this->v1.begin(); i < this->v1.end() ;++i) {
    if( !(**i).get_IgnoreFlag() && !(**i).get_ErrorFlag() ) {
        hobj obj(*i, length);
        validobjs.push_back(hobj);
    }
}

for(auto j = this->v2.begin(); j < this->v2.end() ;++j) {
    if( !(**j).get_IgnoreFlag() && !(**j).get_ErrorFlag() ) {
        hobj obj(*j, length);
        validobjs.push_back(hobj);
    }
}

Многопоточный регистр

std::vector<hobj> validobjs;
int length = 70;

#pragma omp parallel
{
    std::vector<hobj> threaded1;   // Each thread has own local vector
    #pragma omp for nowait firstprivate(length)
    for(auto i = this->v1.begin(); i < this->v1.end() ;++i) {
        if( !(**i).get_IgnoreFlag() && !(**i).get_ErrorFlag() ) {
            hobj obj(*i, length);
            threaded1.push_back(obj);
        }
    }

    std::vector<hobj> threaded2;  // Each thread has own local vector
    #pragma omp for nowait firstprivate(length)
    for(auto j = this->v2.begin(); j < this->v2.end() ;++j) {
        if( !(**j).get_IgnoreFlag() && !(**j).get_ErrorFlag() ) {
            hobj obj(*j, length);
            threaded2.push_back(obj);
        }
    }

    #pragma omp critical  // Insert local vectors to main vector one thread at a time
    {
        validobjs.insert(validobjs.end(), threaded1.begin(), threaded1.end());
        validobjs.insert(validobjs.end(), threaded2.begin(), threaded2.end());
    }
}

В не многопоточном случае мое общее время, потраченное на выполнение операции, примерно в 4 раза быстрее, чем в многопоточном случае (~ 1,5 с против ~ 6 с).

Я знаю, что критическая директива #pragma omp является падением производительности, но, поскольку я заранее не знаю размер вектора validob js, я не могу полагаться на случайную вставку index.

Итак, вопросы:

1) Подходит ли этот вид операций для многопоточности?

2) Если да, то 1) - выглядит ли многопоточный код разумным?

* 1 028 * 3) Могу ли я что-нибудь сделать, чтобы улучшить производительность, чтобы получить скорость быстрее, чем в случае без ниток?

Дополнительная информация:

  • Приведенный выше код вложен в гораздо большую кодовую базу, которая выполняет 10 000–100 000 итераций (эта l oop не использует многопоточность). Я знаю, что порождающие потоки также влекут за собой снижение производительности, но насколько я знаю, эти потоки поддерживаются до тех пор, пока приведенный выше код снова не будет выполняться, каждая итерация
  • omp_set_num_threads установлена ​​в 32 (I '). м на 32-ядерном компьютере).
  • Ubuntu, g cc 7.4

Ура!

Ответы [ 2 ]

1 голос
/ 30 января 2020

Я не эксперт по многопоточности, но попробую:

Подходит ли этот тип операций для многопоточности?

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

Как и слияние в конце многопоточной версии.

Многопоточный код выглядит разумно?

Я думаю, вы на правильном пути, чтобы каждый поток работал с независимыми данными.

Могу ли я что-нибудь сделать, чтобы улучшить производительность, чтобы получить его быстрее, чем в случае без потоков?

Я вижу несколько моментов, которые могут улучшить производительность:

  1. Векторы должны будут часто менять размер , что дорого. Вы можете использовать reserve () , чтобы заранее зарезервировать память и таким образом уменьшить количество перераспределений (до 0 в оптимальном случае).

  2. То же самое относится и к объединение двух векторов в конце, которое является критической точкой, сначала резерв:

    validobjs.reserve(v1.size() + v2.size());
    

    , затем объединение.

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

  4. Вы также можете попытаться заменить элементы параллельно в результирующем векторе. Это может быть полезно, если создание элемента по умолчанию дешево и копирование немного дороже.

    1. Фильтруйте данные в два потока, как вы делаете сейчас.
    2. Синхронизируйте их и выделите вектор с количеством элементов:

      validobjs.resize(v1.size() + v2.size());
      
    3. Пусть каждый поток вставляет элементы в независимые части вектора. Например, поток 1 записывает в индексы 1 до x, а поток 2 записывает в индексы x + 1 в validobjs.size() - 1

      Несмотря на то, что я не уверен, является ли это полностью законным или если это неопределенное поведение


Вы также можете подумать об использовании std::list (связанный список). Конкатенация связанных списков или удаление элементов происходит в постоянное время, однако добавление элементов происходит немного медленнее, чем в std::vector с зарезервированной памятью.


Это были мои мысли по этому поводу, надеюсь, что-то было полезно в этом.

1 голос
/ 30 января 2020

ИМХО,

Вы копируете каждый элемент дважды: в threadaded 1/2 и после этого в validob js. Это может замедлить ваш код.

Вы можете добавлять элементы в один вектор, используя синхронизацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...