2011-12-14 2 views
0

스레드 당 하나의 난수 생성기를 사용하는 방법을 찾고 동시에 프로그램을 다시 실행하면 동일한 숫자가 생성되도록하는 데 어려움을 겪고 있습니다. '데이터'목록을 작성 randomgenerator가 씨앗을 제공하고, 계산에 사용되는 randomgenerators가에 따라 씨앗을 제공하고 있기 때문에알려진 시드가있는 ThreadLocal 임의 생성기 만들기

class Program { 
    static void Main(string[] args) { 

     var seed = 10; 
     var data = new List<double>(); 
     var dataGenerator = new Random(seed); 

     for (int i = 0; i < 10000; i++) { 
      data.Add(dataGenerator.NextDouble()); 
     } 

     var results = new ConcurrentBag<double>(); 

     Parallel.ForEach(data, (d) => { 
      var result = Calculate(d, new Random(d.GetHashCode()); 
      results.Add(result); 
     }); 

    } 

    static double Calculate(double x, Random random) { 
     return x * random.NextDouble(); 
    } 
} 

: 나는 지금 무엇

이 같은 것입니다 처리중인 번호의 해시 코드는 결과가 반복 가능합니다. 스레드의 수와 인스턴스화 된 순서에 관계없이

각 스레드에 대해 하나의 임의 생성기를 인스턴스화 할 수 있는지 궁금합니다. 다음 코드 조각은이를 수행하는 것으로 보이지만 무작위 생성기에 (재생산 가능한) 시드가 더 이상 제공되지 않으므로 결과가 반복되지 않습니다.

class Program { 
    static void Main(string[] args) { 

     var seed = 10; 
     var data = new List<double>(); 
     var dataGenerator = new Random(seed); 

     for (int i = 0; i < 10000; i++) { 
      data.Add(dataGenerator.NextDouble()); 
     } 

     var results = new ConcurrentBag<double>(); 

     var localRandom = new ThreadLocal<Random>(() => new Random()); 

     Parallel.ForEach(data, (d) => { 
      var result = Calculate(d, localRandom.Value); 
      results.Add(result); 
     }); 

    } 

    static double Calculate(double x, Random random) { 
     return x * random.NextDouble(); 
    } 
} 

누구나이 문제에 대한 좋은 해결책을 생각할 수 있습니까?

+0

나는 그것이 가능하지 않다고 생각합니다. 첫 번째 솔루션은 무작위 생성기 수가 스레드 수와 독립적이기 때문에 작동합니다. 두 번째 경우에는 스레드 당 하나의 랜덤 생성자를 생성 할 때 시드를 초기화하는 일관된 방법을 찾더라도 결과가 스레드 수에 따라 다릅니다. 새 번호를 생성 할 때마다 시드를 제공 할 수 있도록 임의의 사용자 지정 구현을 만들 수 있습니까? –

+0

Bummer .. 매번 새로운 시드를 설정하는 것은 매번 새로운 랜덤을 인스턴스화하는 것과 동일하다고 생각합니다. – jkokorian

+0

예, 그렇습니다. 스레드 당 하나의 무작위 인스턴스를 사용하려는 이유가 있습니까? –

답변

3

사실, 당신이 질문에 올바르게 할 수는 있지만 문제는 그것이 당신이 원하는 것이 아닙니다.

매번 동일한 숫자로 스레드 로컬 Random을 시드 한 경우 이전 작업 수와 관련하여 해당 스레드 내에서 결과를 결정적으로 만들 수 있습니다. 원하는 것은 입력과 관련하여 결정 론적 인 의사 난수입니다.

음, 그냥 Random()을 붙일 수 있습니다. 그것은 그렇게 무거운 것이 아닙니다.

또는 자신의 의사 랜덤 알고리즘을 사용할 수 있습니다. 여기에 (더 나은 해시 코드의 비트를 배포 할 목적으로) 재 해싱 알고리즘을 기반으로 간단한 예는 다음과 같습니다

private static double Calculate(double x) 
{ 
    unchecked 
    { 
    uint h = (uint)x.GetHashCode(); 
    h += (h << 15)^0xffffcd7d; 
    h ^= (h >> 10); 
    h += (h << 3); 
    h ^= (h >> 6); 
    h += (h << 2) + (h << 14); 
    return (h^(h >> 16))/(double)uint.MaxValue * x; 
    } 
} 

이 특히 좋은 의사 난수 생성기 아니지만, 꽤 빠릅니다. 또한 할당을 수행하지 않으므로 가비지 수집을하지 않습니다.

이 전체 접근 방식의 절충안이 있습니다. 당신은 위를 단순화 할 수 있고 더 빨라지지만 덜 "무작위 적"이되거나 더 많은 노력을하기 위해 더 "무작위 적"이 될 수 있습니다. 위의 코드보다 더 빠르고 더 "무작위 적"인 코드가있을 것입니다. 다른 어떤 것보다 그 접근법을 더 보여줄 것이지만, 라이벌 알고리즘 중에서는 품질의 균형을 찾고 있습니다. 생성 된 숫자 대 성능 new Random(d).NextDouble()은 그 트레이드 오프의 특정 시점에 있으며, 다른 접근법은 다른 시점에 있습니다.

편집 : 내가 사용한 재전송 알고리즘은 Wang/Jenkins 해시입니다. 내가 그것을 썼을 때 나는 그 이름을 기억할 수 없었다.

편집 : 코멘트에서 사용자의 요구 사항에의 더 나은 생각하는 데, 지금은 말할 것 ...

을 당신은, 그 위의 알고리즘을 사용할 수하는 PRNG 클래스를 만들 System.Random의 원하는 (복용 반사 된 코드를 시작점으로 사용), 언급 한 128 비트 XorShift 알고리즘 또는 기타. 중요한 차이점은 Reseed 메서드를 가져야한다는 것입니다. 예를 들어, System.Random의 접근 방식을 복사하면 reseed는 대부분의 생성자 본문처럼 보일 것입니다. 실제로 (실제로는 사용하는 배열을 만들지 않고 생성자가 reseed를 호출 할 수 있도록 리 팩터를 사용합니다).

그런 다음 스레드 당 인스턴스를 만들고 기존 코드에 Random을 새로 만들려는 시점에 .Reseed(d.GetHashCode())을 호출하십시오.

또 다른 장점이 있습니다. PRNG의 일관된 결과에 의존하는 경우 (프레임 워크 버전간에 일관된 알고리즘을 약속하지 않았다는 사실입니다. 패치 및 보안 픽스 포함)은 당신에게 나쁜 포인트이며,이 접근법은 일관성을 추가합니다.

그러나 일치 알고리즘을 double.GetHashCode()에게 약속하지 않았습니다. 나는 그들이 (자주 변경되는, string.GetHashCode() 달리) 변경 거라고 의심 싶지만, 다만 경우에 당신이 만들 수있는 당신의 Reseed() 걸릴 이중 무언가 같은 :

private static unsafe int GetSeedInteger(double d) 
{ 
    if(d == 0.0) 
    return 0; 
    long num = *((long*)&d); 
    return ((int)num)^(int)(num >> 32); 
} 

거의 바로 복사 현재 double.GetHashCode() ,하지만 이제는 프레임 워크 변경에 직면하게 될 것입니다.

작업 세트를 직접 청크로 분해하고 각 청크에 대한 스레드를 만든 다음이 개체를 청크 별 방법으로 로컬로 생성하는 것이 좋습니다.

장점 :

ThreadLocal<T> 액세스는 로컬 T 액세스보다 더 비싸다.

작업의 상대 시간이 일정하면 Parallel.ForEach의 영리함이 많이 필요하지 않습니다.

단점 :

Parallel.ForEach 사물을 균형 정말 좋다. 당신이하고있는 일은 매우 자연스럽게 균형을 이루어야합니다. 또는 사용을 피하기 전에 미리 큰 덩어리로 저장해야합니다.

+0

맞습니다. 제 구현에서 임의 생성기를 만드는 것은 이제 성능에 있습니다 왜냐하면 새로운 임의 생성기가 각 반복마다 인스턴스화되기 때문입니다. psrg와 병렬 처리가 공통적 인 문제이기 때문에 '할 수있는'방법이 있는지 궁금합니다. – jkokorian

+0

사람들은 알고리즘이 어쨌든 그 스펙의 일부인 아주 정확하게 특정 된 경우를 제외하고는 결정 론적 산출물을 피하기보다는 피하고 싶어합니다. 여기서 답을 해봤습니까? –

+0

나는 그것이 우리의 응용 프로그램에 맞는 것 같지 않기 때문에 대답을 시도하지 않았습니다. 생성되는 난수의 무작위성이 매우 중요한 몬테카를로 시뮬레이션입니다 (재현성이 있어야합니다). 또한 Calculate 메서드에 전달 된 임의 생성자는 많은 다른 메서드로 전달되므로 .Next() 메서드가있는 'portable'개체 여야합니다. 현재 코드 플렉스에서이 http://www.codeproject.com/KB/recipes/Random.aspx?msg=2906065 프로젝트의 128bitXorShift 랜덤 생성기를 사용하고 있습니다. – jkokorian