Как получить максимальную производительность, используя Parallel.For / ForEach? (время исполнения включено) - PullRequest
3 голосов
/ 12 октября 2011

Я пытаюсь распараллелить мой инструмент для разбора веб-страниц, но прирост скорости кажется очень минимальным. У меня i7-2600K (8 ядер с гиперпоточностью).

Вот код, который покажет вам идею. Я только показываю Parallel.ForEach, но вы поняли:

List<string> AllLinks = this.GetAllLinks();
ConcurrentDictionary<string, Topic> AllTopics = new ConcurrentDictionary<string, Topic> ( );

int count = 0;
Stopwatch sw = new Stopwatch ( );
sw.Start ( );

Parallel.ForEach ( AllLinks, currentLink =>
{
    Topic topic = this.ExtractTopicData ( currentLink );
    this.AllTopics.TryAdd ( currentLink, topic );

    ++count;

    if ( count > 50 )
    {
        Console.WriteLine ( sw.ElapsedMilliseconds );
        count = 0;
    }
} );

Я получаю эти тайминги:

Standard foreach loop:
24582
59234
82800
117786
140315

2 links per second


Paralel.For:

21902
31649
41168
49817
59321


5 links per second

Paralel.ForEach:
10217
20401
39056
49220
58125

5 links per second

Во-первых, почему время запуска значительно ниже в Parallel.For?

Кроме этого, параллельные циклы дают мне 2,5-кратную скорость по сравнению со стандартным циклом foreach. Это нормально?

Есть ли настройка, которую я могу установить, чтобы параллельные циклы могли использовать все ядра?

EDIT:

Вот в значительной степени то, что делает ExtractTopicData:

HtmlAgilityPack.HtmlWeb web = new HtmlWeb ( );
HtmlAgilityPack.HtmlDocument doc = web.Load ( url );
IEnumerable<HtmlNode> links = doc.DocumentNode.SelectNodes ( "//*[@id=\"topicDetails\"]" );

var topic = new Topic();

foreach ( var link in links )
{
    //parse the link data
}

1 Ответ

9 голосов
/ 12 октября 2011

Краткое прочтение HtmlAgilityPack.HtmlWeb подтверждает, что он использует синхронный WebRequest API.Поэтому вы помещаете долго выполняющиеся задачи в ThreadPool (через Parallel).ThreadPool предназначен для кратковременных операций, которые быстро возвращают поток обратно в пул.Блокировка на IO - это большое нет-нет.Учитывая нежелание ThreadPool запускать новые потоки (потому что он не предназначен для такого использования), вы будете ограничены этим поведением.

Асинхронно извлекайте ваш веб-контент ( см. здесь и здесь , чтобы узнать, как правильно использовать API, вам придется самостоятельно исследовать ... ), чтобы вы не связывали ThreadPool с задачами блокировки.Затем вы можете передать декодированный ответ в HtmlAgilityPack для анализа.

Если вы действительно хотите поднять производительность, вам также следует учитывать, что WebRequest не способен выполнять асинхронный поиск DNS.IMO, это ужасный недостаток в дизайне WebRequest.

Метод BeginGetResponse требует выполнения некоторых задач синхронной настройки (например, разрешение DNS, обнаружение прокси-сервера и соединение через сокет TCP), прежде чем этот метод станетасинхронный.

Это делает высокую производительность загрузки настоящей PITA.Примерно в это же время вы можете подумать о том, чтобы написать свою собственную библиотеку HTTP, чтобы все могло выполняться без блокировки (и, следовательно, истощать поток ThreadPool).

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

...