bargain2.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package matchfinder
  2. import (
  3. "encoding/binary"
  4. "math"
  5. "math/bits"
  6. "slices"
  7. )
  8. const (
  9. bargain2TableBits = 18
  10. bargain2TableSize = 1 << bargain2TableBits
  11. )
  12. // Bargain2 is a MatchFinder that attempts to find the encoding with the lowest
  13. // "bit cost", using 2 hash lengths (5 and 8).
  14. type Bargain2 struct {
  15. MaxDistance int
  16. // Skip is whether to look for matches at every other byte instead of every
  17. // byte (to increase speed but decrease compression).
  18. Skip bool
  19. history []byte
  20. table5 [bargain2TableSize]tableEntry
  21. table8 [bargain2TableSize]tableEntry
  22. // holding onto buffers to reduce allocations:
  23. arrivals []arrival
  24. matches []Match
  25. }
  26. func (z *Bargain2) Reset() {
  27. z.table5 = [bargain2TableSize]tableEntry{}
  28. z.table8 = [bargain2TableSize]tableEntry{}
  29. z.history = z.history[:0]
  30. }
  31. func (z *Bargain2) FindMatches(dst []Match, src []byte) []Match {
  32. if z.MaxDistance == 0 {
  33. z.MaxDistance = 1 << 16
  34. }
  35. var histogram [256]uint32
  36. for _, b := range src {
  37. histogram[b]++
  38. }
  39. var byteCost [256]float32
  40. for b, n := range histogram {
  41. cost := max(math.Log2(float64(len(src))/float64(n)), 1)
  42. byteCost[b] = float32(cost)
  43. }
  44. // Each element in arrivals corresponds to the position just after
  45. // the corresponding byte in src.
  46. arrivals := z.arrivals
  47. if len(arrivals) < len(src) {
  48. arrivals = make([]arrival, len(src))
  49. z.arrivals = arrivals
  50. } else {
  51. arrivals = arrivals[:len(src)]
  52. for i := range arrivals {
  53. arrivals[i] = arrival{}
  54. }
  55. }
  56. if len(z.history) > z.MaxDistance*2 {
  57. delta := len(z.history) - z.MaxDistance
  58. copy(z.history, z.history[delta:])
  59. z.history = z.history[:z.MaxDistance]
  60. for i := range z.table5 {
  61. v := z.table5[i].offset
  62. v -= int32(delta)
  63. if v < 0 {
  64. z.table5[i] = tableEntry{}
  65. } else {
  66. z.table5[i].offset = v
  67. }
  68. }
  69. for i := range z.table8 {
  70. v := z.table8[i].offset
  71. v -= int32(delta)
  72. if v < 0 {
  73. z.table8[i] = tableEntry{}
  74. } else {
  75. z.table8[i].offset = v
  76. }
  77. }
  78. }
  79. historyLen := len(z.history)
  80. z.history = append(z.history, src...)
  81. src = z.history
  82. addMatch := func(m absoluteMatch, unmatched int, repeat bool) {
  83. var startCost float32
  84. if m.Start > historyLen {
  85. startCost = arrivals[m.Start-historyLen-1].cost
  86. }
  87. insertCost := float32(bits.Len(uint(unmatched)))
  88. var distanceCost float32
  89. if !repeat {
  90. distanceCost = float32(bits.Len(uint(m.Start - m.Match)))
  91. }
  92. cost := startCost + baseMatchCost + insertCost + distanceCost
  93. for j := m.End; j >= m.Start+3; j-- {
  94. a := &arrivals[j-historyLen-1]
  95. if a.cost > 0 && a.cost <= cost {
  96. break
  97. }
  98. *a = arrival{
  99. length: uint32(j - m.Start),
  100. distance: uint32(m.Start - m.Match),
  101. cost: cost,
  102. }
  103. }
  104. }
  105. var nextOverlapSearch int
  106. for i := historyLen; i < len(src); i++ {
  107. var arrivedHere arrival
  108. if i > historyLen {
  109. arrivedHere = arrivals[i-historyLen-1]
  110. }
  111. unmatched := 0
  112. if arrivedHere.distance == 0 {
  113. unmatched = int(arrivedHere.length)
  114. }
  115. prevDistance := 0
  116. if unmatched != 0 && i-unmatched > historyLen {
  117. prevDistance = int(arrivals[i-historyLen-1-unmatched].distance)
  118. }
  119. literalCost := byteCost[src[i]]
  120. nextArrival := &arrivals[i-historyLen]
  121. if nextArrival.cost == 0 || arrivedHere.cost+literalCost < nextArrival.cost {
  122. *nextArrival = arrival{
  123. cost: arrivedHere.cost + literalCost,
  124. length: uint32(unmatched + 1),
  125. }
  126. }
  127. if i > len(src)-8 {
  128. // There's no room to check hashes.
  129. continue
  130. }
  131. cv := binary.LittleEndian.Uint64(src[i:])
  132. nextHash5 := z.hash5(cv)
  133. nextHash8 := z.hash8(cv)
  134. candidate5 := z.table5[nextHash5]
  135. candidate8 := z.table8[nextHash8]
  136. entry := tableEntry{offset: int32(i), val: uint32(cv)}
  137. z.table5[nextHash5] = entry
  138. z.table8[nextHash8] = entry
  139. // Look for a repeat match, unless there is no previous distance, or a match at
  140. // that distance has already been found.
  141. if prevDistance != 0 && prevDistance != int(arrivals[i-historyLen-1+4].distance) {
  142. repIndex := i - prevDistance
  143. if repIndex >= 0 && binary.LittleEndian.Uint32(src[repIndex:]) == uint32(cv) {
  144. // We have a repeat of the previous match distance.
  145. m := extendMatch2(src, i, repIndex, i)
  146. addMatch(m, unmatched, true)
  147. }
  148. }
  149. if z.Skip {
  150. if i%2 != 0 {
  151. continue
  152. }
  153. }
  154. nextByteIsUnmatched := arrivals[i-historyLen-1+1].distance == 0
  155. if unmatched > 0 || i >= nextOverlapSearch || nextByteIsUnmatched {
  156. if int(candidate5.offset) < i && i-int(candidate5.offset) < z.MaxDistance && uint32(cv) == candidate5.val &&
  157. binary.LittleEndian.Uint32(src[candidate5.offset:]) == uint32(cv) {
  158. m := extendMatch2(src, i, int(candidate5.offset), historyLen)
  159. delta := i - m.Start
  160. if delta == 0 {
  161. addMatch(m, unmatched, false)
  162. } else {
  163. // The match was extended backwards. Add it with and without the extra.
  164. addMatch(m, max(unmatched-delta, 0), false)
  165. m.Start += delta
  166. m.Match += delta
  167. addMatch(m, unmatched, false)
  168. }
  169. nextOverlapSearch = max(nextOverlapSearch, m.Start+1, m.End-6)
  170. }
  171. if int(candidate8.offset) < i && i-int(candidate8.offset) < z.MaxDistance && uint32(cv) == candidate8.val &&
  172. binary.LittleEndian.Uint32(src[candidate8.offset:]) == uint32(cv) {
  173. m := extendMatch2(src, i, int(candidate8.offset), historyLen)
  174. delta := i - m.Start
  175. if delta == 0 {
  176. addMatch(m, unmatched, false)
  177. } else {
  178. // The match was extended backwards. Add it with and without the extra.
  179. addMatch(m, max(unmatched-delta, 0), false)
  180. m.Start += delta
  181. m.Match += delta
  182. addMatch(m, unmatched, false)
  183. }
  184. nextOverlapSearch = max(nextOverlapSearch, m.Start+1, m.End-6)
  185. }
  186. }
  187. }
  188. // We've found the shortest path; now walk it backward and store the matches.
  189. matches := z.matches[:0]
  190. i := len(arrivals) - 1
  191. for i >= 0 {
  192. a := arrivals[i]
  193. if a.distance > 0 {
  194. matches = append(matches, Match{
  195. Length: int(a.length),
  196. Distance: int(a.distance),
  197. })
  198. i -= int(a.length)
  199. } else {
  200. if len(matches) == 0 {
  201. matches = append(matches, Match{})
  202. }
  203. matches[len(matches)-1].Unmatched = int(a.length)
  204. i -= int(a.length)
  205. }
  206. }
  207. z.matches = matches
  208. slices.Reverse(matches)
  209. return append(dst, matches...)
  210. }
  211. func (z *Bargain2) hash5(u uint64) uint32 {
  212. return uint32(((u << 24) * 889523592379) >> (64 - bargain2TableBits))
  213. }
  214. func (z *Bargain2) hash8(u uint64) uint32 {
  215. return uint32((u * 0xcf1bbcdcb7a56463) >> (64 - bargain2TableBits))
  216. }