- PVSM.RU - https://www.pvsm.ru -
Исходные данные: Есть простой алгоритм для генерации комбинаций.
Задача: Заставить работать быстрее с минимумом программистчасов
Решение: Parallel, Claster, ThreadTasks
статья может быть дописана
for( int i1cards=0; i1cards< cards.Count(); i1cards++ ) {
for( int i2cards=0; i2cards< cards.Count(); i2cards++ ) {
Card[] _cards = new Card[] { cards[i1cards], cards[i2cards] };
if( result.Exists( pair => pair.Contains(_cards[0]) && pair.Contains(_cards[1]) ) )
continue;
result.Add( _cards );
}
}
if( result.Exists( pair => pair.Contains(_cards[0]) && pair.Contains(_cards[1]) ) ) — так мы исключаем из результирующего списка сгенеренную комбинацию. (Далее это вынесено за пределы цикла для «чистого тестирования циклов» и соответствующим образом изменено)
foreach( Card card1 in cards ) {
foreach( Card card2 in cards ) {
foreach( Card card3 in cards ) {
result.Add( new Card[]{card1,card2,card3} );
}
}
}
Parallel.ForEach( cards, ( card1 ) =>
Parallel.ForEach( cards, ( card2 ) => {
result.Add( new Card[] { card1, card2 } );
} ) );
Загрузило процессор на 100% на несколько минут и свыше 10 потоков (число потоков изменялось на протяжении генерации). recult.Count = разные больше 100,000.
ParallelOptions po= new ParallelOptions() {
MaxDegreeOfParallelism = 8,
};
Это так сказать глобальные настройки для работы Parallel. MaxDegreeOfParallelism — ограничивает максимально допустимым числом потоков (в нашем случае ограничили 8 потоками)
Загрузка процессора все те же 100% и 10 потоков (с последующим увеличением числа потоков) и отработка около секунды.
Partitioner.Create( cards, true ).AsParallel().ForAll( ( card1 ) =>
Partitioner.Create( cards, true ).AsParallel().ForAll( ( card2 ) =>
Partitioner.Create( cards, true ).AsParallel().ForAll( ( card3 ) =>
result.Add( new Card[3] { card1, card2, card3 } )
) ));
Partitioner.Create( cards, true ) — заставляет разбивать на блоки. Т.е. если обычный foreach выполняет по одному вхождению в потоке, то в этом случае будет сразу несколько итераций взято в одном потоке.
Загрузка процессора меньше 5% и потоков выше 50. recult.Count = 113698, 114976. Иногда выскакивает исключилка: «Длина результирующего массива недостаточна» или «Индекс находился вне границ массива.».
Partitioner.Create( cards, true ).AsParallel().WithMergeOptions( ParallelMergeOptions.FullyBuffered ).ForAll( ( card1 ) =>
Partitioner.Create( cards, true ).AsParallel().WithMergeOptions( ParallelMergeOptions.FullyBuffered ).ForAll( ( card2 ) =>
Partitioner.Create( cards, true ).AsParallel().WithMergeOptions( ParallelMergeOptions.FullyBuffered ).ForAll( ( card3 ) =>
result.Add( new Card[3] { card1, card2, card3 } )
) ));
WithMergeOptions( ParallelMergeOptions.FullyBuffered ) — заставляет при распараллеливании использовать настройку объединения и в нашем случае с режимом FullyBuffered, т.е. полностью буферизовать.
Число потоков свыше 50, загрузка процессора меньше 5%. recult.Count = 114988, 133495.
cards.ForEach( ( card1 ) =>
cards.ForEach( ( card2 ) =>
cards.ForEach( ( card3 ) =>
result.Add( new Card[] { card1, card2, card3 } )
) ) );
Отработка мгновенная. Не успел увидеть изменения числа потоков. recult.Count = 140608.
По идее, должно само распараллеливать вычисления (сейчас уже не могу вспомнить где, но определенные операции вида, Select, Where,… другие запросы разработаны microsoft так, что оно само раскидывает на потоки, а использование AsParallel явно указывает распараллеливать.
cards.AsParallel().ForAll( ( card1 ) =>
cards.AsParallel().ForAll( ( card2 ) =>
cards.AsParallel().ForAll( ( card3 ) =>
result.Add( new Card[] { card1, card2, card3 } )
) ) );
Процессор меньше 5% и потоков выше 50. recult.Count = 136339, 135416. Иногда выскакивает исключилка: «Длина результирующего массива недостаточна» или «Индекс находился вне границ массива.» (под час частота появления этих ошибок бывает изумительно большая).
будем использовать Thread, Task, WCF (мультипроцесс сеть (бот-нет)… нужно?..
Мне, как рядовому программисту, не очень понятно по какому же алгоритму работают эти механизмы от разработчиков microsoft. Там где оно должно работать предсказуемо и просто — работает с какой-то магией.
Все что я вычитал из справки к этим классам не содержит информации о том, что обычный for по конечному числу элементов будет выдавать разные результаты, подчас «Очень» разные, возможно, я нарвался на недокументированные возможности, которые можно использовать как «Генератор случайных чисел».
Я «где-то» читал, что Parallel и PLINQ должны сами рассчитывать оптимальное число потоков в зависимости от ресурсов и ресурсоемкости распараллеливаемого алгоритма, но иногда вычисления занимают через чур много времени, а систему даже не загружают и на 5%.
В итоге я вынужден создавать свой «транспорт», чтобы на нем ехать (это значить кто-то зря ест свой хлеб).
некоторых комбинаций — тоже заставило задуматься. По идее самым лучшим вариантом делать такую проверку именно в цикле генерации, НО возникает проблема «отложенных обработок», это значить, что пока совершается очередной поиск совпадения — нужно притормозить все остальные потоки, чтобы не получилось, что пока мы ищем уже изменился результат.
Автор: cybermerlin
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/61073
Ссылки в тексте:
[1] Источник: http://habrahabr.ru/post/224677/
Нажмите здесь для печати.