zfast.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package matchfinder
  2. import (
  3. "encoding/binary"
  4. "math"
  5. )
  6. type tableEntry struct {
  7. val uint32
  8. offset int32
  9. }
  10. const (
  11. zfastTableBits = 15
  12. zfastTableSize = 1 << zfastTableBits
  13. zfastHashLen = 6
  14. prime6Bytes = 227718039650203
  15. )
  16. // ZFast is a MatchFinder based on the "Fastest" setting in
  17. // github.com/klauspost/compress/zstd.
  18. type ZFast struct {
  19. MaxDistance int
  20. history []byte
  21. // current is the offset at the start of history
  22. current int32
  23. table [zfastTableSize]tableEntry
  24. }
  25. func (z *ZFast) Reset() {
  26. z.current = 0
  27. z.table = [zfastTableSize]tableEntry{}
  28. z.history = z.history[:0]
  29. }
  30. func (z *ZFast) FindMatches(dst []Match, src []byte) []Match {
  31. if z.MaxDistance == 0 {
  32. z.MaxDistance = 1 << 16
  33. }
  34. // Protect against overflow of current.
  35. if int(z.current) >= int(math.MaxInt32)-2*z.MaxDistance-len(z.history) {
  36. minOffset := z.current + int32(len(z.history)) - int32(z.MaxDistance)
  37. for i := range z.table {
  38. v := z.table[i].offset
  39. if v < minOffset {
  40. v = 0
  41. } else {
  42. v = v - z.current + int32(z.MaxDistance)
  43. }
  44. z.table[i].offset = v
  45. }
  46. z.current = int32(z.MaxDistance)
  47. }
  48. if len(z.history)+len(src) > cap(z.history) {
  49. // history doesn't have enough capacity to hold the new block.
  50. if cap(z.history) == 0 {
  51. historySize := max(2*z.MaxDistance, 1<<20, len(src))
  52. z.history = make([]byte, 0, historySize)
  53. } else {
  54. // Move down
  55. offset := len(z.history) - z.MaxDistance
  56. copy(z.history[:z.MaxDistance], z.history[offset:])
  57. z.current += int32(offset)
  58. z.history = z.history[:z.MaxDistance]
  59. }
  60. }
  61. s := int32(len(z.history))
  62. z.history = append(z.history, src...)
  63. if len(src) < 10 {
  64. return append(dst, Match{
  65. Unmatched: len(src),
  66. })
  67. }
  68. src = z.history
  69. sLimit := int32(len(src)) - 8
  70. const stepSize = 2
  71. nextEmit := s
  72. cv := binary.LittleEndian.Uint64(src[s:])
  73. var offset1, offset2 int32
  74. mainLoop:
  75. for {
  76. // t will contain the match offset when we find one.
  77. // When exiting the search loop, we have already checked 4 bytes.
  78. var t int32
  79. for {
  80. nextHash := z.hash(cv)
  81. nextHash2 := z.hash(cv >> 8)
  82. candidate := z.table[nextHash]
  83. candidate2 := z.table[nextHash2]
  84. repIndex := s - offset1 + 2
  85. z.table[nextHash] = tableEntry{offset: s + z.current, val: uint32(cv)}
  86. z.table[nextHash2] = tableEntry{offset: s + z.current + 1, val: uint32(cv >> 8)}
  87. if offset1 != 0 && repIndex >= 0 && binary.LittleEndian.Uint32(src[repIndex:]) == uint32(cv>>16) {
  88. // There is a repeated match at s+2.
  89. end := extendMatch(src, int(repIndex+4), int(s+6))
  90. start := s + 2
  91. for repIndex > 0 && start > nextEmit && src[repIndex-1] == src[start-1] {
  92. repIndex--
  93. start--
  94. }
  95. dst = append(dst, Match{
  96. Unmatched: int(start - nextEmit),
  97. Length: end - int(start),
  98. Distance: int(start - repIndex),
  99. })
  100. s = int32(end)
  101. nextEmit = s
  102. if s >= sLimit {
  103. break mainLoop
  104. }
  105. cv = binary.LittleEndian.Uint64(src[s:])
  106. continue
  107. }
  108. coffset0 := s - (candidate.offset - z.current)
  109. coffset1 := s - (candidate2.offset - z.current) + 1
  110. if coffset0 < int32(z.MaxDistance) && uint32(cv) == candidate.val {
  111. t = candidate.offset - z.current
  112. if binary.LittleEndian.Uint32(src[t:]) == uint32(cv) {
  113. // found a regular match
  114. break
  115. }
  116. }
  117. if coffset1 < int32(z.MaxDistance) && uint32(cv>>8) == candidate2.val {
  118. t = candidate2.offset - z.current
  119. if binary.LittleEndian.Uint32(src[t:]) == uint32(cv>>8) {
  120. s++
  121. break
  122. }
  123. }
  124. s += stepSize + ((s - nextEmit) >> 5)
  125. if s > sLimit {
  126. break mainLoop
  127. }
  128. cv = binary.LittleEndian.Uint64(src[s:])
  129. }
  130. // A 4-byte match has been found. We'll later see if more than
  131. // 4 bytes.
  132. offset2 = offset1
  133. offset1 = s - t
  134. end := extendMatch(src, int(t+4), int(s+4))
  135. for t > 0 && s > nextEmit && src[t-1] == src[s-1] {
  136. s--
  137. t--
  138. }
  139. dst = append(dst, Match{
  140. Unmatched: int(s - nextEmit),
  141. Length: end - int(s),
  142. Distance: int(s - t),
  143. })
  144. s = int32(end)
  145. nextEmit = s
  146. if s >= sLimit {
  147. break mainLoop
  148. }
  149. cv = binary.LittleEndian.Uint64(src[s:])
  150. // Check offset 2
  151. if o2 := s - offset2; offset2 != 0 && binary.LittleEndian.Uint32(src[o2:]) == uint32(cv) {
  152. end := extendMatch(src, int(o2+4), int(s+4))
  153. // Store the hash, since we have it.
  154. nextHash := z.hash(cv)
  155. z.table[nextHash] = tableEntry{offset: s + z.current, val: uint32(cv)}
  156. dst = append(dst, Match{
  157. Length: end - int(s),
  158. Distance: int(offset2),
  159. })
  160. s = int32(end)
  161. nextEmit = s
  162. offset1, offset2 = offset2, offset1
  163. if s >= sLimit {
  164. break mainLoop
  165. }
  166. cv = binary.LittleEndian.Uint64(src[s:])
  167. }
  168. }
  169. if int(nextEmit) < len(src) {
  170. dst = append(dst, Match{
  171. Unmatched: len(src) - int(nextEmit),
  172. })
  173. }
  174. return dst
  175. }
  176. func (z *ZFast) hash(u uint64) uint32 {
  177. return uint32(((u << 16) * prime6Bytes) >> (64 - zfastTableBits))
  178. }