block.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. package lz4block
  2. import (
  3. "encoding/binary"
  4. "math/bits"
  5. "sync"
  6. "github.com/pierrec/lz4/v4/internal/lz4errors"
  7. )
  8. const (
  9. // The following constants are used to setup the compression algorithm.
  10. minMatch = 4 // the minimum size of the match sequence size (4 bytes)
  11. winSizeLog = 16 // LZ4 64Kb window size limit
  12. winSize = 1 << winSizeLog
  13. winMask = winSize - 1 // 64Kb window of previous data for dependent blocks
  14. // hashLog determines the size of the hash table used to quickly find a previous match position.
  15. // Its value influences the compression speed and memory usage, the lower the faster,
  16. // but at the expense of the compression ratio.
  17. // 16 seems to be the best compromise for fast compression.
  18. hashLog = 16
  19. htSize = 1 << hashLog
  20. mfLimit = 10 + minMatch // The last match cannot start within the last 14 bytes.
  21. )
  22. // Pool of hash tables for CompressBlock.
  23. var HashTablePool = hashTablePool{sync.Pool{New: func() interface{} { return new([htSize]int) }}}
  24. type hashTablePool struct {
  25. sync.Pool
  26. }
  27. func (p *hashTablePool) Get() *[htSize]int {
  28. return p.Pool.Get().(*[htSize]int)
  29. }
  30. // Zero out the table to avoid non-deterministic outputs (see issue#65).
  31. func (p *hashTablePool) Put(t *[htSize]int) {
  32. *t = [htSize]int{}
  33. p.Pool.Put(t)
  34. }
  35. func recoverBlock(e *error) {
  36. if r := recover(); r != nil && *e == nil {
  37. *e = lz4errors.ErrInvalidSourceShortBuffer
  38. }
  39. }
  40. // blockHash hashes the lower 6 bytes into a value < htSize.
  41. func blockHash(x uint64) uint32 {
  42. const prime6bytes = 227718039650203
  43. return uint32(((x << (64 - 48)) * prime6bytes) >> (64 - hashLog))
  44. }
  45. func CompressBlockBound(n int) int {
  46. return n + n/255 + 16
  47. }
  48. func UncompressBlock(src, dst []byte) (int, error) {
  49. if len(src) == 0 {
  50. return 0, nil
  51. }
  52. if di := decodeBlock(dst, src); di >= 0 {
  53. return di, nil
  54. }
  55. return 0, lz4errors.ErrInvalidSourceShortBuffer
  56. }
  57. func CompressBlock(src, dst []byte, hashTable []int) (_ int, err error) {
  58. defer recoverBlock(&err)
  59. // Return 0, nil only if the destination buffer size is < CompressBlockBound.
  60. isNotCompressible := len(dst) < CompressBlockBound(len(src))
  61. // adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
  62. // This significantly speeds up incompressible data and usually has very small impact on compression.
  63. // bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
  64. const adaptSkipLog = 7
  65. // si: Current position of the search.
  66. // anchor: Position of the current literals.
  67. var si, di, anchor int
  68. sn := len(src) - mfLimit
  69. if sn <= 0 {
  70. goto lastLiterals
  71. }
  72. if cap(hashTable) < htSize {
  73. poolTable := HashTablePool.Get()
  74. defer HashTablePool.Put(poolTable)
  75. hashTable = poolTable[:]
  76. } else {
  77. hashTable = hashTable[:htSize]
  78. }
  79. _ = hashTable[htSize-1]
  80. // Fast scan strategy: the hash table only stores the last 4 bytes sequences.
  81. for si < sn {
  82. // Hash the next 6 bytes (sequence)...
  83. match := binary.LittleEndian.Uint64(src[si:])
  84. h := blockHash(match)
  85. h2 := blockHash(match >> 8)
  86. // We check a match at s, s+1 and s+2 and pick the first one we get.
  87. // Checking 3 only requires us to load the source one.
  88. ref := hashTable[h]
  89. ref2 := hashTable[h2]
  90. hashTable[h] = si
  91. hashTable[h2] = si + 1
  92. offset := si - ref
  93. // If offset <= 0 we got an old entry in the hash table.
  94. if offset <= 0 || offset >= winSize || // Out of window.
  95. uint32(match) != binary.LittleEndian.Uint32(src[ref:]) { // Hash collision on different matches.
  96. // No match. Start calculating another hash.
  97. // The processor can usually do this out-of-order.
  98. h = blockHash(match >> 16)
  99. ref = hashTable[h]
  100. // Check the second match at si+1
  101. si += 1
  102. offset = si - ref2
  103. if offset <= 0 || offset >= winSize ||
  104. uint32(match>>8) != binary.LittleEndian.Uint32(src[ref2:]) {
  105. // No match. Check the third match at si+2
  106. si += 1
  107. offset = si - ref
  108. hashTable[h] = si
  109. if offset <= 0 || offset >= winSize ||
  110. uint32(match>>16) != binary.LittleEndian.Uint32(src[ref:]) {
  111. // Skip one extra byte (at si+3) before we check 3 matches again.
  112. si += 2 + (si-anchor)>>adaptSkipLog
  113. continue
  114. }
  115. }
  116. }
  117. // Match found.
  118. lLen := si - anchor // Literal length.
  119. // We already matched 4 bytes.
  120. mLen := 4
  121. // Extend backwards if we can, reducing literals.
  122. tOff := si - offset - 1
  123. for lLen > 0 && tOff >= 0 && src[si-1] == src[tOff] {
  124. si--
  125. tOff--
  126. lLen--
  127. mLen++
  128. }
  129. // Add the match length, so we continue search at the end.
  130. // Use mLen to store the offset base.
  131. si, mLen = si+mLen, si+minMatch
  132. // Find the longest match by looking by batches of 8 bytes.
  133. for si+8 < sn {
  134. x := binary.LittleEndian.Uint64(src[si:]) ^ binary.LittleEndian.Uint64(src[si-offset:])
  135. if x == 0 {
  136. si += 8
  137. } else {
  138. // Stop is first non-zero byte.
  139. si += bits.TrailingZeros64(x) >> 3
  140. break
  141. }
  142. }
  143. mLen = si - mLen
  144. if mLen < 0xF {
  145. dst[di] = byte(mLen)
  146. } else {
  147. dst[di] = 0xF
  148. }
  149. // Encode literals length.
  150. if lLen < 0xF {
  151. dst[di] |= byte(lLen << 4)
  152. } else {
  153. dst[di] |= 0xF0
  154. di++
  155. l := lLen - 0xF
  156. for ; l >= 0xFF; l -= 0xFF {
  157. dst[di] = 0xFF
  158. di++
  159. }
  160. dst[di] = byte(l)
  161. }
  162. di++
  163. // Literals.
  164. copy(dst[di:di+lLen], src[anchor:anchor+lLen])
  165. di += lLen + 2
  166. anchor = si
  167. // Encode offset.
  168. _ = dst[di] // Bound check elimination.
  169. dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
  170. // Encode match length part 2.
  171. if mLen >= 0xF {
  172. for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
  173. dst[di] = 0xFF
  174. di++
  175. }
  176. dst[di] = byte(mLen)
  177. di++
  178. }
  179. // Check if we can load next values.
  180. if si >= sn {
  181. break
  182. }
  183. // Hash match end-2
  184. h = blockHash(binary.LittleEndian.Uint64(src[si-2:]))
  185. hashTable[h] = si - 2
  186. }
  187. lastLiterals:
  188. if isNotCompressible && anchor == 0 {
  189. // Incompressible.
  190. return 0, nil
  191. }
  192. // Last literals.
  193. lLen := len(src) - anchor
  194. if lLen < 0xF {
  195. dst[di] = byte(lLen << 4)
  196. } else {
  197. dst[di] = 0xF0
  198. di++
  199. for lLen -= 0xF; lLen >= 0xFF; lLen -= 0xFF {
  200. dst[di] = 0xFF
  201. di++
  202. }
  203. dst[di] = byte(lLen)
  204. }
  205. di++
  206. // Write the last literals.
  207. if isNotCompressible && di >= anchor {
  208. // Incompressible.
  209. return 0, nil
  210. }
  211. di += copy(dst[di:di+len(src)-anchor], src[anchor:])
  212. return di, nil
  213. }
  214. // blockHash hashes 4 bytes into a value < winSize.
  215. func blockHashHC(x uint32) uint32 {
  216. const hasher uint32 = 2654435761 // Knuth multiplicative hash.
  217. return x * hasher >> (32 - winSizeLog)
  218. }
  219. func CompressBlockHC(src, dst []byte, depth CompressionLevel, hashTable, chainTable []int) (_ int, err error) {
  220. defer recoverBlock(&err)
  221. // Return 0, nil only if the destination buffer size is < CompressBlockBound.
  222. isNotCompressible := len(dst) < CompressBlockBound(len(src))
  223. // adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
  224. // This significantly speeds up incompressible data and usually has very small impact on compression.
  225. // bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
  226. const adaptSkipLog = 7
  227. var si, di, anchor int
  228. sn := len(src) - mfLimit
  229. if sn <= 0 {
  230. goto lastLiterals
  231. }
  232. // hashTable: stores the last position found for a given hash
  233. // chainTable: stores previous positions for a given hash
  234. if cap(hashTable) < htSize {
  235. poolTable := HashTablePool.Get()
  236. defer HashTablePool.Put(poolTable)
  237. hashTable = poolTable[:]
  238. } else {
  239. hashTable = hashTable[:htSize]
  240. }
  241. _ = hashTable[htSize-1]
  242. if cap(chainTable) < htSize {
  243. poolTable := HashTablePool.Get()
  244. defer HashTablePool.Put(poolTable)
  245. chainTable = poolTable[:]
  246. } else {
  247. chainTable = chainTable[:htSize]
  248. }
  249. _ = chainTable[htSize-1]
  250. if depth == 0 {
  251. depth = winSize
  252. }
  253. for si < sn {
  254. // Hash the next 4 bytes (sequence).
  255. match := binary.LittleEndian.Uint32(src[si:])
  256. h := blockHashHC(match)
  257. // Follow the chain until out of window and give the longest match.
  258. mLen := 0
  259. offset := 0
  260. for next, try := hashTable[h], depth; try > 0 && next > 0 && si-next < winSize; next, try = chainTable[next&winMask], try-1 {
  261. // The first (mLen==0) or next byte (mLen>=minMatch) at current match length
  262. // must match to improve on the match length.
  263. if src[next+mLen] != src[si+mLen] {
  264. continue
  265. }
  266. ml := 0
  267. // Compare the current position with a previous with the same hash.
  268. for ml < sn-si {
  269. x := binary.LittleEndian.Uint64(src[next+ml:]) ^ binary.LittleEndian.Uint64(src[si+ml:])
  270. if x == 0 {
  271. ml += 8
  272. } else {
  273. // Stop is first non-zero byte.
  274. ml += bits.TrailingZeros64(x) >> 3
  275. break
  276. }
  277. }
  278. if ml < minMatch || ml <= mLen {
  279. // Match too small (<minMath) or smaller than the current match.
  280. continue
  281. }
  282. // Found a longer match, keep its position and length.
  283. mLen = ml
  284. offset = si - next
  285. // Try another previous position with the same hash.
  286. }
  287. chainTable[si&winMask] = hashTable[h]
  288. hashTable[h] = si
  289. // No match found.
  290. if mLen == 0 {
  291. si += 1 + (si-anchor)>>adaptSkipLog
  292. continue
  293. }
  294. // Match found.
  295. // Update hash/chain tables with overlapping bytes:
  296. // si already hashed, add everything from si+1 up to the match length.
  297. winStart := si + 1
  298. if ws := si + mLen - winSize; ws > winStart {
  299. winStart = ws
  300. }
  301. for si, ml := winStart, si+mLen; si < ml; {
  302. match >>= 8
  303. match |= uint32(src[si+3]) << 24
  304. h := blockHashHC(match)
  305. chainTable[si&winMask] = hashTable[h]
  306. hashTable[h] = si
  307. si++
  308. }
  309. lLen := si - anchor
  310. si += mLen
  311. mLen -= minMatch // Match length does not include minMatch.
  312. if mLen < 0xF {
  313. dst[di] = byte(mLen)
  314. } else {
  315. dst[di] = 0xF
  316. }
  317. // Encode literals length.
  318. if lLen < 0xF {
  319. dst[di] |= byte(lLen << 4)
  320. } else {
  321. dst[di] |= 0xF0
  322. di++
  323. l := lLen - 0xF
  324. for ; l >= 0xFF; l -= 0xFF {
  325. dst[di] = 0xFF
  326. di++
  327. }
  328. dst[di] = byte(l)
  329. }
  330. di++
  331. // Literals.
  332. copy(dst[di:di+lLen], src[anchor:anchor+lLen])
  333. di += lLen
  334. anchor = si
  335. // Encode offset.
  336. di += 2
  337. dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
  338. // Encode match length part 2.
  339. if mLen >= 0xF {
  340. for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
  341. dst[di] = 0xFF
  342. di++
  343. }
  344. dst[di] = byte(mLen)
  345. di++
  346. }
  347. }
  348. if isNotCompressible && anchor == 0 {
  349. // Incompressible.
  350. return 0, nil
  351. }
  352. // Last literals.
  353. lastLiterals:
  354. lLen := len(src) - anchor
  355. if lLen < 0xF {
  356. dst[di] = byte(lLen << 4)
  357. } else {
  358. dst[di] = 0xF0
  359. di++
  360. lLen -= 0xF
  361. for ; lLen >= 0xFF; lLen -= 0xFF {
  362. dst[di] = 0xFF
  363. di++
  364. }
  365. dst[di] = byte(lLen)
  366. }
  367. di++
  368. // Write the last literals.
  369. if isNotCompressible && di >= anchor {
  370. // Incompressible.
  371. return 0, nil
  372. }
  373. di += copy(dst[di:di+len(src)-anchor], src[anchor:])
  374. return di, nil
  375. }