간단한 유닛이지만 만들어 놓고 상당히 잘 활용했던 코드입니다.  이제는 델파이에서 자체적으로 지원해주기 시작하지만, 아직 XE7을 사용하지 않는 분들에게는 도움 될 듯 합니다.


병렬처리 유닛https://code.google.com/p/ryulib4delphi/source/browse/trunk/XE2/SimpleMP.pas


사용 예제https://code.google.com/p/ryulib4delphi/source/browse/trunk/XE2/Samples/SimpleMP


예제에는 

  • 1부터 100까지 더하기
  • CRC32 구하기를 단순 반복과 병렬처리를 놓고 속도를 비교하기
가 포함되어 있습니다.



[그림 1] 예제의 실행 결과


예제의 실행 결과를 보면 속도가 3.21 배 향상 된 것으로 나오는데, 처리 할 단위 작업이 시간이 많이 걸릴 수록 효율이 높아집니다.  또한, 기껏 병렬처리하면서 각 단위 작업이 임계영역을 사용하거나 하면 시간이 더 많이 걸리게 됩니다.


델파이 내부 코드 중에는 디버그 모드에서 병렬처리가 오히려 더 느린 경우도 있습니다.  배포하실 때는 릴리즈 모드를 사용하세요.


type
  {*
    스레드 마다 한 번만 호출한다.
    @param Context Execute 메소드의 AContext 인자가 그대로 전달 됩니다.
    @param ThreadNo 현재 TSimpleProcedureSingle 함수를 실행하는 스레드의 순번이 전달 됩니다.
           이를 통해서 스레드를 구별 할 수 있습니다.
    @param ThreadCount 현재 사용 중인 전체 스레드의 숫자를 알려줍니다.
           Execute 함수를 호출 할 때 지정한 AThreadCount와 동일한 값을 가집니다.
  }
  TSimpleProcedureSingle = reference to procedure(Context:pointer; ThreadNo,ThreadCount:integer);

  {*
    스레드 내부에서 익명메소드를 필요한 횟수만큼 반복한다.  (전체 Task를 나눠서 해당 스레드가 꼭 필요한 횟수 만큼)
    @param Context Execute 메소드의 AContext 인자가 그대로 전달 됩니다.
    @param ThreadNo 현재 TSimpleProcedureSingle 함수를 실행하는 스레드의 순번이 전달 됩니다.
           이를 통해서 스레드를 구별 할 수 있습니다.
    @param Index Execute 메소드의 ATaskCount 횟 수만큼 반복해서 TSimpleProcedureRepeat를 실행할 때, 현재 반복하고 있는 순서입니다.
           병렬처리이기 때문에 Index는 순서대로 진행된다는 보장은 없습니다.
  }
  TSimpleProcedureRepeat = reference to procedure(Context:pointer; ThreadNo,Index:integer);

  TSimpleMP = class
  private
  public
    {*
       AThreadCount 숫자만큼 스레드를 만들어서, 각각의 스레드가 ASimpleProcedure를 병렬로 실행합니다.
       스레드와 ASimpleProcedure가 1:1로 매칭 됩니다.

       AContext는 ASimpleProcedure 내부에서 사용하고 싶은 객체(데이터)의 참조를 넘겨주고 싶을 때 사용합니다.
    }
    class procedure Execute(AContext:pointer; AThreadCount:integer; ASimpleProcedure:TSimpleProcedureSingle); overload;

    {*
      반복 횟수(ATaskCount)가 정해져 있는 경우 사용합니다.

      만약 "for i:= 1 to 10 do ..."를 병렬처리한다면, 아래처럼 사용하게 됩니다.
      AThreadCount는 사용하고 싶은 스레드 개수입니다.
        TSimpleMP.Execute( nil, 10, AThreadCount,
          procedure(Context:pointer; ThreadNo,Index:integer)
          begin
            ...
          end
        );
    }
    class procedure Execute(AContext:pointer; ATaskCount,AThreadCount:integer; ASimpleProcedure:TSimpleProcedureRepeat); overload;
  end;

SimpleMP.pas 인터페이스 부분만 올려보았습니다.  간단한 사용법은 유닛 안에 위와 같이 주석처리 되어 있습니다.


가장 단순한 형태는 아래와 같습니다.  1부터 100까지 더하기를 32개의 스레드를 이용해서 구하는 예제입니다.

Trace는 OutputDebugString 함수입니다.  계산되는 과정을 DebugView를 통해서 확인 할 수가 있습니다.

  TSimpleMP.Execute( nil, 100, 32,
    procedure (Context:pointer; ThreadNo,Index:integer) begin
      iTemp := InterlockedExchangeAdd( iSum, Index + 1 );
      Trace( Format('ThreadNo(%d) - %d: %d', [ThreadNo, Index + 1, iTemp]) );
    end
  );



Posted by 류종택