Немного предыстории - я запускаю следующую настройку:
- i5 8300H (4 ядра, 8 потоков)
- 32 ГБ ОЗУ
- Ubuntu 19.10
- GCC 9.2.1, стандарт C ++ 17
У меня есть менеджер потоков - по сути, объект, которому вы можете передавать некоторые данные, вы даете ему вызываемый объект, а затем вы можете запустить задачу параллельно, и у менеджера потоков есть возможность тайм-аута потоков (если какая-то задача зависает, как это может быть в случае с тем, что я делаю), передавать им данные пакетами и т. д. .
Псевдокод для этого поведения выглядит следующим образом:
function do_tasks(task, data, batch_size, timeout, threads, output_streams):
assert arguments_are_valid()
failed_tasks = []
while(true):
if data.size() == 0:
break
for thread in threads:
if thread.running():
stop_thread(thread)
if thread.results.size() == 0:
failed_tasks <- failed_tasks + thread.given_data
else:
data <- data + thread.given_data(data.begin() + thread.results.size(), thread.given_data.end())
start_thread(thread, task, take_data(data, min(batch_size, data.size()))
wait_for_threads_completed_or_timeout(threads, timeout)
return failed_tasks
Я не использую ничего экзотического, все это выполняется с использованием простого std :: thread, std :: list, std :: future и std :: обещание.
Короче говоря, вы предоставляете потоку свои данные. Когда вы оцениваете, что сделал поток, если весь пакет завершается неудачно (то есть ни один из элементов данных не решен), весь пакет переносится в контейнер failed_tasks, который позже возвращается. Затем эти неудачные пакеты затем разрешаются путем запуска задач с размером batch_size, равным 1 (поэтому, когда время выполнения задачи истекает, это действительно то, что должно быть проверено вручную), но эта часть не важна. Если хотя бы один из элементов данных разрешен, то неразрешенная часть переносится обратно в контейнер данных. Это выполняется до тех пор, пока все элементы данных не будут разрешены или помечены как fail_tasks.
Теперь, обычно, допустим, я запускаю это на 100000 элементов в 7 потоках. То, что происходит, - то, что в первый раз, когда я запускаю это, время ожидания до 2000 элементов. Второй раз тоже что-то похожее, время ожидания 500-2000 элементов. Но вот странная часть - после его запуска несколько раз, я получаю намеченное поведение, около 2-5 задач не выполняются.
Если посмотреть на выполняемую функцию, она может обрабатывать 10500 элементов данных в секунду насредний однопоточный. Его минимальное время выполнения составляет менее наносекунды, в то время как максимальное наблюдаемое время выполнения составляет несколько миллисекунд (оно сопоставляет данные с регулярными выражениями, и существуют последовательности, которые более или менее действуют как DoS-атаки, и, следовательно, могут значительно замедлить выполнение),Запуск в 7 потоков обычно позволяет обрабатывать в среднем 70000 элементов данных в секунду, поэтому эффективность составляет около 95%. Однако, когда происходят первые несколько запусков, это падает до 55000 элементов данных в секунду, что составляет около 75% эффективности, что значительно снижает производительность. Теперь производительность не столь критична (мне нужно обрабатывать 20000 элементов данных в секунду, для чего достаточно двух потоков задачи), но наряду с более низкой производительностью возникает большее число неудачных задач, что заставляет меня подозревать, что проблема заключается всами потоки.
Я прочитал это:
Что на самом деле означает "прогревать" потоки при обработке многопоточности?
, но кажется, чтоповедение вызвано JIT-интерпретатором, чего нет в C ++ во время компиляции. Я знаю о std :: thread overhead, но подозреваю, что он не такой большой. То, что я испытываю здесь, похоже на разминку, но я никогда не слышал о темах, имеющих период разогрева. Это поведение согласуется, даже когда я меняю данные (каждый прогон, другой набор данных), поэтому я подозреваю, что нет никакого кэширования, которое бы ускорило его.
Реализация, вероятно, правильная, она была рассмотрена иофициально проверено. Код в основном на C и C ++ и активно поддерживается, поэтому я подозреваю, что это не ошибка. Но я не смог найти кого-то еще в Интернете с такой же проблемой, поэтому мне стало интересно, есть ли что-то, чего мы пропускаем.
У кого-нибудь есть идея, почему происходит такой разогрев?
РЕДАКТИРОВАТЬ: Работа выполняется следующим образом:
for(ull i = 0; i != batch_size && future.wait_for(nanoseconds(0)) == future_status::timeout; ++i)
{
//do stuff
}
Функция, выполняемая потоком, получает будущее, которое поток может проверить перед выполнением задачи на следующем элементе данных, здесь это называется future.