Почему программа, написанная с потоками, работает гораздо медленнее обычной?
С потоками работает медленнее. Время выполнения сильно отличается, как минимум в 2 раза. Программы запускал на виртуалке vmware, на ubuntu последней версии, но я думаю, что дело не в этом.
Ну вот, все и решилось.
у меня в ubuntu в настройках виртуалки стоит только 1 ядро
Понимаете, бессмысленно делать параллельность на одном ядре. Это ядро должно выполнить все то, что у вас делалось в однопоточной программе (и только по очереди - оно же одно!), плюс выполнить всю работу по созданию потоков, плюс - если потоки длинные еще их и переключать.
Так что все понятно. Выигрыша не будет никакого, кроме проигрыша.
Потоков вообще не рекомендуется делать больше, чем ядер - с единственным исключением: когда поток что-то ожидает, скажем, от контроллера, и в это время работает другой поток. Случай не ваш.
А вычислительные потоки будут только мешать друг другу, если их больше, чем число ядер.
Во-первых, нет смысла сравнивать производительность относительно элементарных кусков кода, если они отличаются наличием относительно "тяжелых" операций.
В вашем многопоточном варианте присутствуют вызовы функции strncpy с заведомо большим значением лимита размера: 2000 при том, что ваши слова состоят всего из 2-5 символов. Если вы представляете, что такое strncpy и что эта функция на самом деле делает, то вы должны понимать, что эти вызовы будут делать типичную для stncpy "дурную работу", т.е. заниматься обнулением сравнительно большого неиспользуемого хвоста массива. Учитывая, что ваши входные слова на порядки короче, чем 2000 , уже этого вполне достаточно, чтобы замедлить вашу реализацию с strncpy в разы.
Первый вариант не занимается тупым вываливанием в память почти N * 6K нулей (где N - количество слов), а второй - занимается. Неудивительно, что второй вариант работает намного медленнее.
Во-вторых, вы делаете
при том, что threadData[i].slovo - это просто char [20] (!). То есть каждый такой strncpy просто вылетает за пределы массива и тупо бомбит нулями аж чуть ли не 2K чужой памяти за пределами этого массива. О чем вообще можно говорить в такой программе? Программа мертва чуть менее, чем полностью и какие-то замеры ее производительности - бесмысленны.
Не могу больше на это смотреть :) Вот, набросал вариантик. Надеюсь, не сильно накосячил. Мелочи вылизывать лень.
Важное замечание: данные для всех потоков одни и те же. Изменяемая в них часть - только позиция текущего обрабатываемого потоком слова. Точно так же (в этой же структуре) можно расшарить и аналитику, что там и как считается не вникал. Очевидные обработки ошибок тоже пропущены.
На самом деле тут просто реализация разгребания очереди несколькими потоками, где в качестве очереди выступает массив слов. То же самое можно сделать на основе очереди или стека (в смысле контейнеров таких).
По сути, данная программа подсчитывает количество слов для поиска и сумму длинн найденных слов, которые хотя бы один раз встретились в тексте.
Наверное, для определения схожести текста с набором заданных слов, несколько лучше было бы искать все вхождения слов в текст, определяя таким образом, какая часть текста покрывается данными словами.
Впрочем, вопрос, как мне кажется, скорее относится к эфективной реализации подобных действий в многопоточной программе.
В общем случае, чем больше полезной работы выполняет каждый поток, тем более эффективна будет программа в целом. В частности, надо стараться уменьшить количество синхронизирующих взаимодействий (использование mutex, condition variables и т.п.) между потоками.
К счастью, данная программа по своей сути именно такая, она позволяет показать, как можно независимо разделить всю работу по поиску вхождения слов в текст и подсчет некоторй статистики совершенно без взаимодействия потоков.
Идея состоит в том, что перед поиском мы создадим массив слов и запустим заданное количество потоков, каждый из которых, основываясь на переданном ему его прядковом номере, будет вычислять какие слова из массива он будет искать в тексте, не пересекаясь с другими потоками. То же самое относится и к результатам работы каждого потока, которые тот создает в отведенном для его номера элементе массива, не взаимодействуя с другими потоками.
Затем, первоначальный поток (main) дожидается завершения всех остальных и собирает частичные суммы, накопленные каждым из потоков вместе.
Что же касается демонстрации ускорения подобной многопоточной прграммы над однопоточной, то нужо взять побольше данных (особенно размер текста). (попозже попробую продемонстрировать это и найти точку равной эффективности)
Update
Модифицировал программку. Теперь она сначала выдает результат (время) выполнения в последовательном, а затем в многопоточном варианте. Первый параметр -- количество потоков, второй -- множитель для размера текста (переменная S2[] ). Также добавил в S1[] пару слов для поиска, чтобы равномерно загружать потоки в 4-х (8-ми) ядерном CPU.
В общем, поиграйтесь с различной длиной текста и количеством потоков.
(обратите внимание, что начиная с некоторого большого размера текста (на одной машине 1.5Мб, на другой 8Мбайт), результаты снова становятся практически равными (это играет роль скорость обмена между основной памятью и кэшем процессора))
Update 2 (упрощенный вариант, используем внешние массивы для данных потоков)
Очевидно, что не имеет смысла закладывать больше потоков, чем максимальное количество слов, поэтому константа NWORDS используется для обеих.
Теперь функция потока process несколько меняется и в качестве аргумента получает сразу номер потока (приведенный к void * ).
Также небольшие изменения в main , упрощается создание списка слов, меняются циклы создания потока и сборки результата. (для краткости я полностью выбросил вызов подсчета в одном (главном) потоке)