zdfast.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package matchfinder
  2. import (
  3. "encoding/binary"
  4. "math"
  5. )
  6. const (
  7. zdfastLongTableBits = 17
  8. zdfastLongTableSize = 1 << zdfastLongTableBits
  9. )
  10. // ZDFast is a MatchFinder based on the "Default" setting in
  11. // github.com/klauspost/compress/zstd.
  12. type ZDFast struct {
  13. MaxDistance int
  14. history []byte
  15. // current is the offset at the start of history
  16. current int32
  17. table [zfastTableSize]tableEntry
  18. longTable [zdfastLongTableSize]tableEntry
  19. }
  20. func (z *ZDFast) Reset() {
  21. z.current = 0
  22. z.table = [zfastTableSize]tableEntry{}
  23. z.longTable = [zdfastLongTableSize]tableEntry{}
  24. z.history = z.history[:0]
  25. }
  26. func (z *ZDFast) FindMatches(dst []Match, src []byte) []Match {
  27. if z.MaxDistance == 0 {
  28. z.MaxDistance = 1 << 16
  29. }
  30. // Protect against overflow of current.
  31. if int(z.current) >= int(math.MaxInt32)-2*z.MaxDistance-len(z.history) {
  32. minOffset := z.current + int32(len(z.history)) - int32(z.MaxDistance)
  33. for i := range z.table {
  34. v := z.table[i].offset
  35. if v < minOffset {
  36. v = 0
  37. } else {
  38. v = v - z.current + int32(z.MaxDistance)
  39. }
  40. z.table[i].offset = v
  41. }
  42. for i := range z.longTable {
  43. v := z.longTable[i].offset
  44. if v < minOffset {
  45. v = 0
  46. } else {
  47. v = v - z.current + int32(z.MaxDistance)
  48. }
  49. z.longTable[i].offset = v
  50. }
  51. z.current = int32(z.MaxDistance)
  52. }
  53. if len(z.history)+len(src) > cap(z.history) {
  54. // history doesn't have enough capacity to hold the new block.
  55. if cap(z.history) == 0 {
  56. historySize := max(2*z.MaxDistance, 1<<20, len(src))
  57. z.history = make([]byte, 0, historySize)
  58. } else {
  59. // Move down
  60. offset := len(z.history) - z.MaxDistance
  61. copy(z.history[:z.MaxDistance], z.history[offset:])
  62. z.current += int32(offset)
  63. z.history = z.history[:z.MaxDistance]
  64. }
  65. }
  66. s := int32(len(z.history))
  67. z.history = append(z.history, src...)
  68. if len(src) < 16 {
  69. return append(dst, Match{
  70. Unmatched: len(src),
  71. })
  72. }
  73. src = z.history
  74. sLimit := int32(len(src)) - 10
  75. const stepSize = 1
  76. nextEmit := s
  77. cv := binary.LittleEndian.Uint64(src[s:])
  78. var offset1, offset2 int32
  79. mainLoop:
  80. for {
  81. // t will contain the match offset when we find one.
  82. // When exiting the search loop, we have already checked 4 bytes.
  83. var t int32
  84. for {
  85. nextHashL := z.hashLong(cv)
  86. nextHashS := z.hashShort(cv)
  87. candidateL := z.longTable[nextHashL]
  88. candidateS := z.table[nextHashS]
  89. repIndex := s - offset1 + 1
  90. entry := tableEntry{offset: s + z.current, val: uint32(cv)}
  91. z.longTable[nextHashL] = entry
  92. z.table[nextHashS] = entry
  93. if offset1 != 0 && repIndex >= 0 && binary.LittleEndian.Uint32(src[repIndex:]) == uint32(cv>>8) {
  94. // There is a repeated match at s+1.
  95. end := extendMatch(src, int(repIndex+4), int(s+5))
  96. start := s + 1
  97. for repIndex > 0 && start > nextEmit && src[repIndex-1] == src[start-1] {
  98. repIndex--
  99. start--
  100. }
  101. dst = append(dst, Match{
  102. Unmatched: int(start - nextEmit),
  103. Length: end - int(start),
  104. Distance: int(start - repIndex),
  105. })
  106. s = int32(end)
  107. nextEmit = s
  108. if s >= sLimit {
  109. break mainLoop
  110. }
  111. cv = binary.LittleEndian.Uint64(src[s:])
  112. continue
  113. }
  114. coffsetL := s - (candidateL.offset - z.current)
  115. coffsetS := s - (candidateS.offset - z.current)
  116. if coffsetL < int32(z.MaxDistance) && uint32(cv) == candidateL.val {
  117. t = candidateL.offset - z.current
  118. if binary.LittleEndian.Uint32(src[t:]) == uint32(cv) {
  119. // found a long match (likely at least 8 bytes)
  120. break
  121. }
  122. }
  123. if coffsetS < int32(z.MaxDistance) && uint32(cv) == candidateS.val {
  124. t = candidateS.offset - z.current
  125. if binary.LittleEndian.Uint32(src[t:]) != uint32(cv) {
  126. goto noMatch
  127. }
  128. // Found a regular match.
  129. // See if we can find a long match at s+1
  130. cv := binary.LittleEndian.Uint64(src[s+1:])
  131. nextHashL = z.hashLong(cv)
  132. candidateL = z.longTable[nextHashL]
  133. coffsetL = s - (candidateL.offset - z.current) + 1
  134. z.longTable[nextHashL] = tableEntry{offset: s + 1 + z.current, val: uint32(cv)}
  135. if coffsetL < int32(z.MaxDistance) && uint32(cv) == candidateL.val {
  136. t = candidateL.offset - z.current
  137. if binary.LittleEndian.Uint32(src[t:]) == uint32(cv) {
  138. // We found a long match at s+1, so we'll use that instead
  139. // of the regular match at s.
  140. s++
  141. break
  142. }
  143. }
  144. t = candidateS.offset - z.current
  145. break
  146. }
  147. noMatch:
  148. s += stepSize + ((s - nextEmit) >> 7)
  149. if s > sLimit {
  150. break mainLoop
  151. }
  152. cv = binary.LittleEndian.Uint64(src[s:])
  153. }
  154. // A 4-byte match has been found. We'll later see if more than
  155. // 4 bytes.
  156. offset2 = offset1
  157. offset1 = s - t
  158. end := extendMatch(src, int(t+4), int(s+4))
  159. for t > 0 && s > nextEmit && src[t-1] == src[s-1] {
  160. s--
  161. t--
  162. }
  163. dst = append(dst, Match{
  164. Unmatched: int(s - nextEmit),
  165. Length: end - int(s),
  166. Distance: int(s - t),
  167. })
  168. prevS := s
  169. s = int32(end)
  170. nextEmit = s
  171. if s >= sLimit {
  172. break mainLoop
  173. }
  174. // Store some table entries near the start and end of the match.
  175. index0 := prevS + 1
  176. index1 := s - 2
  177. cv0 := binary.LittleEndian.Uint64(src[index0:])
  178. cv1 := binary.LittleEndian.Uint64(src[index1:])
  179. te0 := tableEntry{offset: index0 + z.current, val: uint32(cv0)}
  180. te1 := tableEntry{offset: index1 + z.current, val: uint32(cv1)}
  181. z.longTable[z.hashLong(cv0)] = te0
  182. z.longTable[z.hashLong(cv1)] = te1
  183. cv0 >>= 8
  184. cv1 >>= 8
  185. te0.offset++
  186. te1.offset++
  187. te0.val = uint32(cv0)
  188. te1.val = uint32(cv1)
  189. z.table[z.hashShort(cv0)] = te0
  190. z.table[z.hashShort(cv1)] = te1
  191. cv = binary.LittleEndian.Uint64(src[s:])
  192. // Check offset 2
  193. if o2 := s - offset2; offset2 != 0 && binary.LittleEndian.Uint32(src[o2:]) == uint32(cv) {
  194. end := extendMatch(src, int(o2+4), int(s+4))
  195. // Store the hashes, since we have them.
  196. nextHashS := z.hashShort(cv)
  197. nextHashL := z.hashLong(cv)
  198. entry := tableEntry{offset: s + z.current, val: uint32(cv)}
  199. z.table[nextHashS] = entry
  200. z.longTable[nextHashL] = entry
  201. dst = append(dst, Match{
  202. Length: end - int(s),
  203. Distance: int(offset2),
  204. })
  205. s = int32(end)
  206. nextEmit = s
  207. offset1, offset2 = offset2, offset1
  208. if s >= sLimit {
  209. break mainLoop
  210. }
  211. cv = binary.LittleEndian.Uint64(src[s:])
  212. }
  213. }
  214. if int(nextEmit) < len(src) {
  215. dst = append(dst, Match{
  216. Unmatched: len(src) - int(nextEmit),
  217. })
  218. }
  219. return dst
  220. }
  221. func (z *ZDFast) hashShort(u uint64) uint32 {
  222. return uint32(((u << 24) * 889523592379) >> (64 - zfastTableBits))
  223. }
  224. func (z *ZDFast) hashLong(u uint64) uint32 {
  225. return uint32((u * 0xcf1bbcdcb7a56463) >> (64 - zdfastLongTableBits))
  226. }