[미리 보는 C++11] 8. Placement Insert

C++0x 2012. 2. 27. 09:00 Posted by 알 수 없는 사용자

Placement Insert C++11의 기능 중에 하나로 STL 컨테이너와 관계가 있습니다.

 

struct ITEM

{

ITEM( int nCode )

{

}

};

 

std::vector< ITEM > Items;

Items.push_back( ITEM( 1 ) );

 

현재까지는 위 코드처럼 ITEM이라는 객체를 Items 컨테이너에 생성과 동시에 추가를 할 때는 위와 같이해야 합니다. 그런데 위 방식으로 하면 추가를 위해 컨테이너에 한번 생성을 한 후 복사를 해야 하는 문제가 발생합니다(또 임시 객체 만들므로 삭제 비용도 발생합니다).

이와 같은 동작은 우리가 원하는 것이 아닙니다.

그래서 C++11에서는 이와 같은 문제를 해결했습니다. 바로 Placement Insert가 해결했습니다.

 

C++11‘Placement Insert’를 사용하면 위의 코드는 아래와 같이 할 수 있습니다.

std::vector< ITEM > Items;

Items.emplace_back( 1 );

 

emplace_back push_back과 같지만 Placement Insert 기능이 구현된 것으로 임시 오브젝트를 만들면서 발생하는 비용을 없애줍니다.

 

C++11의 각 컨테이너에는 Placement Insert와 관련된 멤버로
emplace(insert),
emplace_back(push_back),
emplace_front(push_front),
emplace_hint(insert.
연관 컨테이너 용)
가 추가됩니다.

 

Placement InsertC++11의 새로운 기능인 가변 인수 템플릿을 사용하여 구현되었습니다.

 

 

Placement Insert는 아래와 같은 주의할 점도 있습니다.

1. explicit 문제.

   explicit 생성자도 암묵적으로 호출됩니다.


2. "0" 문제.

생성자의 파라미터가 포인터인 경우 인자로 0을 넘기면 int로 추론합니다. 그래서 이 경우에는 nullptr을 사용해야 합니다.

 

Apache Hadoop On Windows

Cloud 2012. 2. 24. 08:30 Posted by 알 수 없는 사용자

Apache Hadoop On Windows

Apache Hadoop On Windows 에 대한 위키 페이지는 아래를 참고하십시오.

http://social.technet.microsoft.com/wiki/contents/articles/6204.hadoop-based-services-for-windows.aspx

제한된 CTP에 대한 내용은 아래 링크를 클릭해서 등록하시면 됩니다.

https://connect.microsoft.com/SQLServer/Survey/Survey.aspx?SurveyID=13697


여러 링크가 있어 Hadoop에 대한 내용을 살펴볼 수 있을 것 같습니다.

Getting Started with Hadoop-based Services for Windows

On-Premise Deployment of Hadoop-based Services for Windows

Windows Azure Deployment of Hadoop-based Services for Windows

Windows Azure Deployment of Hadoop-based Services on the Elastic Map Reduce (EMR) Portal

Windows Azure에 대한 내용은 얼른 구성을 해서 살펴보겠습니다.


SQL Azure 가격 변경 및 100MB 데이터베이스

Cloud 2012. 2. 20. 08:30 Posted by 알 수 없는 사용자


SQL Azure
가격 변경 (2012 2)


SQL Azure
의 경우 가격이 상당히 많이 인하되었습니다. 50% 가량 인하되었습니다.

또한 100MB 데이터베이스도 제공이 되어 테스트나 데모 등에 사용하면 비용 절감에 도움이 될 것 같습니다.


아래 링크를 참조하시면 보다 더 구체적인 가격 변경에 대한 내용을 알 수 있습니다.

http://blogs.msdn.com/b/windowsazure/archive/2012/02/14/announcing-reduced-pricing-on-sql-azure-and-new-100mb-database-option.aspx

GB

Previous Pricing

New Pricing

New Price/GB

Total % Decrease

5

$49.95

$25.99

$5.20

48%

10

$99.99

$45.99

$4.60

54%

25

$299.97

$75.99

$3.04

75%

50

$499.95*

$125.99

$2.52

75%

100

$499.95 *

$175.99

$1.76

65%

150

$499.95*

$225.99

$1.51

55%


현재까지는 Windows Azure Management Portal에서 데이터베이스 생성시 데이터베이스 선택에서 100MB 는 나오지 않았습니다.



Inertia Tensor(2)

물리 2012. 2. 19. 17:19 Posted by 알 수 없는 사용자
이번에는 이전 운동량, 각운동량, 질량중심의 내용을 바탕으로 Inertia Tensor에 대해 설명 드리겠습니다.

1) 힘F(Input)에 대한 질점(Particle)의 변화량(Output)
아래의 그림처럼 m1에 F라는 힘을 가하면,  m1의 운동량(각운동량)이 변하게 됩니다.
 

 

 

식으로 나타내면 아래와 같고, 결과적으로 가속도가 변하게 됩니다.

 



2) 힘F(Input)에 대한 물체(RigidBody)의 변화량(Output)
아래의 그림은 m1과 m2가 붙어있은 경우 입니다.
힘 F를 가하면, 붙어있기 때문에 m1과 m2의 운동량(각운동량)이 모두 변하게 된다.

  

식으로 나타내면 아래와 같습니다.



v2는 상대운동을 함으로, 식을 아래와 같이 바꿀 수 있습니다.


그러나 역시, 식은 하나인데 변수는 2개입니다.  이식으론  m1, m2의 변화량을 구할 수 없습니다.
질점(Particle)과 달리 물체(RigidBody)의 변화량은,  운동량 보존법칙 하나만으론 해석할 수 없습니다.

다음은 각운동량 법칙을 이용해서 m1, m2의 변화량을 해석해 보겠습니다.
아래는 p점을 기준으로 각운동량의 변화량을 측정한 그림입니다. 


식을로 나타내면, 아래와 같습니다.



v2는 상대운동을 함으로, 식을 아래와 같이 바꿀 수 있습니다.


역시, 변수가 두개이기 때문에 각운동량 보존법칙 하나만으로는 문제는 풀 수 없습니다.
그러나 앞의 운동량 보존법칙과 각운동량 보존법칙을 연립하면, 식이 2개 변수가 2개 이기 때문에 변화량을 구할 수 있습니다.  정리하면 아래와 같습니다.


그러나 위의 식은 너무 복잡합니다. 지금은 m1, m2두개의 물체가 있을때 지만,  아래의 그림과 같은 상황이 되면 식을 풀기가 어렵습니다.

 


3) Center of Mass의 특성을 이용해, 물체(RigidBody)의 운동량(각운동량)해석을 간소화함
아래의 그림처럼 물체(RigidBody)의 속도는 기준점의 속도(Linear Velocity)와 기준점에 대한 상대속도(Angular Velocity)의 합으로 나타낼 수 있습니다.


기준점을 Center of Mass로 잡으면 , 운동량 보존법칙은 아래 식과 같습니다.
r은 Center of Mass로 부터의 거리, w는 각속도, Vc는 Center of Mass의 속도를 나타냅니다.
 



외부힘에 대한, 운동량의 변화량을 식으로 나타내면 아래와 같습니다.


마찬가지로, 각운동량의 측정지점을 아래 그림과 같이 Center of Mass로 합니다.


각운동량을 식으로 나타내면 아래와 같습니다.


F힘이 위치 PF위치에서 작용했을때, 각운동량의 변화량을 식으로 나타내면 아래와 같습니다.



여기서 식을 아래와 같이 정리 할 수 있습니다.



그리고 Inertia Tensor를 아래와 같이 정의 하게 됩니다.



Inertia Tensor를 이용해 운동량을 해석하면 아래와 같습니다.



4) 박스의 Inertai Tensor
예로 박스의 Inertia Tensor를 구해 보겠습니다.

Inertia Tensor는 아래식과 같습니다.


식을 x, y, z순으로 적분하면 아래과 같이 됩니다.



최종 Inertia Tensor는 아래와 같이 됩니다.



5) Inertia Tensor의 대각화
Bullet이나 대부분의 물리엔진은 Inertia Tensor를 행렬이 아닌 백터의 형태로 가지고 있습니다.
이는 Inertia Tensor의 3x3행열을 대각화 시킨 것 입니다.
박스, 구, 실린더, 캡슐등의 Inertia Tensor는기본적으로 대각화가 되어 있습니다. 위의 박스의 Inertia Tensor도 대각화 되어 있는 걸 확인하실 수 있습니다. (대각화를 구하는 방법은 따로 언급하지 않겠습니다.)
대각화된 Inertia Tensor는 아래와 같은 특징을 가집니다.


Inertia Tensor가 대각화 됬을 경우, 운동량식을  정리하면 아래와 같습니다.



이상으로 Inertia Tensor에 대한 설명을 마치겠습니다.

'물리' 카테고리의 다른 글

Inertia Tensor(1)  (0) 2012.02.13
RigidBody의 Restitution, Friction, Damping  (2) 2012.02.06
[Bullet Physics] RigidBody 만들기  (2) 2012.01.05
[Bullet Physics] Bullet 물리엔진의 설치  (2) 2011.12.22

Welcome to Parallel C#(16) - 분배 전략.

C# Parallel Programming 2012. 2. 17. 09:00 Posted by 알 수 없는 사용자
이번에는 TPL에서 제공하는 옵션에 대해서 알아보겠습니다. 병렬 작업을 위해서는 먼저 병렬로 수행할 수 있게끔 작업을 분할하는 일이 먼저 수행되어야 합니다. 예를 들어서 10000개의 데이터를 4개의 스레드가 나눠서 처리해야 하고, 각 10000개의 작업이 독립적이라면, 이걸 공평하게 4등분 할 것인지, 혹은 다른 방법을 사용할 것인지등을 결정하는 것 말이죠.


- 두 가지 선택.

TPL에서 작업 분할을 할때 사용하는 방법으로 구간 분할과 로드 밸런싱이 있습니다. 구간 분할을 루프의 크기를 스레드의 개수만큼 공평하게 나눠서 처리하는 것을 말합니다. 단순하죠.  :) 그리고 로드밸런싱은
처리할 구간을 미리 정하지 않고, 처리해야 할 작업에 따라서 각 스레드가 처리할 구간의 크기나 스레드의 개수가 유동적으로 변하게 됩니다. 이때 스레드하나가 이 작업을 담당하게 됩니다. 이 스레드는 작업 상황을 지켜보면서 그때 그때 스레드를 생성해서 작업을 맡기등의 역할을 맡게 되는 거죠. 그래서 작업을 끝낸 스레드가 다음에 처리할 구간을 할당받기 전까지 기다리는 시간이 필요하고, 이런 동기화에 따른 부하가 발생하게 되는 거죠. 나눠주는 구간의 크기가 작을 수록 스레드는 자주 구간을 할당받으려고 기다려야 하고 성능은 더 저하되는 거죠. :)

Parallel.For의 기본 동작은 로드 밸런싱을 사용하도록 되어 있습니다. 그래서 위에서 언급한 동기화 이슈가 자주 발생하는 경우 싱글스레드로 실행되는 for루프에 비해서 그다지 성능의 우위를 보이지 못하게 됩니다. 예제로 확인해볼까욤? ㅋㅋ

using System;

using System.Linq;

using System.Threading.Tasks;

using System.Collections.Concurrent;

using System.Collections.Generic;

 

namespace Exam20

{

    class Program

    {

        static void RangeParallelForEach(int[] nums)

        {

            List<int> threadIds = new List<int>();

            var part = Partitioner.Create(0, nums.Length);

 

            Parallel.ForEach(part, (num, state) =>

            {

                if (!threadIds.Contains(Task.CurrentId.Value))

                {

                    threadIds.Add(Task.CurrentId.Value);

                }

 

                for (int i = num.Item1; i < num.Item2; i++)

                {

                    nums[i] = nums[i] * nums[i];

                }

            });

 

            Console.WriteLine("Thread ID list of RangeForEach");

            foreach (var id in threadIds)

            {

                Console.WriteLine("{0}", id.ToString());

            }

        }

 

        static void ParallelFor(int[] nums)

        {

            List<int> threadIds = new List<int>();

 

            Parallel.For(0, nums.Length, (i) =>

            {

                if (!threadIds.Contains(Task.CurrentId.Value))

                {

                    threadIds.Add(Task.CurrentId.Value);

                }

 

                nums[i] = nums[i] * nums[i];

            });

 

            Console.WriteLine("Thread ID list of ParallelFor");

            foreach (var id in threadIds)

            {

                Console.WriteLine("{0}", id.ToString());

            }

        }

 

        static void Main(string[] args)

        {

            int max = 50000000;

            int[] nums1 = Enumerable.Range(1, max).ToArray<int>();

            int[] nums2 = Enumerable.Range(1, max).ToArray<int>();

 

            ParallelFor(nums1);

            RangeParallelForEach(nums2);

        }

    }

}

위 코드를 보면 로드밸런싱을 사용하는 Parallel.For와 Partitioner.Create 메서드를 이용해서 구간분할을 사용하도록 설정한 Parallel.ForEach를 사용하는 코드를 포함하고 있습니다. 이렇게 수행하면서 각 방법 별로 몇 개의 스레드가 생성되는지 확인한 결과를 볼까요.

Thread ID list of ParallelFor
1
2
3
4
5
6
7
8
9
10
--------------중략--------------
91
92
93
94
95
96
97
98
99
100
101
Thread ID list of RangeForEach
102
103
104
105
106
107
108
Press any key to continue . . .

구간 분할을 사용하는 경우(RangeForEach)는 고작 7개의 스레드가 생성되어서 작업을 처리한 것에 비해서, 로드밸런싱을 사용한 경우는 무려 101개의 스레드가 생성된 것을 볼 수 있습니다. 스레드가 많이 교체되면서 생기는 비효율성은 굳이 말을 안해도 되겠죵. 그렇다면 다음과 같은 코드로 실제로 어떤 속도의 차이가 발생하는지 확인을 해보시져 :)

using System;

using System.Linq;

using System.Threading.Tasks;

using System.Collections.Concurrent;

 

namespace Exam21

{

    class Program

    {

        static void NormalFor(int[] nums)

        {

            for (int i = 0; i < nums.Length; i++)

            {

                nums[i] = nums[i] * nums[i];

            }

        }

 

        static void ParallelFor(int[] nums)

        {

            Parallel.For(0, nums.Length, (i) =>

            {

                nums[i] = nums[i] * nums[i];

            });

        }

 

        static void RangeParallelForEach(int[] nums)

        {

            var part = Partitioner.Create(0, nums.Length);

 

            Parallel.ForEach(part, (num, state) =>

            {

                for (int i = num.Item1; i < num.Item2; i++)

                {

                    nums[i] = nums[i] * nums[i];

                }

            });

        }

 

        static void Main(string[] args)

        {

            int max = 50000000;

            int[] nums1 = Enumerable.Range(1, 10).ToArray<int>();

            int[] nums2 = Enumerable.Range(1, 10).ToArray<int>();

            int[] nums3 = Enumerable.Range(1, 10).ToArray<int>();

 

            //JIT컴파일을 위해.

            NormalFor(nums1);

            ParallelFor(nums2);

            RangeParallelForEach(nums3);

 

            nums1 = Enumerable.Range(1, max).ToArray<int>();

            nums2 = Enumerable.Range(1, max).ToArray<int>();

            nums3 = Enumerable.Range(1, max).ToArray<int>();

 

            DateTime normalForStart = DateTime.Now;

            NormalFor(nums1);

            DateTime normalForEnd = DateTime.Now;

            TimeSpan normalForResult = normalForEnd - normalForStart;

 

            DateTime parallelForStart = DateTime.Now;

            ParallelFor(nums2);

            DateTime parallelForEnd = DateTime.Now;

            TimeSpan parallelForResult = parallelForEnd - parallelForStart;

 

            DateTime rangeForStart = DateTime.Now;

            RangeParallelForEach(nums3);

            DateTime rangeForEnd = DateTime.Now;

            TimeSpan rangeForResult = rangeForEnd - rangeForStart;

 

            Console.WriteLine("Single-threaded for : {0}", normalForResult);

            Console.WriteLine("Load-balancing Parallel.For : {0}", parallelForResult);

            Console.WriteLine("Range-partition Parallel.ForEach : {0}", rangeForResult);

        }

    }

}

좀 어글리한 코드이긴 하지만, 싱글 스레드를 사용한 for루프와 로드 밸런싱을 사용하는 Parallel.For, 그리고 구간 분할을 사용하도록 한 Parallel.ForEach의 성능을 각각 비교한 코드입니다. 그럼 결과를 확인해보죠!

Single-threaded for : 00:00:00.3000763
Load-balancing Parallel.For : 00:00:00.3430858
Range-partition Parallel.ForEach : 00:00:00.2130545
Press any key to continue . . .

조금은 의외의 결과가 나왔습니다. 오히려 병렬로 작업을 처리한 Parallel.For가 순차적으로 실행되는 for루프에 비해서 낮은 성능을 낸 것이죠. 왜 그럴까욤? 첫 번째 원인은 너무 빈번하게 스레드 간의 작업 전환을 하느라 오버헤드가 컸던 점을 들 수 있을 것 같구요. 그리고 다음은, 병렬 작업에 사용되는 델리게이트의 내용이 너무 짧아서 입니다. 위 코드를 보면 델리게이트의 내용이 간단한 연산 한 줄로 이루어진 것을 볼 수 있는데요. 위 처럼 호출 수가 작은 경우는 큰 문제가 되지 않지만, 위의 코드 처럼 많은 횟수를 호출하는 경우 델리게이트 호출은 부담이 됩니다. 델리게이트의 내용을 처리하는 것 보다 델리게이트를 호출하는 시간이 더 많이 걸리게 되면서 일반 for루프보다도 못한 성능을 보여주는 거죠. 그래서 Partitioner.Create메서드를 이용해서 큰 작업 덩어리로 잘라줘서 이 오버헤드를 줄일 수 있습니다. 그 결과 위와 같은 시간이 나오게 된거죠.

어떻나요? 단순히 for루프를 Parallel.For로 교체하는 것만 가지고는 제대로된 성능을 기대하기 힘듭니다. 혹시, "어? 뭐야 당신 이전 포스트에서는 단순히 Parallel.For를 쓰기만 해도 성능이 좋아진다며?" 하는 생각이 드셨다면... 당신은 저의 팬...???!?!? ㅋㅋㅋㅋ 아마 기억하시는 분은 없겠죠. 암튼, 주의하고 볼일 입니다. 오늘은 여기까징~! :)

SharePoint 2010 ULS Viewer

SharePoint 2010 2012. 2. 16. 08:30 Posted by 알 수 없는 사용자

ULS Viewer

SharePoint 2010의 오류가 발생할 경우 상관관계 ID를 통해 오류 로그를 액세스하게 됩니다.

C 드라이브일 경우 위치는 아시다시피 아래와 같습니다.

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\LOGS

오류를 볼 경우 찾기를 통해 해당 상관관계 ID를 찾게 되는데요.

ULS Viewer를 쓰시면 오류만 필터링하거나 해서 트러블슈팅하실 때 도움이 될 것 같습니다.

아래 링크를 참고하셔서 다운로드 받아서 서버에서 실행하면 됩니다.

http://archive.msdn.microsoft.com/ULSViewer

파일 메뉴의 Open From -> ULS를 통해서 로그를 확인이 가능하며 Level High 보시면 필터링해서 오류등의 결과 확인에 더 도움이 됩니다.


지난 포스팅에서 Break와 Stop메서드의 차이에 대해서 시덥잖은 퀴즈를 남겼었는데욤, 뭐 별로 궁금한 분들이 없거나 너무 쉬워서 코에서 바람 좀 내뿜은 분들이 많으리라 생각합니다. 저는 이게 뭔 차이인지 잠깐 헤맸습니다만... 허허허 :) 그럼 차이점에 대해서 잠깐 이야기를 해볼까욤.


- Parallel.For와 for의 브레끼는 닮았더라.

Parallel.For의 Break메서드는 for의 break와 좀 닮았습니다. for루프에서 break를 만나게 되면 그 즉시 for루프를 중단하게 됩니다. 그리고 break가 걸리기 전 까지의 인덱스에 대해서는 루프가 실행되었음을 보장할 수 있습니다. for루프는 처음부터 순차적으로 실행되기 때문이죠. 그런데 Parallel.For에서는 루프문의 길이에 대해서 각각의 스레드가 조금씩 나눠서 담당하게 됩니다. 예를 들면 1-10000까지의 구간이라면, 스레드 하나가 1-5000까지, 다른 스레드가 5001-10000까지 뭐 이런식으로 말이죠. 이런 상황에서 5100번째 인덱스에서 Break가 호출된다면 어떻게 될까요? 기존의 for루프를 사용하던 관점에서 보자면 당연히 5109번째 인덱스까지는 다 처리되었을 거라고 예상하게 됩니다. 이런 개발자의 기대를 저버리지 않기 위해서 Break메서드를 호출하게 되면 Break가 호출된 인덱스보다 작은 인데스에 대해서 남아있는 작업은 모두 실행하도록 합니다. 예제를 볼까요?

using System;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Parallel.For(0, 20, (i, loopState) =>

            {

                if (i == 10)

                {

                    loopState.Break();

                    Console.WriteLine("Break at {0}", i.ToString());

                }

                else

                {

                    Console.WriteLine(i.ToString());

                }

            });

        }

    }

}


아주 간단하게 0부터 19까지 Parallel.For루프를 돌면서 i가 10일때 Break를 호출해서 루프를 중단하도록 하는 코드입니다. 실행할 때마다 결과가 다르게 나오는데요, 그 중에 가장 설명하기 좋은 결과를 고르면!

0

1

2

3

4

6

5

Break at 10

7

8

9

Press any key to continue . . .


대략 이렇습니다. 스레드가 구간을 나눠서 루프를 실행하는데, 먼저 실행한 스레드가 0-6까지를 처리하고 다른 스레드가 10을 처리하는 순간 Break를 만나게 되는 거죠. 하지만 여기서 중단 되지 않고, 다른 스레드가 7, 8, 9를 처리하도록 해줍니다. 그래서 10에서 Break가 호출되었더라도 바로 중단하지 않고 10보다 작은 인덱스가 모두 처리되도록 해주는 것이죠.


- 장비를 정지합니다... 어 안되잖아?

Break를 즉시 중단하는 개념으로 생각하다 보면 조금은 이상할 수 있습니다만, 사실 기존의 for루프와 유사한 동작을 제공한다는 개념에서는 바람직한 동작이 아닌가 싶습니다. 그런데, 데이터에서 뭔가를 검색하는 경우는 어떨까요? 4명이 근무하는 사무실을 생각해봅시다. 뭔가 중요한 서류를 찾아야 해서 사장 이하 4명이 구역을 나눠서 사무실을 뒤지기 시작합니다. 한 명은 책상을, 한 명은 캐비넷을, 또 한 명은 창고를, 또 한 명은 잉여짓을. 이렇게 4명이 찾다가 누군가 한 명이 그 서류를 발견했습니다!!! 그러면 그 사람은 '심봤다 어허으어릉러허엏엉 나 심봤쪄영 뿌우 'ㅅ'~'하고 외치겠죠? 그러면 그 사람들은 그 찾는 작업을 멈추게 됩니다. 그게 정상이겠죠. '그럴리가 없엉 어흐어흐어흐러라어헝'하면서 나머지를 계속 찾는 사람은 '안돼에~'. 허허허허 :)

이런 경우에는 Parallel.For를 즉시 중시해야 합니다. 그럴때! 바로 Stop을 호출하게 됩니다.

using System;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Parallel.For(0, 20, (i, loopState) =>

            {

                if (i == 10)

                {

                    loopState.Stop();

                    Console.WriteLine("Stop at {0}", i.ToString());

                }

                else

                {

                    Console.WriteLine(i.ToString());

                }

            });

        }

    }

}

Break대신에 Stop을 호출한 코드죠. 결과능!

0

5

Stop at 10

Press any key to continue . . .


짤 없습니다. 그냥 중단해버리는 거죠. 자 지난 포스팅에서 왜 Stop을 호출한 게 미세하게 빨랐는지 아시겠나요? 소수는 1과 자신 외에 하나라도 나눌 수 있는 숫자가 존재하면 안되는 거죠. 그래서 for루프에서 숫자를 바꿔가면서 나눠보다가 하나라도 나눠지는 게 발견되면 그 즉시 중단하는 게 맞는 거죠. Break는 발견되더라도, 그 보다 작은 인덱스에 대해서는 모두 수행하게 되기 때문에 Stop에 비해서 아주 미세하게 느린 결과가 나오게 되는 거죵. 허허허허허 :) 참 별 내용 없네요 ㅠㅠ...

그럼 오늘은 여기까징~!

Welcome to Parallel C#(14.2) - 긴급 패치 2.

C# Parallel Programming 2012. 2. 14. 09:15 Posted by 알 수 없는 사용자

김명신님께서 또 아주 좋은 지적을 해주셨습니다~~ List<T>도 ConcurrentBag<T>이든 하나의 컬렉션을 이용하는 건 바람직하지 않은 것 같다는 의견이셨는데요, 동감합니다 :) 그래서 제 나름대로 해결책을 강구해봤는데욤. 스레드 로컬 변수를 이용해서 스레드 별로 작업을 시키고, 나중에 하나의 리스트로 모으는 것입니다. 코드를 볼까욤~?

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApplication1
{
    class Program
    {
        static readonly object _sync = new object();
 
        static IEnumerable<long> GetPrimeNumber(long num)
        {
            List<long> primeList = new List<long>();
 
            Parallel.For<List<long>>(0, num + 1, 
                () => new List<long>(), //스레드 로컬 변수 초기화
                (i, outerLoopState, subList) =>
            {
                bool isPrime = true;
                Parallel.For(2, i, (j, loopState) =>
                {
                    if (i % j == 0)
                    {
                        isPrime = false;
                        loopState.Break();
                    }
                });
 
                if (isPrime)
                {
                    subList.Add(i); //스레드 로컬 리스트에 추가
                }
 
                return subList;
            },
            (subList) => //각 스레드 종료 후에 취합
            {
                lock (_sync)
                {
                    primeList.AddRange(subList);
                }
            });
 
            return primeList;
        }
 
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            IEnumerable<long> primeList = GetPrimeNumber(99999);
            sw.Stop();
 
            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",
                sw.Elapsed.ToString(),
                primeList.Count());
            //뭔가 한다.
        }
    }
}

위 코드를 보면, Parallel.For 루프에서 스레드 로컬 변수를 사용하도록 밑줄 친 부분들이 추가된 것을 볼 수 있습니다. 즉 이 Parallel.For는 List<long>타입의 스레드 지역 변수를 사용하며, 그 지역변수를 어떻게 초기화 할 것인지를 명시하고, 각 스레드가 종료할 때 지역 변수를 리턴하도록 한 것이죠. 그리고 Parallel.For의 마지막 매개변수로, 각 스레드가 종료할 때 어떤 동작을 취할 것인가를 명시하는데, 스레드가 끝날 때 리턴한 지역 변수가 매개변수로 들어오게 됩니다. 이렇게 하면 동기화를 좀 더 줄일 수 있는 것이죠. 그래서, 이왕 코드를 고친 김에 시간을 재봤습니다.

   ConcurrentBag<T>사용(초)  스레드 지역 변수 사용(초)
 GetPrimeNumber(99999)  2.54  2.52 
 GetPrimeNumber(199999)  9.32  9.25
 GetPrimeNumber(299999)  20.03  19.96

미세하게 스레드 지역 변수를 사용한 쪽이 빠르긴 한데요, 위 코드의 경우 하나의 리스트로 값을 합치는 데 시간이 좀 소요되어서 큰 속도 향상이 없는 것으로 보입니다. 동기화해서 수행해야 하는 작업이 간단할 수록 속도차이는 벌어질 것으로 보이는데요, 가능하면 각자 작업 후에 하나로 합치는 쪽이 더 효율적일 것 같네요.

부족한 실력이다 보니 이 이상 뭐가 떠오르지 않네요 -_-;; 더 나은 방법이 있으면 알려주세용~ :)

Welcome to Parallel C#(14.1) - 긴급 패치.

C# Parallel Programming 2012. 2. 13. 20:23 Posted by 알 수 없는 사용자

신석현님께서 아주 좋은 지적을 해주셨습니다. 제가 예제로 사용한 코드를 돌리면 매번 prime count가 다르게 나온다는 것이었습니다. 참고를 위해서~~ 다시 그 문제의 코드를 붙여 보겠습니당. :)

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            Parallel.For(0, num + 1, (i) =>

            {

                bool isPrime = true;

                Parallel.For(2, i, (j, loopState) =>

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        loopState.Break();

                    }

                });

 

                if (isPrime)

                {

                    primeList.Add(i);

                }

            });

 

            return primeList;

        }

 

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


이 코드에서 문제가 되는 곳은 바로~!!!!! List<T>를 사용했다는 점입니다. Parallel.For를 돌리면, 여러 스레드가 구역을 나눠서 동시에 Parallel.For안의 코드를 돌리게 되는데요. 이 과정에서 스레드 간의 충돌이 생겨서 그렇습니다. 문제를 찾아보기 위해서 리플렉터로 List<T>.Add(T)의 코드를 찾아보니...

public void Add(T item)

{

    if (this._size == this._items.Length)

    {

        this.EnsureCapacity(this._size + 1);

    }

    this._items[this._size++] = item;

    this._version++;

}


이렇게 되어 있습니다. 여기서 7번째 줄의 'this._size++'부분이 문제가 되는 것 같습니다. 제가 11번째 포스팅에서 적은 내용 처럼 말이죠. 그래서 size가 1증가 되기 전에 같은 같은 인덱스에 값이 두번 쓰여지는 것이 아닌가 하는 추측이 가네욤!!! :)

그래서 가설을 확인해보기 위해서 List<T>를 여러스레드가 동시에 사용가능한 ConcurrentBag<T>로 바꿔서 테스트를 진행해봤습니다. 그랬더니!! 매번 같은 prime count가 나오는 것을 볼 수 있었습니다. 하하하하하 ㅠ_ㅠ;; 참고로 ConcurrentBag<T>.Add<T>의 코드를 봤더니 내부적으로...

private void AddInternal(ThreadLocalList<T> list, T item)

{

    bool lockTaken = false;

    try

    {

        Interlocked.Exchange(ref list.m_currentOp, 1);

        if ((list.Count < 2) || this.m_needSync)

        {

            list.m_currentOp = 0;

            Monitor.Enter(list, ref lockTaken);

        }

        list.Add(item, lockTaken);

    }

    finally

    {

        list.m_currentOp = 0;

        if (lockTaken)

        {

            Monitor.Exit(list);

        }

    }

}


위와 같은 코드를 사용하더군요. Monitor를 사용해서 동기화를 시키고 있죠~? 하하하 :) 날카로운 질문을 해주신 신석현님께 감사드립니다 :)

Welcome to Parallel C#(14) - 거기까지.

C# Parallel Programming 2012. 2. 13. 09:00 Posted by 알 수 없는 사용자

자~ 그럼 이제 부터는 다시 병렬 프로그래밍으로 돌아가서 조금 이야기를 해보도록 할 까욤~? 오래전에 써놓은 자료를 보다보니..... 제가 쓴 내용이지만 무슨 내용인지 이해가 안되는 글이 많아서 다시 정리를 하느라 조금 애를 먹고 있습니다. 하하하하 :) 뭐 제 수준이 딱 거기 까지니까 말이죵 호호호호.


- 병렬 작업을 중단할 때능?

기존의 for루프를 중단할 때는 그냥 간단하게 break하나 추가해넣으면 되었습니다. for루프는 정해진 순서에 따라서 순차적으로 실행되기 때문에 특정 조건에서 break를 만나서 루프가 중단 된다고 하더라도 항상 취소되기 이전까지의 내용은 모두 실행이 되었음을 보장할 수 있었죠. 즉,

for (int i = 0; i < 100; i++)

{

    if (i == 50)

    {

        break;

    }

}


위와 같은 코드가 있다고 할 때, i가 50이되어서 for루프가 중단된다고 할 때, i가 0-49일때는 이미 다 진행된 상태라는 것이죠. 그런데 이런 루프를 빠르게 처리하기 위해서 여러 스레드를 동시에 사용하는 Parallel.For를 사용했다고 한다면... 각 스레드가 나눠서 진행중인 작업을 어떻게 중단 시켜야 할까요?


- 브레이크를 걸자.

일단은 기존의 for루프와 가장 유사한 형태를 보이는 것 부터 살펴봅시당. 일단 비교를 위해서 다음과 같은 예제를 먼저 살펴보겠습니다.

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            for (long i = 0; i <= num; i++)

            {

                bool isPrime = true;

                for (long j = 2; j < i; j++)

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        break;

                    }

                }

                if (isPrime)

                {

                    primeList.Add(i);

                }

            }

 

            return primeList;

        }

       

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


아주 간단한 코드인데요, 1부터 사용자가 입력한 숫자 중에서 소수를 구해서 리스트에 추가하는 코드입니다. 그리고 각 수가 소수인지 검사하는 부분에서 1이 아닌 수로 나눠지는 케이스가 발견 되면 그 즉시 break를 이용해서 중단을 시키고 있죠. 위 코드를 실행하면 CPU점유율은 아래와 같이 단일 스레드를 이용하는 것을 볼 수 있습니다.


그리고 위 코드를 3번 실행해서 얻은 평균 실행 시간은 6.28초 입니다. 그렇다면 위 코드의 Parallel.For버전을 살펴 볼까욤?

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            Parallel.For(0, num + 1, (i) =>

            {

                bool isPrime = true;

                Parallel.For(2, i, (j, loopState) =>

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        loopState.Break();

                    }

                });

 

                if (isPrime)

                {

                    primeList.Add(i);

                }

            });

 

            return primeList;

        }

       

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


for를 Parallel.For로 바꿨고, break역시 Parallel.For의 인자로 넘어오는 loopState변수에 대해서 Break메서드를 호출하는 것으로 변경되었습니다. 이 코드를 실행해보면, 아래와 같이 CPU의 4개의 코어가 모두 동작하는 것을 볼 수 있습니다.




그리고 역시 위 코드를 3번 실행해서 얻은 평균 실행 시간은 5.32 초입니다. 전에 비해서 약 15%의 성능향상이 있었습니다. 물론 소수를 구하는 알고리즘 자체를 더 개선할 수도 있지만, 단순히 싱글 스레드를 이용한 코드와 작업을 병렬화 시켜서 멀티 코어를 이용한 코드간의 차이가 이정도라면 의미가 있지 않을까요? 그리고 좀더 CPU를 많이 사용하는 코드일 수록 그 차이는 더 벌어지겠죠 :)


- 그런데 사실은...

위 코드에서 몇자를 바꾸고 나면, 코드의 평균 실행 시간이 5.29초로 조금 더 떨어지게 됩니다. 그 코드를 올려드릴 테니 어디를 수정한 건지 한번 찾아보시죵 :)

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            Parallel.For(0, num + 1, (i) =>

            {

                bool isPrime = true;

                Parallel.For(2, i, (j, loopState) =>

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        loopState.Stop();

                    }

                });

 

                if (isPrime)

                {

                    primeList.Add(i);

                }

            });

 

            return primeList;

        }

        

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


어딘지 찾으셨나요? 바로 loopState변수에 대해서 Break를 호출하던 것을 Stop으로 바꿔준 것입니다. 그래서 조금 더 효율적인 코드가 되는 것이죠. 왜 그럴까요? 그거에 대해서는!!!! 다음 포스팅에서 ㅋㅋㅋㅋㅋ


- 참고자료
http://stackoverflow.com/questions/1510124/program-to-find-prime-numbers

Inertia Tensor(1)

물리 2012. 2. 13. 00:09 Posted by 알 수 없는 사용자
이번에는 Inertia Tensor에 대해서 설명 드리겠습니다.
Inertia Tensor를 이해하기 위해서는,  운동량(Linear Momentum), 각운동량(Angulra Momentum), 질량중심(Center of Mass)에 대해서 알고 있어야 합니다. 그래서 이 3요소에 대해서 먼저 언급을 한 후에,  Inerta Tensor에 대해 설명 드리겠습니다.


1) 운동량(Linear Momentum)

운동량은 말 그대로 물체가 현재 운동하고 있는 정도를 나타냅니다.
아래의 그림처럼, 질량이 m1, 위치가 p1, 속도가 v1이라 물체가 있을때 이 물체의 운동량은 아래 식과 같습니다.




여기서 ML1은 1번 물체의 운동량(Linear Momentum)을 나타냅니다.
이 운동량을 시간에 대해서 미분하면(시간에 따른 변화를 측정) 아래와 같은 식이 나옵니다.


이 식을 보면, 물체의 운동량을 변화시키기 위해서는 힘이 필요하고, 외부의 힘이 없으면 물체의 운동량은 일정하게 유지 됩니다.
이게 바로 운동량 보존의 법칙입니다.


2) 각운동량(Angular Momentum)

각운동량은 임의의 기준점에 대한, 물체의 회전 운동량을 나타냅니다.
아래의 그림처럼, 질량이 m1, 위치가 p1, 속도가 v1이라 물체가 있을때, p점에서 측정한 이 물체의 각운동량은 아래의 식과 같습니다.




여기서 MA1은 1번 물체의 각운동량(Angular Momentum)을 나타냅니다.
이 각운동량을 시간에 대해서 미분하면(시간에 따른 변화를 측정) 아래와 같은 식이 나옵니다.

 



이 식을 보면, 물체의 각운동량을 변화시키기 위해서는 힘이 필요하고, 외부의 힘이 없으면 물체의 각운동량은 일정하게 유지 됩니다. 이게 바로 각운동량 보존의 법칙입니다.
그리고  각운동량은 운동량과 달리, 아래의 그림처럼 측정 지점에 따라 다르게 나타납니다.




3) 질량중심(Center of mass)
물체(RigidBody)는 아래의 그림처럼 미소질량들의 모임으로 해석할 수 있습니다. 


질량중심은 두가지 의미를 가지고 있습니다.
(1) 물체의 각 미소질량들이 동일한 힘을 받을때,  질량중심은 아래 그림처럼 그 물리량을 대표할 수 있는 하나의 지점을 의미합니다. 미소질량들이 받는 동일한 힘의 대표적인 예가 중력입니다. 질량중심에 반대의 힘을 가하면 물체의 운동량은 정지되게 됩니다. (중심을 잡게 됩니다.)


식으로 나타내면 아래와 같습니다.
여기서 Pc가 구하고자 하는 질량 중심이고, Pxyz는 미소질량의 위치, P는 측정 위치를 나타냅니다.



이식을 풀면, 아래과 같은 식이 나옵니다.


(2) 질량중심에서는 아래의 그림처럼 상대운동(속도, 가속도)이 모두 0이 됩니다.


식으로 나타내면 아래와 같습니다.
아래의 식을 풀어도 (1)과 같은 결과를 얻습니다.


이상으로 운동량, 각운도량, 질량중심에 대한 설명을 마치겠습니다.

 


 

'물리' 카테고리의 다른 글

Inertia Tensor(2)  (0) 2012.02.19
RigidBody의 Restitution, Friction, Damping  (2) 2012.02.06
[Bullet Physics] RigidBody 만들기  (2) 2012.01.05
[Bullet Physics] Bullet 물리엔진의 설치  (2) 2011.12.22

Welcome to Parallel C#(13) - 더 편하겡... 더 빠르겡...

C# Parallel Programming 2012. 2. 10. 09:00 Posted by 알 수 없는 사용자

- 뭐 좀 더 편한 방법이?

지난 포스팅에서는 Monitor를 좀 더 간편하게 사용할 수 있게 해주는 lock구문에 대해서 살펴봤었습니다. 그런데... 더 편한 방법이 있다는 군요. 한 줄이면 된다고 합니다. 긴 말 필요없이 예제를 보죠!

using System;

using System.Threading.Tasks;

using System.Threading;

 

namespace Exam25

{

    class Program

    {

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

            for (int i = 0; i < count; i++)

            {

                Interlocked.Increment(ref sum);

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

            for (int i = 0; i < count; i++)

            {

                Interlocked.Decrement(ref sum);

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

위 예제는 정확하게 lock을 사용한 예제와 동일한 결과를 냅니다. Interlocked라는 클래스에 정의된 메서드를 통해서 1씩 증가시키고 감소를 시킨거죠. 즉, lock사용해서 명시적으로 락을 획득하는 대신에 스레드 안전한 방식으로 값을 증가시키고 감소시키도록 하는 것입니다. 그런데, 여기까지만 보면 약간의 의문이 생깁니다. 생각보다 더 간단해 보이지가 않는 다는 말이죠. 허허허허 :)

그런데 한 가지 장점이 더 있습니다. 바로 수행 속도 인데요. 잠금을 획득하고 해제하는 게 시간이 많이 걸리는 작업이다 보니, lock을 사용하는 것보다 Interlocked를 사용하는 게 조금 더 빠릅니다. 그럼 얼마나 더 빠르다는 건지 간단한 코드를 통해서 직접 확인을 해볼까욤?

using System;
using System.Threading.Tasks;
using System.Threading;

namespace Exam26
{
    class Program
    {
        readonly static object sync = new object();
        static readonly int count = 10000000;
        static int sum = 0;       

        static void SpeedTest()
        {
            DateTime lockStart = DateTime.Now;
            for (int i = 0; i < count; i++)
            {
                lock (sync)
                {
                    sum++;
                }
            }
            DateTime lockEnd = DateTime.Now;
            TimeSpan lockResult = lockEnd - lockStart;

            sum = 0;

            DateTime InterlockedStart = DateTime.Now;

            for (int i = 0; i < count; i++)
            {
                Interlocked.Increment(ref sum);
            }
            DateTime InterlockedEnd = DateTime.Now;
            TimeSpan InterlockedResult = InterlockedEnd - InterlockedStart;

            Console.WriteLine("lock Result : {0}", lockResult);
            Console.WriteLine("Interlocked Result : {0}", InterlockedResult);
        }

        static void Main(string[] args)
        {
            //JIT 컴파일 수행
            SpeedTest();

            SpeedTest();
        }
    }
}


수행된 결과를 볼까요?

lock Result : 00:00:00.3270844
Interlocked Result : 00:00:00.1820249
lock Result : 00:00:00.3090990
Interlocked Result : 00:00:00.1820285
Press any key to continue . . .

두 번째 실행된 결과를 보면 Interlocked를 사용한 쪽이 70%정도 더 빠른 속도를 보이는 것을 할 수 있습니다. 위 코드 같이 단순한 연산이 많이 수행되는 경우에는 Interlocked를 사용하는 쪽이 조금 더 유리할 수 있겠죠. 물론 요즘 세상에 별로 신경쓸만한 차이는 아니라고 생각이 듭니다. 하하하하 그냥 이런 것도 있다는 거죠 :)

그럼, 오늘도 이만 하구요~ 다음에 또 뵙죵 하하하 :)

Welcome to Parallel C#(12) - 귀찮으면 안 해.

C# Parallel Programming 2012. 2. 8. 09:00 Posted by 알 수 없는 사용자
- 인류 발전의 원동력.

귀.찮.다. 이 세자로 이루어진 표현이 인류의 발전을 이끌어왔다면 확대해석일까욤. 정말 죠낸죠낸쵸오낸 귀찮다 보니 사람들은 어떻게 하면 반복적인 작업을 줄일 수 있을까 하는 문제를 생각하게 됩니다. 그래서 더 나은 방법을 고안해 내고, 더 빠르고 더 간편한 시스템, 그리고 나아가서 자동화를 생각하게 됩니다. 그러다 보니, 수작업으로 이루어진 프로세스는 실수의 여지가 많은 반면에 자동화로 이루어진 프로세스에서는 그 여지가 굉장히 줄어들게 되는 부수적인 효과를 얻게 되는 것이죠.

물론 귀찮음으로 인해서 인류는 많은 퇴보도 했을 겁니다. 복잡하고 비효율적인 것도 몸에 익으면 편하게 느껴지게 됩니다. 그리고 더 깔끔한 방법으로 바꾸려 하면 머리속에서는 엄청난 저항이 일어나게 되죠. 이미 한번 구성되어서 굳어진 뇌의 회로를 다시 구성해야 한다는 그...... 귀찮음. 허허허허허허


- 락에 자동화를 도입!

지난 포스트에서 Monitor를 이용해서 락을 거는 방법을 알아봤습니다. 하지만 어떤가요? 매번 try-finally블록을 사용해서 락을 걸고 해야 한다면? 그리고 Enter는 있는데 Exit를 깜빡하게 빼먹었다면? 어머나!!!!

그래서 C#에서는 귀찮음을 싫어하는 인류의 특성을 감안해서 간편한 Syntactic Sugar를 제공합니다. lock이라는 구문이 바로 그것입니다. 하하하하 :) 그럼 간단한 예를 보죠.

using System;

using System.Threading.Tasks;

 

namespace Exam24

{

    class Program

    {

        readonly static object sync = new object();

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

            for (int i = 0; i < count; i++)

            {

                lock (sync)

                {

                    sum += 1;

                }

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

            for (int i = 0; i < count; i++)

            {

                lock (sync)

                {

                    sum -= 1;

                }

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

어떤가요? Monitor를 사용해서 해주던 번거로운 작업이 lock이라는 구문으로 임계영역을 감싸주는 것으로 간단하게 해결 되었습니다. 자, 그럼 lock이라는 편리한 구문 뒤에 실제로 생성되는 코드를 한번 확인해 볼까욤?

private static void Main(string[] args)

{

    Task task = Task.Factory.StartNew(new Action(Program.IncreaseByOne));

    for (int i = 0; i < count; i++)

    {

        object CS$2$0000;

        bool <>s__LockTaken1 = false;

        try

        {

            Monitor.Enter(CS$2$0000 = sync, ref <>s__LockTaken1);

            sum--;

        }

        finally

        {

            if (<>s__LockTaken1)

            {

                Monitor.Exit(CS$2$0000);

            }

        }

    }

    task.Wait();

    Console.WriteLine("Result = {0}", sum);

}

 

private static void IncreaseByOne()

{

    for (int i = 0; i < count; i++)

    {

        object CS$2$0000;

        bool <>s__LockTaken0 = false;

        try

        {

            Monitor.Enter(CS$2$0000 = sync, ref <>s__LockTaken0);

            sum++;

        }

        finally

        {

            if (<>s__LockTaken0)

            {

                Monitor.Exit(CS$2$0000);

            }

        }

    }

}

위에서 볼 수 있듯이 생성되는 코드는 Moniter를 이용한 코드와 정확하게 같은 코드를 사용하고 있습니다. 대신에 훨씬 간단하죠? 물론 lock구문을 통해서 락을 걸어줄 객체는 Monitor를 사용할 때와 동일하게 readonly static이어야 합니다.
 
락을 거는 대상이 되는 잠금 객체에 대해서 조금 덧붙이자면, 락을 걸어줄 객체는 값 형(Value 타입)이 아닌 참조 형(Reference 타입) 객체여야 합니다. 왜냐면, 값 형의 경우는 다른 변수에 값을 대입할 때 값을 복사해서 넘겨 주기 때문에 매번 메모리에 동일한 값이 새로 생겨나게 됩니다. 이렇게 되면 Enter메서드에서 받는 잠금 객체와 Exit메서드에서 받는 잠금 객체가 서로 다른 객체가 되므로 SynchronizationLockException이 발생하게 됩니다. 반면에 참조 형 객체의 경우는 동일한 객체에 대한 참조를 복사해서 넘겨주므로 항상 동일한 객체에 대해서 Enter와 Exit를 호출하게 되겠죠.

그럼~ 다음에 또 뵙죵~ :)

Welcome to Parallel C#(11) - 동기 부여.

C# Parallel Programming 2012. 2. 6. 09:00 Posted by 알 수 없는 사용자

우와~!!!! 증말 오랜만입니다. 이게 얼마만이죠? 하하하하하하하하-_-

기다리는 분이 아무도 없다는 건 알지만, 저 자신을 위해서 그냥 한번 호들갑을 떨어봤습니다. 허허허허 원래 제가 쓰는 포스팅이 다 그렇죠. 병렬 프로그래밍에 대해서 정리해놓은 자료가 조금 있었는데요, 매우 늦긴 했지만 그래도 일단 공부하면서 정리해놓은 자료를 그냥 썩히기는 아까워서 조금씩 옮겨 적어볼까 합니다. 그래서 다시 시작하는 거지요! 호호호호 :)

언제나 그렇듯이 제 포스팅은 공부하면서 정리하는 겁니다. 다른 분들이 적으시는 것 처럼 내공이 깊지도 않고, 그다지 실용적이지도 않지요. 의견이 있으시면 거침없이! 하지만 예의는 쪼금 지켜 주시면서! 가르침을 더 해주시면 읽으시는 분들에게 더 도움이 될 겁니다. 짧은 인생 Love & Peace! 그럼 또 초큼씩 풀어 볼까욤? ㅋㅋㅋ


- 화장실이 한 개!!!

4인 가족 기준으로 화장실이 한 개라고 생각을 하면..... 어머나!!! 끔찍해!!! 출근 시간이 되면 화장실에 응가를 때리러 러시아워가 벌어집니다. 먼저 들어간 사람은 느긋하게 다이빙하는 응가에 예술 점수를 매기면서 시간을 보낼 수 있지만, 출근 시간과 이제 그만 포기하자고 하는 장과 협상을 벌이면서 조마조마해야 하죠. 화장실을 원하는 사람은 4명인데, 화장실이 하나이다 보니, 먼저 들어간 사람이 화장실을 선점하고 문을 잠가서 락을 걸어버리면, 화장실을 원하는 다른 사람들은 그 락이 해제 될 때까지 인고의 시간을 보내야 하는 거죠. 어머나 슬픈이야기...ㅠㅠ

그러면 락을 걸지 못하게 하면 효과적인 해결책이 될까요? 일보고 있는데 갑자기 문을 열고 들어와서는 '난 얼굴 씻고 이빨만 닦으련다 변기를 쓰는 것도 아니니 너랑 내가 이 화장실을 평화롭게 공유하면서 각박한 출근 시간에 조그마한 훈훈함이라도 나눠가지지 않겠느냐' 하면 뭐 괘안을 수도 있겠습니다만.... 조금만 더 생각을 해보면 말이죠. 응가라는 건 굉장한 집중이 요구됩니다. 그리고 아무도 보는 사람이 없다는 안도감 속에서야 차분히 진행될 수 있는 일종의 비밀 의식 같은 거죠. 화장실을 공유하는 훈훈함 속에서 응가를 제대로 할 수 는 없습니다. 그리고 세면을 하는 사람 역시 은은하게 풍겨나오는 향기에 정신을 빼앗기다 보면 어서빨리 탈출하고 싶은 욕구에 사로잡히게 되어 세면을 제대로 할 수 없게 됩니다.

즉, 락을 거는 게 가장 효율적이라는 거죠. 여담이지만 그래서 저는 공중화장실에서는 응가를 안하는 편입니다. 정신 사나워서 집중을 할 수 없거든요. :)


- 그래서 필요한 동기화

그래서 프로그래밍을 하면서도 동기화에 신경을 써줘야 합니다. 컴퓨터의 자원을 한정적이고 그걸 호시탐탐 선점하려는 스레드의 욕구를 잘 제어해줘야 하기 때문이죠. 그럼 동기화 없이 스레드의 탐욕에 맡긴 결과가 어떻게 되나 한번 볼까요. 후후후후

using System;

using System.Threading.Tasks;

 

namespace Exam22

{

    class Program

    {

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

            for (int i = 0; i < count; i++)

            {

                sum += 1;

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

            for (int i = 0; i < count; i++)

            {

                sum -= 1;

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

두 개의 스레드를 이용해서, 주 스레드에서는 sum의 값을 1씩 감소 시키고, 추가 스레드에서는 증가 시키도록 해놓은 평범한 예제입니다. 하지만, 서로 뺏고 뺏기는 리얼 야생 탐욕이 살아 숨쉬는 정글의 법칙이 녹아들어 있습니다. 결과를 보시죠.

Result = 17104

계속하려면 아무 키나 누르십시오...

분명히 같은 횟수로 1씩 증가시키고 감소를 시켰는데 이런 결과가 나오다니!!! 매번 실행 할때 마다 다른 값이 나오긴 하지만, 이 결과는 분명 이상합니다. 오래전 포스팅이지만 원자성에 대해서 잠깐 이야기를 한적이 있었는데요, 특정 작업이 실행 될 때 그 작업 외에 다른 작업이 끼어들어서는 안된다는 원칙을 이야기 하는 것입니다. 그렇다면! 위에서 사용중인 +=, -= 연산자는 분명히 원자성을 위반하고 있는 것으로 보입니다.

단순하게 'a += 1' 을 가지고 생각해볼까욤. 이 연산은 몇 가지 단계 나누어 집니다. 우선 a의 값을 가져오고, 가져온 값을 1 증가 시키고, 증가된 값을 다시 a에 대입하는 단계로 나누어 지는 거죠. 그런데 a값을 가져온 직후에 다른 스레드가 a의 값을 변화시킨다면 어떻게 될까요? 이런 거 말로 설명하면 설명하는 사람도 읽는 사람도 불통에 빠지게 됩니다. 현재 sum이라는 변수의 값이 5라고 가정하고 간단하게 표를 통해 설명 해보죠 ㅋ

 증가 스레드(sum += 1 수행) 감소 스레드(sum -= 1 수행) 
   5를 읽어옴
 5를 읽어옴  
 1증가시킴  
 6을 저장  
  1감소시킴 
  4를 저장 

위와 같은 순서로 수행이 된다면?

분명히 1증가와 1감소가 수행되었지만, 결과는 1감소만 수행된 셈이죠. 이런식으로 서로 방해와 방해를 하다보면 이상한 결과가 나오게 되는 겁니다. 그래서 화장실에 락을 걸어서 순서를 정해서 사용하도록 하듯이, 순서를 정해줘야 하는 겁니다. 하하하하 :)

- 모니터로 락을 걸자! 락을 걸자!

자 그럼 아주 간단하게, 모니터로 락을 걸어 볼까요? 준비물은 여러분이 지금 보고 있는 모니터를 사용하면 됩니....는 훼이크고 Monitor라는 클래스를 이용해서 락을 걸어보겠습니다. Enter와 Exit라는 메서드를 이용해서 락을 걸고 빠져나가도록 통제를 하게 되는 데요. 참고로 이렇게 둘 이상의 스레드가 동시에 접근하면 안되기 때문에 락을 걸어야 하는 부분을 임계여역(Critical Section)이라고 합니다. 허허허허. 그럼 Monitor를 사용한 예제를 한번 보시죰.

using System;

using System.Threading.Tasks;

using System.Threading;

 

namespace Exam23

{

    class Program

    {

        readonly static object sync = new object();

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

            for (int i = 0; i < count; i++)

            {

                bool locked = false;

                Monitor.Enter(sync, ref locked);

                try

                {

                    sum += 1;

                }

                finally

                {

                    if (locked)

                    {

                        Monitor.Exit(sync);

                    }

                }

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

            for (int i = 0; i < count; i++)

            {

                bool locked = false;

                Monitor.Enter(sync, ref locked);

                try

                {

                    sum -= 1;

                }

                finally

                {

                    if (locked)

                    {

                        Monitor.Exit(sync);

                    }

                }

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

자, 코드를 보면 락이 걸려있는지를 판별하기 위해서 readonly static으로 sync라는 변수를 선언하고, 그 변수에 대해서 락을 걸로 해제하는 것으로 특정영역에 대한 락을 걸도록 했습니다. 그리고 Monitor를 통해서 들어가고 나오는 순서를 정해주고 있죠. 만약에 주 스레드에서 sync에 대해서 락을 획득하고 sum을 감소시키는 중이라면, 다른 스레드는 sync에 대한 락이 해제 될 때까지 임계영역에 들어가지 못하고 대기하게 됩니다. 그렇다면? 각자가 원하는 작업을 하는 동안 다른 스레드가 방해를 하지 못하겠죵?

참고로 락을 걸고 해제하는 대상이 되는 sync라는 객체를 보면, readonly이므로 객체의 값을 변경시킬 수 없는 immutable한 객체이고, static이므로 유일하게 존재하게 됩니다. 이렇게 복사본이 없고, 값을 변경시킬 수 없는 객체에 대해서 락을 거는 것이 안전한 방법이지요. 위 코드를 실행하면 다음과 같은 결과가 나오게 됩니다.

Result = 0

계속하려면 아무 키나 누르십시오...

이거시 바로 우리가 기대했던 결과지요. 하하하하하하하하. 아... 좀 불편한 자세로 포스팅을 하고 있으려니 허리가..... 일단 오늘은 여기까지 하겠슴니다 하하하하 :)

RigidBody의 Restitution, Friction, Damping

물리 2012. 2. 6. 00:40 Posted by 알 수 없는 사용자

안녕하세요. 이번에는 RigidBody의 속성인 restitution, friction, damping에 대해서 간략하게 설명 드리겠습니다.

1) Restitution
Restitution은 복원력 또는 탄성계수와 같은 의미로 생각하시면 됩니다.
두물체가 충돌했을때 발생하며, 범위는 [0 - 1]입니다. 1은 얌체공과 같은 완전 탄성체를 의미하고, 0은 탄성이 전혀없는 물체를 나타냅니다.(Bullet에서 default값은 0입니다.)
A물체와 B물체가 충돌했을때, A와B의 Restitution은 두 물체 Restitution의 곱으로 결정 됩니다.
아래의 그림처럼 Restitution의 값을 바꾸어 가며 테스트 해보시면 됩니다.



2) Friction
Friction은 마찰력입니다.
접촉 되어 있는 두 물체 사이에 발생하며, 범위는 [0 - 1]입니다. 1은 두 물체사이의 미끄러짐이 전혀 없는 상태이고, 0은 얼음위에서 미끄러지는 것과 같은 상태입니다.(Default는 0.5입니다.)
A와B 사이의 Friction은 역시, 두 물체의 Friction의 곱으로 결정됩니다.
아래의 그림처럼 Friction의 값을 바꾸어 가며 테스트 해보시면 됩니다.



3) Damping
Damping은 물체가 움직일때, 그 속도에 대한 대기(공기, 물, 등등)의 저항력을 의미 합니다.
예를 들면, 물위에서 공을 던지면 잘 날라가지만, 물속에서는 조금 날아가다가 속도가 0이 됩니다. 이것은 공기의 Damping은 작고 물의 Damping이 크기 때문에 나타나는 현상입니다.
Damping의 범위는 [0 - 1]입니다. 0은 저항력이 전혀 없는 상태이고, 1는 저항력이 너무커서 움직이지 못하는 상태입니다.
아래의 그림처럼 Damping의 값을 바꾸어 가며 테스트 해보시면 됩니다.(Linear와 Angular로 나누어져 있지만 개념은 같습니다.)


아래 소스버전을 참조했습니다.


소스 빌드방법
빌드를 하기 위해서는 Bullet 2.79버전과 wxWidgets 2.8.12버전이 있어야 됩니다.


1) Bullet 빌드
Bullet 2.79는 앞의 Bullet설치 페이지를 참고 하시면 됩니다.

2) wxWidgets 빌드
(1) wxWidgets 2.8.12버전을 아래의 사이트에 다운 받습니다.
http://sourceforge.net/projects/wxwindows/files/2.8.12/wxWidgets-2.8.12.zip/download

(2)"wxWidgets-2.8.12\build\msw\wx.sln" 솔루션 열기를 합니다.(솔루션이 2005버전입니다 변환 메시지가 나오면 모두 "확인"을 누릅니다.)
Debug/Relase 모두, 아래의 그림처럼 솔루션 빌드를 합니다.(빌드 후, 오류가 있으면 다시 한번 솔루션 빌드를 합니다.)
 



3) 경로설정
(1) 소스의 압축을 푼 후, "RigidBody.sln" 솔루션 열기를 합니다.

(2) 속성창(Property Manager)을 열어 아래 그림처럼 Bullet 경로를  자신의 경로로 설정함.


(3) 속성창(Property Manager)을 열어 아래 그림처럼 wxWidgets 경로를  자신의 경로로 설정함.

4) 소스 실행

'물리' 카테고리의 다른 글

Inertia Tensor(2)  (0) 2012.02.19
Inertia Tensor(1)  (0) 2012.02.13
[Bullet Physics] RigidBody 만들기  (2) 2012.01.05
[Bullet Physics] Bullet 물리엔진의 설치  (2) 2011.12.22