안녕하세요. Parallel 클래스를 사용한 병렬 프로그래밍에서 최적화를 할 수 있는지 여쭤보려 글을 쓰게 되었습니다.
최적화하고 싶은 이유를 예시를 통해 보여드리겠습니다.
범위를 알 수 있는 List 자료형에 병렬 알고리즘을 적용하면 범위 분할이 되어 병렬 처리가 됩니다. 예를 들어, list에 100개의 원소가 들어있고, 이를 4개의 코어로 병렬 처리하면 0~24, 25~49, 50~74, 75~99로 나뉘어 병렬처리가 됩니다.
여기서 특정 범위의 원소에 작업이 소요되는 시간이 오래걸리는 경우, 처음에는 4개의 스레드로 병렬처리 되다가 3개의 스레드가 먼저 일을 끝내고 1개의 스레드만 작업을 진행하게 됩니다.
이해를 돕기 위한 코드입니다.
var list = new List<int>();
for(var i = 0; i < 100; i++)
{
list.Add(i);
}
list.AsParallel().WithDegreeOfParallelism(4).ForAll(i =>
{
Console.WriteLine($"Id: {Thread.CurrentThread.ManagedThreadId}, Value: {i}");
// 번호가 높을 수록 작업이 오래 걸린다.
Thread.Sleep(i * 5);
});
List<int> list = new List<int>();
for (int i = 0; i < 100; i++)
{
list.Add(i);
}
var queue = new ConcurrentQueue<int>(list);
Parallel.ForEach(Enumerable.Range(0, 4), _ =>
{
while (queue.TryDequeue(out int p))
{
Console.WriteLine($"Id: {Environment.CurrentManagedThreadId}, Value: {p}");
Thread.Sleep(p * 5);
}
});
List<int> list = new List<int>();
for (int i = 0; i < 100; i++)
{
list.Add(i);
}
var po = new ParallelOptions { MaxDegreeOfParallelism = 4 };
Parallel.ForEach(list, po, (item) =>
{
Console.WriteLine($"Id: {Environment.CurrentManagedThreadId}, Value: {item}");
Thread.Sleep(item * 5);
});
원하시는 조건이 단순히 조건이
동시진행 쓰레드 갯수를 4개로 한정시키시려는게 목적이시라면
위의 코드와 같이 Parallel에 아이템에 대한 액션만 대응시키면 되는게 아닌가요?
설명해 주셨듯이 현재 사용하는 자료구조는 List<T>이므로 PLINQ는 범위 분할 알고리듬을 사용했습니다.
PLINQ는 쿼리를 실행하기 전에 분할 과정을 거친 후 실행 된다는 More Effective의 설명으로 보아
4개의 병렬성으로 분할 된 데이터는 쿼리를 실행할 때 다시 분할되지 않을 것으로 예상됩니다.
그러므로 원하시는대로 먼저 데이터를 처리한 쓰레드에서 아직 처리가 완료 되지 않은 쓰레드의 데이터를 다시 분할해서 작업을 도와주는 최적화를 하셔야 한다면 PLINQ가 아닌 새로운 알고리듬을 작성하셔야 할 것 같습니다.
개인 적인 판단이지만 새로운 알고리듬을 직접 만들어 최적화 하는 방법보다는 WithDegreeOfParallelism(4)를 사용하지 않고 PLINQ가 쿼리를 시작하기 전에 최적의 병렬성을 스스로 판단해서 쿼리를 돌리게 만들면 모든 쓰레드의 작업이 거의 동시에 끝나 말씀해 주신 것과 같이 특정 쓰레드의 작업이 끝나길 기다리는 시간이 최소한으로 줄어들 것 같습니다.