240

아래의 코드 조각의 차이점은 무엇입니까? 둘 다 스레드 풀 스레드를 사용하지 않습니까?

예를 들어 컬렉션의 각 항목에 대해 함수를 호출하려는 경우,

Parallel.ForEach<Item>(items, item => DoSomething(item));

vs

foreach(var item in items)
{
  Task.Factory.StartNew(() => DoSomething(item));
}

4 답변


276

첫 번째 옵션이 훨씬 더 좋습니다.

Parallel.ForEach는 내부적으로Partitioner<T>컬렉션을 작업 항목으로 배포 할 수 있습니다. 항목 당 하나의 작업을 수행하지는 않지만 관련된 오 v 헤드를 줄이기 위해이를 일} 처리하십시오.

두 번째 옵션은 단일Task컬렉션의 항목 당 결과는 (거의) 동일 할 것이지만, 특히 대규모 콜렉션의 경우보다 훨씬 많은 오버 헤드가 필요하므로 전반적인 런타임이 느려집니다.

참고 - 사용 된 파티션 관리자는 적절한 파티션 도구를 사용하여 제어 할 수 있습니다.Parallel.ForEach에 과부하, 원하는 경우. 자세한 내용은 다음을 참조하십시오.사용자 정의 파티션MSDN에서.

주된 차이점은 런타임시 두 번째가 비동기 적으로 작동한다는 것입니다. 다음을 수행하여 Parallel.ForEach를 사용하여 복제 할 수 있습니다.

Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));

이 작업을 수행하면 분할자를 계속 활용할 수 있지만 작업이 완료 될 때까지 차단하지 마십시오.


  • IIRC에서 Parallel.ForEach가 수행하는 기본 파티셔닝은 사용 가능한 하드웨어 스레드 수를 고려하므로 최적의 작업 수를 계산하지 않아도됩니다. Microsoft를 확인하십시오.병렬 프로그래밍의 패턴조; 이 모든 것들에 대한 훌륭한 설명이 있습니다. - Mal Ross
  • @Mal : 일종의 ... 실제로는 파티셔너가 아니라 TaskScheduler의 일입니다. TaskScheduler는 기본적으로 새로운 ThreadPool을 사용합니다. - Reed Copsey
  • 감사. " 전문가가 아니지만 ... "에 남아 있어야한다는 것을 알고있었습니다. " 경고. :) - Mal Ross
  • @ReedCopsey : Parallel.ForEach를 통해 시작된 작업을 래퍼 작업에 연결하는 방법은 무엇입니까? 그래서 래퍼 작업에서 .Wait ()를 호출하면 병렬로 실행되는 작업이 완료 될 때까지 중단됩니다. - Konstantin Tarkus
  • @Tarkus 여러 요청을하는 경우 Parallel 루프의 각 작업 항목에서 HttpClient.GetString을 사용하는 것이 좋습니다. 이미 동시 루프의 내부에 비동기 옵션을 넣을 이유가 없습니다. 일반적으로 ... - Reed Copsey

16

Parallel.ForEach는 루프가 완료 될 때까지 최적화 (새 스레드를 시작하지 않을 수도 있음)하고 루프가 완료 될 때까지 차단하고 Task.Factory는 각 항목에 대한 새 타스크 인스턴스를 명시 적으로 작성하고 완료되기 전에 리턴합니다 (비동기 타스크). Parallel.Foreach는 훨씬 효율적입니다.


74

나는 "Parallel.For"로 "1000000000"번, "Task"객체로 하나를 실행하는 작은 실험을했습니다.

나는 프로세서 시간을 측정하고 Parallel이 더 효율적이라는 것을 발견했다. Parallel.For는 작은 작업 항목으로 작업을 분할하고 최적의 방법으로 모든 코어에서 병렬로 실행합니다. 많은 수의 작업 개체를 만드는 동안 (FYI TPL은 내부적으로 스레드 풀링을 사용함) 각 작업마다 모든 실행을 이동하여 상자에 더 많은 스트레스를 생성합니다. 이는 아래 실험에서 분명합니다.

또한 기본 TPL을 설명하는 작은 비디오를 만들었으며 Parallel.For가 코어를보다 효율적으로 활용하는 방법을 보여주었습니다.http://www.youtube.com/watch?v=No7QqSc5cl8일반 작업 및 스레드와 비교할 때

실험 1

Parallel.For(0, 1000000000, x => Method1());

실험 2

for (int i = 0; i < 1000000000; i++)
{
    Task o = new Task(Method1);
    o.Start();
}

Processor time comparison


  • 더 효율적이고 스레드를 만드는 이유는 비용이 많이 든다. 실험 2는 매우 나쁜 습관이다. - Tim
  • @ Georgi-it는 나쁜 점에 대해 더 많이 이야기합니다. - Shivprasad Koirala
  • 나는 미안하다, 나의 실수, 나는 분명히해야만했다. 나는 1000000000에 루프의 작업 생성을 의미합니다. 오버 헤드는 상상할 수없는 것입니다. Parallel은 한 번에 63 개 이상의 작업을 생성 할 수 없으므로이 경우 더욱 최적화되었습니다. - Georgi-it
  • 이것은 1000000000 개의 작업에 해당됩니다. 그러나 이미지 (반복적으로, 확대 / 축소 프랙탈)를 처리하고 Parallel.For 할 때 마지막 스레드가 끝날 때까지 기다리는 동안 많은 코어가 유휴 상태입니다. 이를 빠르게 수행하기 위해 데이터를 64 개의 작업 패키지로 세분화하고 작업을 작성했습니다. (그런 다음 Task.WaitAll이 완료 될 때까지 대기합니다.) 아이디어는 유휴 스레드가 1-2 스레드가 자신의 (Parallel.For) 할당 된 청크를 완료하는 것을 기다리는 대신 작업 패키지를 픽업하여 작업 완료를 돕는 것입니다. - Tedd Hansen
  • 무엇을 하는가?Mehthod1()이 예에서합니까? - Zapnologica

7

내 생각에 가장 현실적인 시나리오는 작업에 많은 작업이 필요할 때입니다. Shivprasad의 접근 방식은 컴퓨팅 자체보다는 개체 생성 / 메모리 할당에 더 중점을 둡니다. 나는 다음과 같은 방법으로 연구를했다 :

public static double SumRootN(int root)
{
    double result = 0;
    for (int i = 1; i < 10000000; i++)
        {
            result += Math.Exp(Math.Log(i) / root);
        }
        return result; 
}

이 방법의 실행에는 약 0.5 초가 소요됩니다.

Parallel을 사용하여 200 번 호출했습니다.

Parallel.For(0, 200, (int i) =>
{
    SumRootN(10);
});

그런 다음 구식 방식으로 200 번 호출했습니다.

List<Task> tasks = new List<Task>() ;
for (int i = 0; i < loopCounter; i++)
{
    Task t = new Task(() => SumRootN(10));
    t.Start();
    tasks.Add(t);
}

Task.WaitAll(tasks.ToArray()); 

첫 번째 사례는 26656ms에 완료되었으며, 두 번째 사례는 24478ms에 완료되었습니다. 나는 여러 번 반복했다. 매번 두 번째 접근법이 더 빨리 끝납니다.

연결된 질문


최근 질문