Нужны мысли по профилированию многопоточности в C на Linux - PullRequest
7 голосов
/ 08 декабря 2011

Сценарий моего приложения такой: я хочу оценить прирост производительности, который можно получить на четырехъядерном компьютере для обработки того же объема данных. У меня есть следующие две конфигурации:

i) 1-процесс: программа без потоков и обрабатывает данные из 1M .. 1G, в то время как система должна была работать только на одном ядре из 4-ядерных.

ii) Процесс с 4 потоками: программа с 4 потоками (все потоки выполняют одну и ту же операцию), но обрабатывает 25% входных данных.

В моей программе для создания 4-х потоков я использовал параметры по умолчанию для pthread (т.е. без какого-либо конкретного pthread_attr_t). Я полагаю, что прирост производительности 4-потоковой конфигурации по сравнению с 1-процессной конфигурацией должен быть ближе к 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%). Однако тенденция увеличения производительности экспоненциально уменьшается с увеличением размера входных данных. Он продолжает уменьшаться до некоторого размера данных до 50M или около того, а затем становится стабильным около 200%. По достижении этой точки он остается практически стабильным даже для 1 ГБ данных.

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

И предложения как это исправить?

Для вашего сведения, я также исследовал поведение threadCreationTime и threadTime для каждого потока, чтобы увидеть, что происходит. Для 1М данных значения этих переменных малы, но с увеличением размера данных обе эти переменные растут экспоненциально (но threadCreationTime должно оставаться почти одинаковым независимо от размера данных, а threadTime должно увеличиваться со скоростью, соответствующей данным в процессе обработки). После продолжения увеличения до тех пор, пока 50M или около того threadCreationTime не станет стабильным и threadTime (точно так же, как падение производительности становится стабильным), и threadCreationTime продолжают увеличиваться с постоянной скоростью, соответствующей увеличению данных, которые будут обрабатываться (что считается понятным).

Как вы думаете, может помочь увеличение размера стека каждого потока, приоритета процесса или пользовательских значений другого типа планировщика (с использованием pthread_attr_init)?

PS: результаты получаются при запуске программ в отказоустойчивом режиме Linux с рутом (то есть минимальная ОС работает без графического интерфейса и сетевых ресурсов).

Ответы [ 2 ]

2 голосов
/ 02 января 2012

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

Только это само по себе может вызвать резкое снижение скорости .

Если памяти достаточно, чтение одного большого блока входных данных всегда будет быстрее, чем чтение данных небольшими фрагментами, особенно из каждого потока. Любые преимущества ввода / вывода от чанкинга (эффекты кэширования) исчезают, когда вы разбиваете их на части. Даже выделение одного большого фрагмента памяти намного дешевле, чем выделение небольших фрагментов много-много раз.

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

В потоке,

  • переключение контекста потоков из-за большого количества потоков может привести к неоптимальному ускорению
  • как уже упоминалось другими, холодный кеш из-за того, что не читает память непрерывно, может вызвать замедление

Но, перечитывая ваш OP, я подозреваю, что замедление связано с вашим вводом данных / выделением памяти. Откуда именно вы читаете свои данные? Какая-то розетка? Вы уверены, что вам нужно выделять память более одного раза в вашем потоке?

Некоторые алгоритмы в ваших рабочих потоках могут быть неоптимальными / дорогостоящими.

0 голосов
/ 16 декабря 2011

Ваша тема начинается с создания?Если это так, то произойдет следующее:

, пока ваш родительский поток создает поток, уже созданный поток начнет работать.Когда вы нажимаете timerStop (таймер ThreadCreation), четверка уже работает в течение определенного времени.Итак, threadCreationTime перекрывается threadTime[i]

Как и сейчас, вы не знаете, что измеряете.Это не решит вашу проблему, потому что, очевидно, у вас есть проблема, так как threadTime не увеличивает линейно, но, по крайней мере, вы не добавите перекрывающиеся времена.

Чтобы получить больше информации, вы можете использовать perfинструмент , если он доступен в вашем дистрибутиве.например:

perf stat -e cache-misses <your_prog>

и посмотрите, что происходит с двухпоточной версией, трехпоточной версией и т. д. *

...