Menu

문서정보

Jump 기반

package main

import (
    "fmt"
)

type jump struct {
    max int64
    j   int64
    b   int64
    key uint64
}

func (self jump) getRandom(key uint64) int32 {

    self.max = self.max - 1
    if self.max <= 0 {
        self.max = 1
    }
    self.key = key

    for self.getZeroOrOne() == 1 {
    }

    return int32(self.b)
}

func (self *jump) getZeroOrOne() int64 {
    if self.j > self.max {
        return 0
    }
    self.b = self.j
    self.key = self.key*2862933555777941757 + 1
    self.j = int64(float64(self.b+1) * (float64(int64(1)<<31) / float64((self.key>>33)+1)))
    return 1
}

func main() {
    var i uint64
    rand := jump{max: 10}
    for i = 0; i < 10; i++ {
        fmt.Println(rand.getRandom(i))
    }
}

		

성능은 아래와 같다. getRandom(100)을 10000회 실행한 결과다.

 Random 성능

Rand Mod 기반

package main

import (
    "fmt"
    "math/rand"
)

type jump struct {
    max   int64
    count int32
    loop  int64
}

func (self jump) getRandom(key int32) int32 {
    var rbuff int32
    for i := 0; i < 30; i++ {
        r := self.getZeroOrOne()
        rbuff = rbuff << 1
        if r == 1 {
            rbuff = rbuff | 1
        }
    }
    return rbuff % key
}
func (self *jump) getZeroOrOne() int64 {
    rtv := rand.Int63() % 2
    return rtv
}

func main() {
    rand := jump{max: 10}
    for i := 0; i < 10; i++ {
        fmt.Println(rand.getRandom(10))
    }
}

		

 Random2 성능

Random 일반

컴퓨터는 입력에 따라 출력이 정해진다. 따라서 컴퓨터는 의사난수만을 만들 수 있다. 이 입력 값을 Seed라고 한다. Seed 값에 따라서 난수표가 달라진다. Seed를 이용하는 난수 발생기는 Seed를 예측 할 수 있기 때문에, 시간을 이용해서 Seed를 예측하기 힘들게 한다. 보통은 밀리초 단위로 설정하기 때문에 인간 레벨에서 같은 seed를 찾는 건 불가능에 가깝다.

시간을 이용해서 Seed를 예측하기 힘들게 할 수는 있지만, 만들어진 대략적인 시간을 안다면 컴퓨터를 이용해서 계산을 할 수 있기 때문에 안전한 방법은 아니다. 리눅스는 /dev/random과 /dev/urandom 디바이스로 해결하고 있다. 이 디바이스는 Hardware random generator로 IO 디바이스 드라이버에서 발생하는 입력 신호의 노이즈를 모은 정보를 이용해서 난수를 만든다. 이건 컴퓨터를 이용한다고 하더라도 현실적으로 예측 할 수 없기 때문에 매우 안전하게 사용 할 수 있다.

/dev/random과 /dev/urandom의 차이는 노이즈 수집방식에 있다. /dev/random은 노이즈가 충분히 수집되기 전에는 난수를 발생하지 않는다. 즉 블럭킹된다. 실제 난수 입력을 요청하는 몇몇 애플리케이션들은 빠르게 난수를 만들기 위해서 마우스나 키보드 입력을 요구하기도 한다. /dev/urandom은 노이즈가 충분히 수집되지 않아도 블럭킹하지 않고 난수를 만들 수 있다.

아래 예제 코드는 math/rand와 /dev/urandom 예제다.
/*
0 ~ (max - 1) 범위에서 랜덤 값을 출력한다.
math/rand와 crypto/rand 두 개 패키지에서 제공하는 랜덤함수를 사용하고 있다. 
crypto/rand는 /dev/urandom 커널 디바이스를 기반으로 랜덤값을 출력한다. 

두 개 패키지의 랜덤함수를 하나의 main 함수에서 테스트 하기 위해서 interface 를 
이용했다.
*/
package main

import (
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	prand "math/rand"
	"os"
	"time"
)

var (
	ZeroBoundaryError = errors.New("Can't mod by zero")
)

type Random interface {
	getZeroOrOne() (uint32, error)
	getRandom(uint32) (uint32, error)
	printType()
}

type uRand struct {
	randBuff []byte
}

func (u uRand) printType() {
	fmt.Println("/dev/urandom ------")
}

func (u uRand) getZeroOrOne() (uint32, error) {
	_, err := rand.Read(u.randBuff)
	if err != nil {
		return 0, err
	}
	return binary.LittleEndian.Uint32(u.randBuff), nil
}

func (u *uRand) getRandom(max uint32) (uint32, error) {
	if u.randBuff == nil {
		fmt.Println("Rand Init")
		u.randBuff = make([]byte, 4)
	}
	if max == 0 {
		return 0, ZeroBoundaryError
	}
	num, err := u.getZeroOrOne()
	if err != nil {
		return 0, err
	}
	return num % max, nil
}

type mRand struct {
	source *prand.Rand
}

func (m mRand) printType() {
	fmt.Println("Go Random function ------")
}

func (m mRand) getZeroOrOne() (uint32, error) {
	return m.source.Uint32(), nil
}

func (m *mRand) getRandom(max uint32) (uint32, error) {
	if m.source == nil {
		fmt.Println("Rand Init")
		seed := time.Now().Unix()
		s2 := prand.NewSource(seed)
		m.source = prand.New(s2)
	}
	num, err := m.getZeroOrOne()
	if err != nil {
		return 0, err
	}
	return num % max, nil
}

func main() {
	randomEngine := [2]Random{&uRand{}, &mRand{}}
	for _, engine := range randomEngine {
		engine.printType()
		for i := 0; i < 10; i++ {
			val, err := engine.getRandom(10000)
			if err != nil {
				fmt.Println(err.Error())
				os.Exit(1)
			}
			fmt.Println(val)
		}
	}
	os.Exit(0)
}

		

참고