bargain3.go 7.3 KB

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