block.go 10 KB

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