Сценарий моего приложения выглядит следующим образом: я хочу оценить прирост производительности, которого можно достичь на четырехъядерном компьютере при обработке того же объема данных. У меня есть следующие две конфигурации:
i) 1-процесс: программа без какой-либо потоковой передачи и обрабатывает данные от 1M .. 1G, в то время как система предполагала запуск только одного ядра из своих 4-ядер.
ii) 4-поточный процесс: программа с 4-мя потоками (все потоки выполняют одну и ту же операцию), но обрабатывают 25% входных данных.
В моей программе для создания 4 потоков я использовал параметры pthread по умолчанию (т.е. без какого-либо конкретного pthread_attr_t). Я считаю, что прирост производительности конфигурации с четырьмя потоками по сравнению с конфигурацией с одним процессом должен быть ближе к 400% (или где-то между 350% и 400%).
Я профилировал время, потраченное на создание потоков, как показано ниже:
timer_start(&threadCreationTimer);
pthread_create( &thread0, NULL, fun0, NULL );
pthread_create( &thread1, NULL, fun1, NULL );
pthread_create( &thread2, NULL, fun2, NULL );
pthread_create( &thread3, NULL, fun3, NULL );
threadCreationTime = timer_stop(&threadCreationTimer);
pthread_join(&thread0, NULL);
pthread_join(&thread1, NULL);
pthread_join(&thread2, NULL);
pthread_join(&thread3, NULL);
Так как увеличение размера входных данных может также увеличить требования к памяти каждого потока, поэтому загрузка всех данных заранее определенно не работоспособный вариант. Следовательно, чтобы не увеличивать требования к памяти для каждого потока, каждый поток считывает данные небольшими порциями, обрабатывает их и считывает следующие порции, обрабатывает их и так далее. Следовательно, структура кода моих функций, выполняемых потоками, выглядит следующим образом:
timer_start(&threadTimer[i]);
while(!dataFinished[i])
{
threadTime[i] += timer_stop(&threadTimer[i]);
data_source();
timer_start(&threadTimer[i]);
process();
}
threadTime[i] += timer_stop(&threadTimer[i]);
Переменная dataFinished [i]
помечена true
процессом, когда он получил и обработал все необходимые данные . Process ()
знает, когда это делать: -)
В основной функции я вычисляю время, затрачиваемое на 4-поточную конфигурацию, как показано ниже:
execTime4Thread = max (threadTime [0] , threadTime [1], threadTime [2], threadTime [3]) + threadCreationTime
.
Прирост производительности вычисляется просто по формуле
gain = execTime1process / execTime4Thread * 100
Проблема: При небольшом размере данных от 1 до 4 МБ прирост производительности обычно хороший (от 350% до 400%). Однако тенденция увеличения производительности экспоненциально уменьшается с увеличением размера входных данных. Он продолжает уменьшаться до тех пор, пока некоторый размер данных не достигнет 50 МБ или около того, а затем станет стабильным около 200%. Достигнув этой точки, он остается почти стабильным даже для 1 ГБ данных.
Мой вопрос: может ли кто-нибудь предложить основную причину такого поведения (т.е., производительность падает вначале, но остается стабильной позже)?
И предложения, как это исправить?
Для вашей информации, я также исследовал поведение threadCreationTime
и threadTime
для каждого потока, чтобы увидеть, что происходит. Для 1M данных значения этих переменных малы, но с увеличением размера данных обе эти две переменные увеличиваются экспоненциально (но threadCreationTime
должно оставаться почти таким же, независимо от размера данных и threadTime
должен увеличиваться со скоростью, соответствующей обрабатываемым данным). После продолжения увеличения примерно до 50 МБ threadCreationTime
становится стабильным, а threadTime
(точно так же, как падение производительности становится стабильным) и threadCreationTime
продолжают увеличиваться с постоянной скоростью, соответствующей увеличению в обрабатываемых данных (что считается понятным).
Как вы думаете, может ли помочь увеличение размера стека каждого потока, приоритета процесса или пользовательских значений другого типа параметров планировщика (с использованием pthread_attr_init
)?
PS: Результаты получаются во время работы программы под Linux в отказоустойчивом режиме с root (т.е. минимальная ОС работает без графического интерфейса и сетевого оборудования).