2

작은 인스턴스에서 실행되는 WebRole이 있습니다. 이 WebRole에는 많은 양의 파일을 BLOB 저장소에 업로드하는 메서드가 있습니다. Azure 인스턴스 사양에 따르면 작은 인스턴스는 코어 1 개만입니다. 그래서 그 얼룩을 업로드 할 때 Parallel.Foreach는 정기적 인 Foreach에 비해 어떤 이점도 있습니까?작은 푸른 하늘에 Parallel.Foreach 사용

답변

5

blob 저장소 API 및/또는 Stream API의 aysnc 버전 사용에 중점을두면 훨씬 편리해질 수 있으므로 CPU 경계가 아닌 I/O 경계가됩니다. BeginXXX API가있는 곳이라면 어디서나 Task.Factory.FromAsync으로 감싸서 사용하고 거기에서 연속을 사용해야합니다. 특정 경우에는 CloudBlob.BeginUploadFromStream을 사용해야합니다. 처음에 스트림을 얻는 방법도 마찬가지로 중요하므로 비동기 API도 찾아야합니다.

그 후에 작은 인스턴스를 사용하지 못하게하는 유일한 방법은 매체가 200Mbps 인 경우 100Mbps로 제한된다는 것입니다. 그런 다음 다시 탄성 계수를 활용하고 더 많은 처리가 필요할 때 역할 수를 늘리고 일이 진정 될 때 다시 규모를 조정할 수 있습니다.

FromAsync을 사용하여 BeginUploadFromStream으로 전화하는 방법의 예입니다. 자, 동시 처리 조정에 이르기까지 비동기 작업을 시작할 때부터 Parallel :: ForEach를 사용하여 최대 동시성을 제한 할 수는 없습니다. 즉, 동시 스레드를 제한하기 위해 Semaphore이있는 원래 스레드에 정기적 인 foreach를 가짐을 의미합니다. 이 MaxDegreeOfParallelism의 동등 제공 할 것입니다 : 당신은 또한 비동기 소스 스트림을 받고해야 어떻게 표시되지하고이 샘플 지금

// Setup a semaphore to constrain the max # of concurrent "thing"s we will process 
int maxConcurrency = ... read from config ... 
Semaphore maxConcurrentThingsToProcess = new Semaphore(maxConcurrency, maxConcurrency); 

// Current thread will enumerate and dispatch I/O work async, this will be the only CPU resource we're holding during the async I/O 
foreach(Thing thing in myThings) 
{ 
    // Make sure we haven't reached max concurrency yet 
    maxConcurrentThingsToProcess.WaitOne(); 

    try 
    { 
     Stream mySourceStream = ... get the source stream from somewhere ...; 
     CloudBlob myCloudBlob = ... get the blob from somewhere ...; 

     // Begin uploading the stream asynchronously 
     Task uploadStreamTask = Task.Factory.FromAsync(
      myCloudBlob.BeginUploadFromStream, 
      myCloudBlob.EndUploadFromStream, 
      mySourceStream, 
      null); 

     // Setup a continuation that will fire when the upload completes (regardless of success or failure) 
     uploadStreamTask.ContinueWith(uploadStreamAntecedent => 
     { 
      try 
      { 
       // upload completed here, do any cleanup/post processing 
      } 
      finally 
      { 
       // Release the semaphore so the next thing can be processed 
       maxConcurrentThingsToProcess.Release(); 
      } 
     }); 
    } 
    catch 
    { 
     // Something went wrong starting to process this "thing", release the semaphore 
     maxConcurrentThingsToProcess.Release(); 

     throw; 
    } 
} 

을하지만, 예를 들어, 다른 URL을 어딘가에서 해당 스트림을 다운로드 한 경우 , 당신은 그것을 비동기 적으로 없애고 비동기 업로드의 시작을 여기에 계속 연결하고 싶을 것입니다.

저는이 코드가 단순한 Parallel::ForEach을 수행하는 것보다 더 많은 코드임을 알고 있습니다. 그러나 CPU 바인딩 작업을위한 동시성을 쉽게 만들기 위해 Parallel::ForEach이 존재합니다. I/O와 관련하여 비동기 API를 사용하면 CPU 리소스를 최소화하면서 최대 I/O 처리량을 얻을 수있는 유일한 방법입니다.

+0

Parallel.ForEach()를 사용하면 루프의 코드가 매우 간단합니다. CloudBlob.UploadFromStream()을 호출하기 만하면됩니다. 그러나 비동기 메서드를 사용할 때는 덜 명확합니다. CloudBlob.BeginUploadStream()을 Task에 랩핑하고 일반 foreach 루프 내에서 루프의 끝에서 Task.WaitAll()을 호출하여 작업 foreach 파일을 생성해야합니까?(훌륭한 코드 스 니펫을 제공 할 수 있다면) –

+0

아니요, 이것은 같은 것이 아닙니다. Parallel :: ForEach 내부에서 동기식 호출을 수행하면 I/O가 발생하는 동안 해당 작업자 스레드가 차단되어 귀중한 CPU 리소스를 소비하게됩니다. 비동기 패턴을 사용하면 조금 더 많은 작업이 필요하지만 사실상 I/O가 관련되어있을 때 그 가치는 그 이상의 가치가 있습니다. 이것이 C# 5.0이 async 키워드를 추가하고 .NET 4.5의 BCL이이 새로운 Task 기반 비동기 패턴을 기반으로 재 설계되는 이유입니다. 심지어 Windows 8 API조차이 패턴을주의 깊게 따르고 있습니다. 간단한 예제를 추가하겠습니다. –

+0

Parallel :: ForEach는 모든 것을 Tasks로 변환하기 때문에 @Drew에 동의합니다. 왜냐하면 Parallel :: ForEach는 Tasks로 모든 것을 변환하기 때문에 스레드 스케줄링은 루프의 각 반복마다 즉시 스레드를 할당하지 않기 때문입니다. 오히려 스레드가 각각의 CPU 시간을 필요로하므로 스레드 (예 : 코어 당 2 개)로 시작해야한다고 가정합니다. 결국 모든 작업이 I/O를 기다리고 있으며 더 많은 스레드를 할당하지만 시간이 걸릴 것입니다. –

3

코어 수가 Parallel.ForEach()에 의해 생성 된 스레드 수와 직접적인 상관 관계가 없습니다.

약 1 년 전 David Aiken은 Small 인스턴스에서 Parallel.ForEach()의 유무에 관계없이 일부 BLOB + 테이블 액세스에 대해 매우 비공식적 인 테스트를 수행했습니다. 결과는 here입니다. 이 경우, 이었으며, 이는 CPU- 바운드 활동이 아니므로 측정 된 향상이었습니다. BLOB 저장소에 많은 수의 개체를 업로드하고 있기 때문에 성능이 향상 될 것으로 판단됩니다.

3

네, 업로드 할 때마다 네트워크에 연결되므로 스케쥴러가 그들 사이에 하나의 핵심을 공유 할 수 있습니다. (결국 단일 코어, 단일 CPU 컴퓨터가 한 번에 두 가지 이상의 작업을 처리하는 방법입니다.)

비슷한 효과를 위해 비동기 BLOB 업로드 기능을 사용할 수도 있습니다.