>

80 바이트의 데이터가 있고 마지막 4 바이트 만 지속적으로 변한다고 가정하면 Go를 사용하여 총 80 바이트를 어떻게 효율적으로 해시 할 수 있습니까? 본질적으로 처음 76 바이트는 동일하지만 마지막 4 바이트는 계속 변경됩니다. 이상적으로는 처음 76 바이트에 대한 해시 다이제스트 사본을 유지하고 마지막 4 바이트 만 계속 변경하려고합니다.


  • 답변 # 1

    Go Playground에서 다음 예제를 시도 할 수 있습니다. 벤치 마크 결과가 끝났습니다.

    참고 : 아래 구현은 동시 사용에 안전하지 않습니다. 나는 의도적으로 이것을 더 간단하고빠르게만들었습니다.

    공용 API 만 사용할 때 가장 빠름 (항상 모든 입력을 해시 함) Go 해시 알고리즘의 일반적인 개념과 인터페이스는 hash.Hash 입니다.  인터페이스. 이것은 당신이 hasher의 상태를 저장하고 저장된 상태로 돌아가거나 되 감는 것을 허용하지 않습니다. 따라서 Go 표준 라이브러리의 공개 해시 API를 사용하면 항상 시작부터 해시를 계산해야합니다.

    공개 API가 제공하는 것은 Hash.Reset() 를 사용하여 이미 생성 된 hasher를 재사용하여 새 입력의 해시를 계산하는 것입니다.  방법. 여러 해시 값을 계산하는 데 (메모리) 할당이 필요하지 않기 때문에 좋습니다. 또한 Hash.Sum() 로 전달 될 수있는 선택적 슬라이스를 활용할 수 있습니다.  현재 해시를 추가하는 데 사용됩니다. 해시 결과를 받기 위해 할당이 필요하지 않도록하는 것이 좋습니다.

    다음은 이들을 활용하는 예입니다.

    type Cached1 struct {
        hasher hash.Hash
        result [sha256.Size]byte
    }
    func NewCached1() *Cached1 {
        return &Cached1{hasher: sha256.New()}
    }
    func (c *Cached1) Sum(data []byte) []byte {
        c.hasher.Reset()
        c.hasher.Write(data)
        return c.hasher.Sum(c.result[:0])
    }
    
    
    테스트 데이터

    다음 테스트 데이터를 사용합니다 :

    var fixed = bytes.Repeat([]byte{1}, 76)
    var variantA = []byte{1, 1, 1, 1}
    var variantB = []byte{2, 2, 2, 2}
    var data = append(append([]byte{}, fixed...), variantA...)
    var data2 = append(append([]byte{}, fixed...), variantB...)
    var c1 = NewCached1()
    
    

    먼저 확실한 결과를 얻자 (우리의 hasher가 제대로 작동하는지 확인하기 위해) :

    fmt.Printf("%x\n", sha256.Sum256(data))
    fmt.Printf("%x\n", sha256.Sum256(data2))
    
    

    출력 :

    fb8e69bdfa2ad15be7cc8a346b74e773d059f96cfc92da89e631895422fe966a
    10ef52823dad5d1212e8ac83b54c001bfb9a03dc0c7c3c83246fb988aa788c0c
    
    

    이제 Cached1 를 확인해 봅시다  와셔 :

    fmt.Printf("%x\n", c1.Sum(data))
    fmt.Printf("%x\n", c1.Sum(data2))
    
    

    출력은 동일합니다 :

    fb8e69bdfa2ad15be7cc8a346b74e773d059f96cfc92da89e631895422fe966a
    10ef52823dad5d1212e8ac83b54c001bfb9a03dc0c7c3c83246fb988aa788c0c
    
    
    더 빠르지 만 깨질 수 있음 (향후 Go 릴리스에서) : 마지막 4 바이트 만 해시

    이제 첫 76 개의 고정 부분의 해시를 한 번만 계산하는 덜 유연한 솔루션을 보자.

    와이즈 비즈의 허셔  패키지는 수출되지 않은 crypto/sha256 입니다  type (보다 정확하게이 형식에 대한 포인터) :

    sha256.digest
    
    

    와이즈 비즈의 가치  struct type은 기본적으로 hasher의 현재 상태를 유지합니다.

    우리가 할 수있는 일은 고정자에게 처음 76 바이트를 공급 한 다음이 구조체 값을저장하는 것입니다. 첫 76 개가 동일한 80 바이트 데이터의 해시를 계산해야 할 때이 저장된 값을 시작점으로 사용하고 마지막 4 바이트를 바꿉니다.

    이 구조체 값은 포인터와 슬라이스 및 맵과 같은 디스크립터 유형을 포함하지 않으므로 간단히 저장하면 충분합니다. 그렇지 않으면 우리는 그것들의 사본도 만들어야하지만 우리는 "행운"입니다. 따라서 향후 // digest represents the partial evaluation of a checksum. type digest struct { h [8]uint32 x [chunk]byte nx int len uint64 is224 bool // mark if this digest is SHA-224 } 를 구현할 경우이 솔루션을 조정해야합니다.  예를 들어 포인터 또는 슬라이스 필드를 추가합니다.

    와이즈 비즈 이후  내 보내지 않은 경우 반사 만 사용할 수 있습니다 ( digest  기본적으로 계산에 약간의 지연이 발생합니다.

    이 작업을 수행하는 구현 예 :

    crypto/sha256
    
    

    테스트 :

    sha256.digest
    
    

    출력은 다시 동일합니다 :

    reflect
    
    

    그래서 작동합니다.

    "궁극적 인"빠른 솔루션

    type Cached2 struct { origv reflect.Value hasherv reflect.Value hasher hash.Hash result [sha256.Size]byte } func NewCached2(fixed []byte) *Cached2 { h := sha256.New() h.Write(fixed) c := &Cached2{origv: reflect.ValueOf(h).Elem()} hasherv := reflect.New(c.origv.Type()) c.hasher = hasherv.Interface().(hash.Hash) c.hasherv = hasherv.Elem() return c } func (c *Cached2) Sum(data []byte) []byte { // Set state of the fixed hash: c.hasherv.Set(c.origv) c.hasher.Write(data) return c.hasher.Sum(c.result[:0]) }  반사가 발생하지 않으면 더 빨라질 수 있습니다. 더 빠른 솔루션을 원한다면 간단히 var c2 = NewCached2(fixed) fmt.Printf("%x\n", c2.Sum(variantA)) fmt.Printf("%x\n", c2.Sum(variantB)) 의 사본을 만들 수 있습니다.  유형과 메소드를 패키지에 포함하므로 리플렉션에 의존하지 않고 직접 사용할 수 있습니다.

    이 작업을 수행하면 fb8e69bdfa2ad15be7cc8a346b74e773d059f96cfc92da89e631895422fe966a 10ef52823dad5d1212e8ac83b54c001bfb9a03dc0c7c3c83246fb988aa788c0c 에 액세스 할 수 있습니다  struct value를 사용하면 다음과 같이 간단하게 사본을 만들 수 있습니다.

    Cached2
    
    

    복원은 다음과 같습니다 :

    sha256.digest
    
    

    난 그냥 digest 를 "복제"패키지를 내 작업 공간에 넣고 var d digest // init d saved := d 를 변경/내보냈습니다.   d = saved 로 입력  데모 목적으로 만 사용하십시오. 그런 다음이 crypto/sha256 를 사용하여  내가 digest 를 구현 한 유형  이렇게 :

    Digest
    
    

    테스트 :

    mysha256.Digest
    
    

    다시 출력은 동일합니다. 그래서 이것도 작동합니다.

    벤치 마크

    이 코드로 성능을 벤치마킹 할 수 있습니다 :

    Cached3
    
    

    벤치 마크 결과 ( type Cached3 struct { orig mysha256.Digest result [sha256.Size]byte } func NewCached3(fixed []byte) *Cached3 { var d mysha256.Digest d.Reset() d.Write(fixed) return &Cached3{orig: d} } func (c *Cached3) Sum(data []byte) []byte { // Make a copy of the fixed hash: d := c.orig d.Write(data) return d.Sum(c.result[:0]) } ) :

    var c3 = NewCached3(fixed)
    fmt.Printf("%x\n", c3.Sum(variantA))
    fmt.Printf("%x\n", c3.Sum(variantB))
    
    

    func BenchmarkCached1(b *testing.B) { for i := 0; i < b.N; i++ { c1.Sum(data) c1.Sum(data2) } } func BenchmarkCached2(b *testing.B) { for i := 0; i < b.N; i++ { c2.Sum(variantA) c2.Sum(variantB) } } func BenchmarkCached3(b *testing.B) { for i := 0; i < b.N; i++ { c3.Sum(variantA) c3.Sum(variantB) } }   go test -bench . -benchmem 보다 약41 % 빠릅니다  꽤 눈에 띄고 훌륭합니다. 와이즈 비즈   BenchmarkCached1-4 1000000 1569 ns/op 0 B/op 0 allocs/op BenchmarkCached2-4 2000000 926 ns/op 0 B/op 0 allocs/op BenchmarkCached3-4 2000000 872 ns/op 0 B/op 0 allocs/op 에 비해 "작은"성능 향상 만 제공 또 다른6 %입니다. 와이즈 비즈   Cached2 보다44 %빠릅니다. .

    또한 어떤 솔루션도 할당을 사용하지 않습니다.

    결론

    추가 40 % 또는 44 %에 대해서는 아마도 Cached1 에 가지 않을 것입니다  또는 Cached3  솔루션. 물론 실제로 성능이 얼마나 중요한지에 달려 있습니다. 그것이 중요하다면, 나는 Cached2 생각  솔루션은 최소한의 복잡성 추가와 눈에 띄는 성능 향상 사이에서 훌륭한 절충안을 제시합니다. 향후 Go 구현으로 인해 문제가 발생할 수 있으므로 위협이됩니다. 문제가 있으면 Cached3  현재 구현을 복사하여이 문제를 해결하고 성능을 약간 향상시킵니다.

    Cached1

  • 이전 python - 유닛과 피어를위한 루프에서 세분화
  • 다음 java - 컴파일 프로세스의 어휘 분석 단계에서 javadoc 주석이 제거/제거됩니까? 최종 class 파일에 포함되어 있습니까?