MSDN에 있는 글을 옮겨와 봤습니다^^

 

다른 프로세서에서 사용되고 있는 복수의 동시실행 태스크가 같은 캐시라인에 배치되어 있는 변수에 쓰기를 하면 거짓 공유가 발생한다. 하나의 태스크가 어떤 변수에 쓰기를 하면 양쪽 변수의 캐시라인이 무효화된다. 캐시라인이 무효화 될 때마다 재 로딩이 필요하게 된다. 즉 거짓 공유가 발생하면 애플리케이션의 성능이 저하할 위험이 크다.

 

long count1 = 0L;

long count2 = 0L;

concurrency::parallel_invoke(

   [&count1] {

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

         ++count1;

   },

   [&count2] {

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

         ++count2;

   }

);

long count = count1 + count2;

위의 코드는 태스크에 공유 변수를 사용하지 않기 위해 count1, count2로 변수를 나누어서 사용하고 있다. 그래서 일견 보기에는 서로 독립적으로 실행될 것이라고 생각할 수 있다. 그러나 위에 설명했듯이 count1 count2는 서로 같은 캐시라인에 배치될 확률이 높아서 결과적으로 거짓 공유가 발생한다.

 

이 문제를 해결하기 위해서는 아래처럼 메모리 정렬에 의해 서로 다른 캐시라인에 배치되도록 한다.

__declspec(align(64)) long count1 = 0L;     

__declspec(align(64)) long count2 = 0L;     

concurrency::parallel_invoke(

   [&count1] {

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

         ++count1;

   },

   [&count2] {

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

         ++count2;

   }

);

long count = count1 + count2;

핵심은 __declspec(align(64))으로 메모리 캐시 사이즈가 64바이트 이하인 경우 같은 캐시라인을 사용하지 않도록 해준다.

 

 

__declspec 설명

http://blog.naver.com/riverrun27/40126498899

 

 

by 흥배 2014. 2. 3. 08:00
  • 크로스 2014.02.03 10:30 ADDR EDIT/DEL REPLY

    msdn에 있는 내용이면 VS계열의 컴파일러에서만 동작하는 키워드인가요?
    gcc등으로 컴파일해서 리눅스계열의 운영체제에서 돌아갈 수 있는 프로그램은 못만드나 싶어서 여쭤봅니다 ^^;;
    만약 어떤 프로그램을 리눅스로 개발해야 한다면 위 키워드는 사용할수 없는지요?

    • 흥배 2014.02.03 15:43 신고 EDIT/DEL

      구글링 해보니 gcc에서는 __attribute__ 라는 키워드를 사용한 것 같습니다. http://stackoverflow.com/questions/7281699/aligning-to-cache-line-and-knowing-the-cache-line-size

  • ruwin126 2014.05.01 22:15 ADDR EDIT/DEL REPLY

    캐시 라인이 64 byte 이하여야 하고(이건 지금 나오는 대부분의 머신이 만족하겠지만) 120바이트의 낭비되는 공간이 있다는 것에서 딱히 추천할 만한 방법은 아닌 것 같네요.
    combinable을 이용하는건 어떻게 생각하세요?

잘못된 SqlConnection

 

// 코드 1

using (var connection = new SqlConnection("접속문자열"))

{

    connection.Open();

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}

  • <코드 1>을 실행하면 크래쉬가 발생

  • 이유는 간단 SqlConnection가 스레드 세이프 하지 않기 때문
  • 위의 코드를 올바르게 수정하려면 아래와 같이 변경
     

    // 코드 2

    Parallel.For(1, 1000, x =>

    {

        using (var connection = new SqlConnection("접속문자열"))

        {

            connection.Open();

            var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

        }

    });

  • <코드 2>는 안전하지만 아마 위 방식은 원하는 방식이 아닐 것이다.

 

 

ThreadLocal

  • 위의 문제를 ThreadLocal을 사용하여 해결해 보자
     

    // 코드 3

    using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

    {

        Parallel.For(1, 1000, x =>

        {

            var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

        });

    }

  • 성능 테스트를 해보면 싱글 스레드를 사용하면 16초, <코드 2>는 5초, <코드 3>은 2초

 

 

<코드 3>은 안전한가?

  • <코드 3>은 위험한 코드
    • 이유는 연결한 후 Dispose 하지 않기 때문.
    • ThreadLocal의 Dispose는 어디까지는 ThreadLocal의 Dispose이기 때문에 그 안의 객체를 Dispose 해주지 않는다.
  • 이 문제는 trackAllValues 라는 옵션을 사용하여 간단하게 해결 가능!

 

// 코드 4

using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }

    , trackAllValues: true)) // ThreadLocalの.Values 프로퍼티의 참조를 유효화 한다

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

    

    // 생성된 모든 Connection을 일괄적으로 Dispose

    foreach (var item in connection.Values.OfType<IDisposable>()) item.Dispose();

}

  • 단 trackAllValues는 닷넷프레임워크 4.5 부터 사용 가능

 

 

trackAllValues 랩퍼 클래스 사용

 

public static class DisposableThreadLocal

{

    public static DisposableThreadLocal<T> Create<T>(Func<T> valueFactory)

        where T : IDisposable

    {

        return new DisposableThreadLocal<T>(valueFactory);

    }

}

    

public class DisposableThreadLocal<T> : ThreadLocal<T>

    where T : IDisposable

{

    public DisposableThreadLocal(Func<T> valueFactory)

        : base(valueFactory, trackAllValues: true)

    {

    

    }

    

    protected override void Dispose(bool disposing)

    {

        var exceptions = new List<Exception>();

        foreach (var item in this.Values.OfType<IDisposable>())

        {

            try

            {

                item.Dispose();

            }

            catch (Exception e)

            {

                exceptions.Add(e);

            }

        }

    

        base.Dispose(disposing);

    

        if (exceptions.Any()) throw new AggregateException(exceptions);

    }

}

   

// 사용

using (var connection = DisposableThreadLocal.Create(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}

 

 

 

출처: http://neue.cc/2013/03/09_400.html

by 흥배 2013. 10. 11. 08:00
| 1 |