2010-06-06 3 views
1

우리는 백업 생성에 사용되는 프로그램이 매우 간단합니다. 그것을 병렬 처리하려고하지만 AggregateException 내에서 OutOfMemoryException이 발생합니다. 원본 폴더 중 일부는 매우 크며 프로그램을 시작한 후 약 40 분 동안 충돌하지 않습니다. 어디서부터 시작해야할지 모르겠다. 아래의 코드는 모든 코드의 정확한 덤프이며, 디렉토리 구조와 예외 로깅 코드는 없다. 어디서부터 시작해야할지 조언 해주세요.Parallel.For System.OutOfMemoryException

using System; 
using System.Diagnostics; 
using System.IO; 
using System.Threading.Tasks; 

namespace SelfBackup 
{ 
class Program 
{ 

static readonly string[] saSrc = { 
    "\\src\\dir1\\", 
    //... 
    "\\src\\dirN\\", //this folder is over 6 GB 
}; 
static readonly string[] saDest = { 
    "\\dest\\dir1\\", 
    //... 
    "\\dest\\dirN\\", 
}; 

static void Main(string[] args) 
{ 
Parallel.For(0, saDest.Length, i => 
{ 
    try 
    { 
     if (Directory.Exists(sDest)) 
     { 
      //Delete directory first so old stuff gets cleaned up 
      Directory.Delete(sDest, true); 
     } 

     //recursive function 
     clsCopyDirectory.copyDirectory(saSrc[i], sDest); 
    } 
    catch (Exception e) 
    { 
     //standard error logging 
     CL.EmailError(); 
    } 
}); 
} 
} 

/////////////////////////////////////// 
using System.IO; 
using System.Threading.Tasks; 

namespace SelfBackup 
{ 
static class clsCopyDirectory 
{ 
    static public void copyDirectory(string Src, string Dst) 
    { 
     Directory.CreateDirectory(Dst); 

     /* Copy all the files in the folder 
      If and when .NET 4.0 is installed, change 
      Directory.GetFiles to Directory.Enumerate files for 
      slightly better performance.*/ 
     Parallel.ForEach<string>(Directory.GetFiles(Src), file => 
     { 
      /* An exception thrown here may be arbitrarily deep into 
       this recursive function there's also a good chance that 
       if one copy fails here, so too will other files in the 
       same directory, so we don't want to spam out hundreds of 
       error e-mails but we don't want to abort all together. 
       Instead, the best solution is probably to throw back up 
       to the original caller of copy directory an move on to 
       the next Src/Dst pair by not catching any possible 
       exception here.*/ 
      File.Copy(file, //src 
         Path.Combine(Dst, Path.GetFileName(file)), //dest 
         true);//bool overwrite 
     }); 

     //Call this function again for every directory in the folder. 
     Parallel.ForEach(Directory.GetDirectories(Src), dir => 
     { 
      copyDirectory(dir, Path.Combine(Dst, Path.GetFileName(dir))); 
     }); 
    } 
} 

스레드 디버그 창에는 예외 발생시 417 개의 작업자 스레드가 표시됩니다.

편집 : 복사는 한 서버에서 다른 서버로 진행됩니다. 저는 이제 마지막 Paralell.ForEach를 사용하여 코드를 실행하려고합니다. 정규 foreach로 변경되었습니다.

+0

일반적으로 디스크 A에서 디스크 B로 복사 하시겠습니까? 아니면 동일한 디스크의 한 위치에서 다른 위치로 복사 하시겠습니까? –

답변

2

의견에 대한 의견을 아직받지 못했기 때문에 여기에 몇 가지 추측을합니다.

작업 (병렬 foreach에서 수행되는 작업 단위 인 작업)이 지정된 시간보다 오래 걸리므로 많은 양의 작업자 스레드가 여기에서 발생하는 것으로 추측됩니다. 따라서 기본 ThreadPool이 커지고 있습니다 thread의 수 이것은 ThreadPool이 풀을 증가시키는 알고리즘을 따르므로 새로운 태스크가 기존의 장기 실행 태스크에 의해 차단되지 않도록합니다. 현재 사용중인 모든 스레드가 0.5 초 동안 바쁘면 풀에 더 많은 스레드를 추가하기 시작합니다. 그러나 모든 작업이 오래 실행되고 추가하는 새로운 작업이 기존 작업을 더 오래 실행하게 만들면 문제가 발생할 것입니다. 이것이 아마도 디스크 스레 싱이나 네트워크 IO가 느려서 (네트워크 드라이브가 포함 된 경우) 많은 수의 작업자 스레드가 표시되는 것입니다.

파일이 한 디스크에서 다른 디스크로 복사되거나 같은 위치의 다른 디스크로 복사되는 것으로 추측됩니다. 이 경우 문제에 스레드를 추가하는 것이 도움이되지 않습니다. 원본 및 대상 디스크에는 한 세트의 헤드 만 있으므로 여러 작업을 동시에 수행하려고하면 실제로 작업 속도가 느려질 수 있습니다.

  • 디스크 헤드가 모든 곳에 돌출되어 있습니다.
  • 디스크 \ OS 캐시가 자주 무효화 될 수 있습니다.

이것은 병렬 처리에 큰 문제가 아닐 수도 있습니다.

귀하의 코멘트에 대한 답변에서

, 당신은 속도 더 작은 데이터 세트에 다중 스레드를 사용하여 얻는 경우에, 당신이 당신의 병렬 foreach는, 예를 들어,에 사용되는 스레드의 최대 수를 낮추는 실험 할 수 업데이트

ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 2 }; 

Parallel.ForEach(Directory.GetFiles(Src), options, file => 
{ 
    //Do stuff 
}); 

디스크 스 래싱은 일반적으로 병렬화의 이점을 무효화 할 수 있습니다. 그것을 가지고 놀고 결과를 측정하십시오.

+0

@chibacity 통찰력을 가져 주셔서 감사합니다. 나는 작은 디렉토리 (네트워크 드라이브)에 대해 몇 가지 테스트를 실시했으며 30 초에서 5 ~ 6 초까지 속도를 향상시키는 것으로 보입니다. 나는 거대한 6GB dir에 그것을 놓았을 때 속도가 절반으로 빨라지기를 기대했다. 당신의 논리는 이해가됩니다. –

+0

디스크 스 래싱이 일반적인 경우 parallelizm의 이점을 무효화 할 수 있지만 접근 방식에 약간의 마일리지가있을 수 있습니다. 내 대답에 대한 업데이트를 추가했습니다. –

관련 문제