m0.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package matchfinder
  2. import (
  3. "encoding/binary"
  4. )
  5. // M0 is an implementation of the MatchFinder interface based
  6. // on the algorithm used by snappy, but modified to be more like the algorithm
  7. // used by compression level 0 of the brotli reference implementation.
  8. //
  9. // It has a maximum block size of 65536 bytes.
  10. type M0 struct {
  11. // Lazy turns on "lazy matching," for higher compression but less speed.
  12. Lazy bool
  13. MaxDistance int
  14. MaxLength int
  15. }
  16. func (M0) Reset() {}
  17. const (
  18. m0HashLen = 5
  19. m0TableBits = 14
  20. m0TableSize = 1 << m0TableBits
  21. m0Shift = 32 - m0TableBits
  22. // m0TableMask is redundant, but helps the compiler eliminate bounds
  23. // checks.
  24. m0TableMask = m0TableSize - 1
  25. )
  26. func (m M0) hash(data uint64) uint64 {
  27. hash := (data << (64 - 8*m0HashLen)) * hashMul64
  28. return hash >> (64 - m0TableBits)
  29. }
  30. // FindMatches looks for matches in src, appends them to dst, and returns dst.
  31. // src must not be longer than 65536 bytes.
  32. func (m M0) FindMatches(dst []Match, src []byte) []Match {
  33. const inputMargin = 16 - 1
  34. const minNonLiteralBlockSize = 1 + 1 + inputMargin
  35. if len(src) < minNonLiteralBlockSize {
  36. dst = append(dst, Match{
  37. Unmatched: len(src),
  38. })
  39. return dst
  40. }
  41. if len(src) > 65536 {
  42. panic("block too long")
  43. }
  44. var table [m0TableSize]uint16
  45. // sLimit is when to stop looking for offset/length copies. The inputMargin
  46. // lets us use a fast path for emitLiteral in the main loop, while we are
  47. // looking for copies.
  48. sLimit := len(src) - inputMargin
  49. // nextEmit is where in src the next emitLiteral should start from.
  50. nextEmit := 0
  51. // The encoded form must start with a literal, as there are no previous
  52. // bytes to copy, so we start looking for hash matches at s == 1.
  53. s := 1
  54. nextHash := m.hash(binary.LittleEndian.Uint64(src[s:]))
  55. for {
  56. // Copied from the C++ snappy implementation:
  57. //
  58. // Heuristic match skipping: If 32 bytes are scanned with no matches
  59. // found, start looking only at every other byte. If 32 more bytes are
  60. // scanned (or skipped), look at every third byte, etc.. When a match
  61. // is found, immediately go back to looking at every byte. This is a
  62. // small loss (~5% performance, ~0.1% density) for compressible data
  63. // due to more bookkeeping, but for non-compressible data (such as
  64. // JPEG) it's a huge win since the compressor quickly "realizes" the
  65. // data is incompressible and doesn't bother looking for matches
  66. // everywhere.
  67. //
  68. // The "skip" variable keeps track of how many bytes there are since
  69. // the last match; dividing it by 32 (ie. right-shifting by five) gives
  70. // the number of bytes to move ahead for each iteration.
  71. skip := 32
  72. nextS := s
  73. candidate := 0
  74. for {
  75. s = nextS
  76. bytesBetweenHashLookups := skip >> 5
  77. nextS = s + bytesBetweenHashLookups
  78. skip += bytesBetweenHashLookups
  79. if nextS > sLimit {
  80. goto emitRemainder
  81. }
  82. candidate = int(table[nextHash&m0TableMask])
  83. table[nextHash&m0TableMask] = uint16(s)
  84. nextHash = m.hash(binary.LittleEndian.Uint64(src[nextS:]))
  85. if m.MaxDistance != 0 && s-candidate > m.MaxDistance {
  86. continue
  87. }
  88. if binary.LittleEndian.Uint32(src[s:]) == binary.LittleEndian.Uint32(src[candidate:]) {
  89. break
  90. }
  91. }
  92. // Invariant: we have a 4-byte match at s.
  93. base := s
  94. s = extendMatch(src, candidate+4, s+4)
  95. origBase := base
  96. if m.Lazy && base+1 < sLimit {
  97. newBase := base + 1
  98. h := m.hash(binary.LittleEndian.Uint64(src[newBase:]))
  99. newCandidate := int(table[h&m0TableMask])
  100. table[h&m0TableMask] = uint16(newBase)
  101. okDistance := true
  102. if m.MaxDistance != 0 && newBase-newCandidate > m.MaxDistance {
  103. okDistance = false
  104. }
  105. if okDistance && binary.LittleEndian.Uint32(src[newBase:]) == binary.LittleEndian.Uint32(src[newCandidate:]) {
  106. newS := extendMatch(src, newCandidate+4, newBase+4)
  107. if newS-newBase > s-base+1 {
  108. s = newS
  109. base = newBase
  110. candidate = newCandidate
  111. }
  112. }
  113. }
  114. if m.MaxLength != 0 && s-base > m.MaxLength {
  115. s = base + m.MaxLength
  116. }
  117. dst = append(dst, Match{
  118. Unmatched: base - nextEmit,
  119. Length: s - base,
  120. Distance: base - candidate,
  121. })
  122. nextEmit = s
  123. if s >= sLimit {
  124. goto emitRemainder
  125. }
  126. if m.Lazy {
  127. // If lazy matching is enabled, we update the hash table for
  128. // every byte in the match.
  129. for i := origBase + 2; i < s-1; i++ {
  130. x := binary.LittleEndian.Uint64(src[i:])
  131. table[m.hash(x)&m0TableMask] = uint16(i)
  132. }
  133. }
  134. // We could immediately start working at s now, but to improve
  135. // compression we first update the hash table at s-1 and at s.
  136. x := binary.LittleEndian.Uint64(src[s-1:])
  137. prevHash := m.hash(x >> 0)
  138. table[prevHash&m0TableMask] = uint16(s - 1)
  139. nextHash = m.hash(x >> 8)
  140. }
  141. emitRemainder:
  142. if nextEmit < len(src) {
  143. dst = append(dst, Match{
  144. Unmatched: len(src) - nextEmit,
  145. })
  146. }
  147. return dst
  148. }