/src/libjxl/lib/jxl/enc_lz77.cc
Line | Count | Source |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/jxl/enc_lz77.h" |
7 | | |
8 | | #include <algorithm> |
9 | | #include <cmath> |
10 | | #include <cstddef> |
11 | | #include <cstdint> |
12 | | #include <limits> |
13 | | #include <unordered_map> |
14 | | #include <vector> |
15 | | |
16 | | #include "lib/jxl/ans_params.h" |
17 | | #include "lib/jxl/base/bits.h" |
18 | | #include "lib/jxl/base/fast_math-inl.h" |
19 | | #include "lib/jxl/base/status.h" |
20 | | #include "lib/jxl/dec_ans.h" |
21 | | #include "lib/jxl/enc_ans.h" |
22 | | #include "lib/jxl/enc_ans_params.h" |
23 | | |
24 | | namespace jxl { |
25 | | |
26 | | namespace { |
27 | | |
28 | | class SymbolCostEstimator { |
29 | | public: |
30 | | SymbolCostEstimator(size_t num_contexts, bool force_huffman, |
31 | | const std::vector<std::vector<Token>>& tokens, |
32 | 29.8k | const LZ77Params& lz77) { |
33 | 29.8k | std::vector<Histogram> builder(num_contexts); |
34 | | // Build histograms for estimating lz77 savings. |
35 | 29.8k | HybridUintConfig uint_config; |
36 | 46.5k | for (const auto& stream : tokens) { |
37 | 36.3M | for (const auto& token : stream) { |
38 | 36.3M | uint32_t tok, nbits, bits; |
39 | 36.3M | (token.is_lz77_length ? lz77.length_uint_config : uint_config) |
40 | 36.3M | .Encode(token.value, &tok, &nbits, &bits); |
41 | 36.3M | tok += token.is_lz77_length ? lz77.min_symbol : 0; |
42 | 36.3M | JXL_DASSERT(token.context < num_contexts); |
43 | 36.3M | builder[token.context].Add(tok); |
44 | 36.3M | } |
45 | 46.5k | } |
46 | 29.8k | max_alphabet_size_ = 0; |
47 | 149k | for (size_t i = 0; i < num_contexts; i++) { |
48 | 119k | max_alphabet_size_ = |
49 | 119k | std::max(max_alphabet_size_, builder[i].counts.size()); |
50 | 119k | } |
51 | 29.8k | bits_.resize(num_contexts * max_alphabet_size_); |
52 | | // TODO(veluca): SIMD? |
53 | 29.8k | add_symbol_cost_.resize(num_contexts); |
54 | 149k | for (size_t i = 0; i < num_contexts; i++) { |
55 | 119k | float inv_total = 1.0f / (builder[i].total_count + 1e-8f); |
56 | 119k | float total_cost = 0; |
57 | 3.71M | for (size_t j = 0; j < builder[i].counts.size(); j++) { |
58 | 3.59M | size_t cnt = builder[i].counts[j]; |
59 | 3.59M | float cost = 0; |
60 | 3.59M | if (cnt != 0 && cnt != builder[i].total_count) { |
61 | 1.15M | cost = -FastLog2f(cnt * inv_total); |
62 | 1.15M | if (force_huffman) cost = std::ceil(cost); |
63 | 2.44M | } else if (cnt == 0) { |
64 | 2.43M | cost = ANS_LOG_TAB_SIZE; // Highest possible cost. |
65 | 2.43M | } |
66 | 3.59M | bits_[i * max_alphabet_size_ + j] = cost; |
67 | 3.59M | total_cost += cost * builder[i].counts[j]; |
68 | 3.59M | } |
69 | | // Penalty for adding a lz77 symbol to this contest (only used for static |
70 | | // cost model). Higher penalty for contexts that have a very low |
71 | | // per-symbol entropy. |
72 | 119k | add_symbol_cost_[i] = std::max(0.0f, 6.0f - total_cost * inv_total); |
73 | 119k | } |
74 | 29.8k | } |
75 | 38.0M | float Bits(size_t ctx, size_t sym) const { |
76 | 38.0M | return bits_[ctx * max_alphabet_size_ + sym]; |
77 | 38.0M | } |
78 | 848k | float LenCost(size_t ctx, size_t len, const LZ77Params& lz77) const { |
79 | 848k | uint32_t nbits, bits, tok; |
80 | 848k | lz77.length_uint_config.Encode(len, &tok, &nbits, &bits); |
81 | 848k | tok += lz77.min_symbol; |
82 | 848k | return nbits + Bits(ctx, tok); |
83 | 848k | } |
84 | 848k | float DistCost(size_t len, const LZ77Params& lz77) const { |
85 | 848k | uint32_t nbits, bits, tok; |
86 | 848k | HybridUintConfig().Encode(len, &tok, &nbits, &bits); |
87 | 848k | return nbits + Bits(lz77.nonserialized_distance_context, tok); |
88 | 848k | } |
89 | 12.3k | float AddSymbolCost(size_t idx) const { return add_symbol_cost_[idx]; } |
90 | | |
91 | | private: |
92 | | size_t max_alphabet_size_; |
93 | | std::vector<float> bits_; |
94 | | std::vector<float> add_symbol_cost_; |
95 | | }; |
96 | | |
97 | | std::vector<std::vector<Token>> ApplyLZ77_RLE( |
98 | | const HistogramParams& params, size_t num_contexts, |
99 | 29.3k | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) { |
100 | 29.3k | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); |
101 | | // TODO(veluca): tune heuristics here. |
102 | 29.3k | SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77); |
103 | 29.3k | float bit_decrease = 0; |
104 | 29.3k | size_t total_symbols = 0; |
105 | 29.3k | std::vector<float> sym_cost; |
106 | 29.3k | HybridUintConfig uint_config; |
107 | 75.3k | for (size_t stream = 0; stream < tokens.size(); stream++) { |
108 | 45.9k | size_t distance_multiplier = |
109 | 45.9k | params.image_widths.size() > stream ? params.image_widths[stream] : 0; |
110 | 45.9k | const auto& in = tokens[stream]; |
111 | 45.9k | auto& out = tokens_lz77[stream]; |
112 | 45.9k | total_symbols += in.size(); |
113 | | // Cumulative sum of bit costs. |
114 | 45.9k | sym_cost.resize(in.size() + 1); |
115 | 36.1M | for (size_t i = 0; i < in.size(); i++) { |
116 | 36.1M | uint32_t tok, nbits, unused_bits; |
117 | 36.1M | uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits); |
118 | 36.1M | sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i]; |
119 | 36.1M | } |
120 | 45.9k | out.reserve(in.size()); |
121 | 18.7M | for (size_t i = 0; i < in.size(); i++) { |
122 | 18.6M | size_t num_to_copy = 0; |
123 | 18.6M | size_t distance_symbol = 0; // 1 for RLE. |
124 | 18.6M | if (distance_multiplier != 0) { |
125 | 7.75M | distance_symbol = 1; // Special distance 1 if enabled. |
126 | 7.75M | JXL_DASSERT(kSpecialDistances[1][0] == 1); |
127 | 7.75M | JXL_DASSERT(kSpecialDistances[1][1] == 0); |
128 | 7.75M | } |
129 | 18.6M | if (i > 0) { |
130 | 38.3M | for (; i + num_to_copy < in.size(); num_to_copy++) { |
131 | 38.3M | if (in[i + num_to_copy].value != in[i - 1].value) { |
132 | 18.6M | break; |
133 | 18.6M | } |
134 | 38.3M | } |
135 | 18.6M | } |
136 | 18.6M | if (num_to_copy == 0) { |
137 | 16.3M | out.push_back(in[i]); |
138 | 16.3M | continue; |
139 | 16.3M | } |
140 | 2.29M | float cost = sym_cost[i + num_to_copy] - sym_cost[i]; |
141 | | // This subtraction might overflow, but that's OK. |
142 | 2.29M | size_t lz77_len = num_to_copy - lz77.min_length; |
143 | 2.29M | float lz77_cost = num_to_copy >= lz77.min_length |
144 | 2.29M | ? CeilLog2Nonzero(lz77_len + 1) + 1 |
145 | 2.29M | : 0; |
146 | 2.29M | if (num_to_copy < lz77.min_length || cost <= lz77_cost) { |
147 | 11.6M | for (size_t j = 0; j < num_to_copy; j++) { |
148 | 9.66M | out.push_back(in[i + j]); |
149 | 9.66M | } |
150 | 2.02M | i += num_to_copy - 1; |
151 | 2.02M | continue; |
152 | 2.02M | } |
153 | | // Output the LZ77 length |
154 | 270k | out.emplace_back(in[i].context, static_cast<uint32_t>(lz77_len)); |
155 | 270k | out.back().is_lz77_length = true; |
156 | 270k | i += num_to_copy - 1; |
157 | 270k | bit_decrease += cost - lz77_cost; |
158 | | // Output the LZ77 copy distance. |
159 | 270k | out.emplace_back( |
160 | 270k | static_cast<uint32_t>(lz77.nonserialized_distance_context), |
161 | 270k | static_cast<uint32_t>(distance_symbol)); |
162 | 270k | } |
163 | 45.9k | } |
164 | | |
165 | 29.3k | if (bit_decrease > total_symbols * 0.2 + 16) { |
166 | 2.88k | return tokens_lz77; |
167 | 2.88k | } |
168 | 26.4k | return {}; |
169 | 29.3k | } |
170 | | |
171 | | |
172 | | // Computes a Murmur-style mix hash over HashSize elements. |
173 | | template <int kHashSize=3> |
174 | 337k | uint32_t GetHash(size_t pos, const std::vector<uint32_t>& data_) { |
175 | 337k | uint32_t h = 0; |
176 | 337k | if (pos + kHashSize <= data_.size()) { |
177 | 1.34M | for (size_t i = 0; i < kHashSize; i++) { |
178 | 1.00M | h ^= data_[pos+i] + 0x9e3779b9 + (h << 6) + (h >> 2); |
179 | 1.00M | } |
180 | 336k | h ^= h >> 16; |
181 | 336k | h *= 0x85ebca6bu; |
182 | 336k | h ^= h >> 13; |
183 | 336k | h *= 0xc2b2ae35u; |
184 | 336k | h ^= h >> 16; |
185 | | |
186 | 336k | } else { |
187 | 1.15k | return 0; |
188 | 1.15k | } |
189 | 336k | return h; |
190 | 337k | } |
191 | | |
192 | | // Hash chain for LZ77 matching |
193 | | template <uint32_t kMaxChainLength> |
194 | | struct HashChain { |
195 | | size_t size_; |
196 | | std::vector<uint32_t> data_; |
197 | | |
198 | | unsigned hash_num_values_ = 32768; |
199 | | unsigned hash_mask_ = hash_num_values_ - 1; |
200 | | unsigned hash_shift_ = 5; |
201 | | |
202 | | std::vector<int> head; |
203 | | std::vector<uint32_t> chain; |
204 | | std::vector<int> val; |
205 | | |
206 | | // Speed up repetitions of zero |
207 | | std::vector<int> headz; |
208 | | std::vector<uint32_t> chainz; |
209 | | std::vector<uint32_t> zeros; |
210 | | uint32_t numzeros = 0; |
211 | | |
212 | | size_t window_size_; |
213 | | size_t window_mask_; |
214 | | size_t min_length_; |
215 | | size_t max_length_; |
216 | | |
217 | | // Map of special distance codes. |
218 | | std::unordered_map<int, int> special_dist_table_; |
219 | | size_t num_special_distances_ = 0; |
220 | | |
221 | | uint32_t maxchainlength = kMaxChainLength; // window_size_ to allow all |
222 | | |
223 | | HashChain(const Token* data, size_t size, size_t window_size, |
224 | | size_t min_length, size_t max_length, size_t distance_multiplier) |
225 | 181 | : size_(size), |
226 | 181 | window_size_(window_size), |
227 | 181 | window_mask_(window_size - 1), |
228 | 181 | min_length_(min_length), |
229 | 181 | max_length_(max_length) { |
230 | 181 | data_.resize(size); |
231 | 92.7k | for (size_t i = 0; i < size; i++) { |
232 | 92.5k | data_[i] = data[i].value; |
233 | 92.5k | } |
234 | | |
235 | 181 | head.resize(hash_num_values_, -1); |
236 | 181 | val.resize(window_size_, -1); |
237 | 181 | chain.resize(window_size_); |
238 | 149k | for (uint32_t i = 0; i < window_size_; ++i) { |
239 | 149k | chain[i] = i; // same value as index indicates uninitialized |
240 | 149k | } |
241 | | |
242 | 181 | zeros.resize(window_size_); |
243 | 181 | headz.resize(window_size_ + 1, -1); |
244 | 181 | chainz.resize(window_size_); |
245 | 149k | for (uint32_t i = 0; i < window_size_; ++i) { |
246 | 149k | chainz[i] = i; |
247 | 149k | } |
248 | | // Translate distance to special distance code. |
249 | 181 | if (distance_multiplier) { |
250 | | // Count down, so if due to small distance multiplier multiple distances |
251 | | // map to the same code, the smallest code will be used in the end. |
252 | 0 | for (int i = kNumSpecialDistances - 1; i >= 0; --i) { |
253 | 0 | special_dist_table_[SpecialDistance(i, distance_multiplier)] = i; |
254 | 0 | } |
255 | 0 | num_special_distances_ = kNumSpecialDistances; |
256 | 0 | } |
257 | 181 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<1u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<3u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<8u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::HashChain(jxl::Token const*, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) Line | Count | Source | 225 | 181 | : size_(size), | 226 | 181 | window_size_(window_size), | 227 | 181 | window_mask_(window_size - 1), | 228 | 181 | min_length_(min_length), | 229 | 181 | max_length_(max_length) { | 230 | 181 | data_.resize(size); | 231 | 92.7k | for (size_t i = 0; i < size; i++) { | 232 | 92.5k | data_[i] = data[i].value; | 233 | 92.5k | } | 234 | | | 235 | 181 | head.resize(hash_num_values_, -1); | 236 | 181 | val.resize(window_size_, -1); | 237 | 181 | chain.resize(window_size_); | 238 | 149k | for (uint32_t i = 0; i < window_size_; ++i) { | 239 | 149k | chain[i] = i; // same value as index indicates uninitialized | 240 | 149k | } | 241 | | | 242 | 181 | zeros.resize(window_size_); | 243 | 181 | headz.resize(window_size_ + 1, -1); | 244 | 181 | chainz.resize(window_size_); | 245 | 149k | for (uint32_t i = 0; i < window_size_; ++i) { | 246 | 149k | chainz[i] = i; | 247 | 149k | } | 248 | | // Translate distance to special distance code. | 249 | 181 | if (distance_multiplier) { | 250 | | // Count down, so if due to small distance multiplier multiple distances | 251 | | // map to the same code, the smallest code will be used in the end. | 252 | 0 | for (int i = kNumSpecialDistances - 1; i >= 0; --i) { | 253 | 0 | special_dist_table_[SpecialDistance(i, distance_multiplier)] = i; | 254 | 0 | } | 255 | 0 | num_special_distances_ = kNumSpecialDistances; | 256 | 0 | } | 257 | 181 | } |
|
258 | | |
259 | | |
260 | 92.5k | uint32_t CountZeros(size_t pos, uint32_t prevzeros) const { |
261 | 92.5k | size_t end = pos + window_size_; |
262 | 92.5k | if (end > size_) end = size_; |
263 | 92.5k | if (prevzeros > 0) { |
264 | 35.6k | if (prevzeros >= window_mask_ && data_[end - 1] == 0 && |
265 | 0 | end == pos + window_size_) { |
266 | 0 | return prevzeros; |
267 | 35.6k | } else { |
268 | 35.6k | return prevzeros - 1; |
269 | 35.6k | } |
270 | 35.6k | } |
271 | 56.8k | uint32_t num = 0; |
272 | 103k | while (pos + num < end && data_[pos + num] == 0) num++; |
273 | 56.8k | return num; |
274 | 92.5k | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<1u>::CountZeros(unsigned long, unsigned int) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<3u>::CountZeros(unsigned long, unsigned int) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<8u>::CountZeros(unsigned long, unsigned int) const enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::CountZeros(unsigned long, unsigned int) const Line | Count | Source | 260 | 92.5k | uint32_t CountZeros(size_t pos, uint32_t prevzeros) const { | 261 | 92.5k | size_t end = pos + window_size_; | 262 | 92.5k | if (end > size_) end = size_; | 263 | 92.5k | if (prevzeros > 0) { | 264 | 35.6k | if (prevzeros >= window_mask_ && data_[end - 1] == 0 && | 265 | 0 | end == pos + window_size_) { | 266 | 0 | return prevzeros; | 267 | 35.6k | } else { | 268 | 35.6k | return prevzeros - 1; | 269 | 35.6k | } | 270 | 35.6k | } | 271 | 56.8k | uint32_t num = 0; | 272 | 103k | while (pos + num < end && data_[pos + num] == 0) num++; | 273 | 56.8k | return num; | 274 | 92.5k | } |
|
275 | | |
276 | 92.5k | void Update(size_t pos) { |
277 | 92.5k | uint32_t hashval = GetHash(pos, data_) & hash_mask_; |
278 | 92.5k | uint32_t wpos = pos & window_mask_; |
279 | | |
280 | 92.5k | val[wpos] = static_cast<int>(hashval); |
281 | 92.5k | if (head[hashval] != -1) chain[wpos] = head[hashval]; |
282 | 92.5k | head[hashval] = wpos; |
283 | | |
284 | 92.5k | if (pos > 0 && data_[pos] != data_[pos - 1]) numzeros = 0; |
285 | 92.5k | numzeros = CountZeros(pos, numzeros); |
286 | | |
287 | 92.5k | zeros[wpos] = numzeros; |
288 | 92.5k | if (headz[numzeros] != -1) chainz[wpos] = headz[numzeros]; |
289 | 92.5k | headz[numzeros] = wpos; |
290 | 92.5k | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<1u>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<3u>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::HashChain<8u>::Update(unsigned long) enc_lz77.cc:jxl::(anonymous namespace)::HashChain<256u>::Update(unsigned long) Line | Count | Source | 276 | 92.5k | void Update(size_t pos) { | 277 | 92.5k | uint32_t hashval = GetHash(pos, data_) & hash_mask_; | 278 | 92.5k | uint32_t wpos = pos & window_mask_; | 279 | | | 280 | 92.5k | val[wpos] = static_cast<int>(hashval); | 281 | 92.5k | if (head[hashval] != -1) chain[wpos] = head[hashval]; | 282 | 92.5k | head[hashval] = wpos; | 283 | | | 284 | 92.5k | if (pos > 0 && data_[pos] != data_[pos - 1]) numzeros = 0; | 285 | 92.5k | numzeros = CountZeros(pos, numzeros); | 286 | | | 287 | 92.5k | zeros[wpos] = numzeros; | 288 | 92.5k | if (headz[numzeros] != -1) chainz[wpos] = headz[numzeros]; | 289 | 92.5k | headz[numzeros] = wpos; | 290 | 92.5k | } |
|
291 | | |
292 | | void Update(size_t pos, size_t len) { |
293 | | for (size_t i = 0; i < len; i++) { |
294 | | Update(pos + i); |
295 | | } |
296 | | } |
297 | | |
298 | | template <typename CB> |
299 | 78.7k | void FindMatches(size_t pos, int max_dist, const CB& found_match) const { |
300 | 78.7k | uint32_t wpos = pos & window_mask_; |
301 | 78.7k | uint32_t hashval = GetHash(pos, data_) & hash_mask_; |
302 | 78.7k | uint32_t hashpos = chain[wpos]; |
303 | | |
304 | 78.7k | int prev_dist = 0; |
305 | 78.7k | int end = std::min<int>(pos + max_length_, size_); |
306 | 78.7k | uint32_t chainlength = 0; |
307 | 78.7k | uint32_t best_len = 0; |
308 | 2.09M | for (;;) { |
309 | 2.09M | int dist = (hashpos <= wpos) ? (wpos - hashpos) |
310 | 2.09M | : (wpos - hashpos + window_mask_ + 1); |
311 | 2.09M | if (dist < prev_dist) break; |
312 | 2.09M | prev_dist = dist; |
313 | 2.09M | uint32_t len = 0; |
314 | 2.09M | if (dist > 0) { |
315 | 2.06M | int i = pos; |
316 | 2.06M | int j = pos - dist; |
317 | 2.06M | if (numzeros > 3) { |
318 | 1.36M | int r = std::min<int>(numzeros - 1, zeros[hashpos]); |
319 | 1.36M | if (i + r >= end) r = end - i - 1; |
320 | 1.36M | i += r; |
321 | 1.36M | j += r; |
322 | 1.36M | } |
323 | 8.54M | while (i < end && data_[i] == data_[j]) { |
324 | 6.47M | i++; |
325 | 6.47M | j++; |
326 | 6.47M | } |
327 | 2.06M | len = i - pos; |
328 | | // This can trigger even if the new length is slightly smaller than the |
329 | | // best length, because it is possible for a slightly cheaper distance |
330 | | // symbol to occur. |
331 | 2.06M | if (len >= min_length_ && len + 2 >= best_len) { |
332 | 1.21M | auto it = special_dist_table_.find(dist); |
333 | 1.21M | int dist_symbol = (it == special_dist_table_.end()) |
334 | 1.21M | ? (num_special_distances_ + dist - 1) |
335 | 1.21M | : it->second; |
336 | 1.21M | found_match(len, dist_symbol); |
337 | 1.21M | if (len > best_len) best_len = len; |
338 | 1.21M | } |
339 | 2.06M | } |
340 | | |
341 | 2.09M | chainlength++; |
342 | 2.09M | if (chainlength >= maxchainlength) break; |
343 | | |
344 | 2.09M | if (numzeros >= 3 && len > numzeros) { |
345 | 29.3k | if (hashpos == chainz[hashpos]) break; |
346 | 28.0k | hashpos = chainz[hashpos]; |
347 | 28.0k | if (zeros[hashpos] != numzeros) break; |
348 | 2.06M | } else { |
349 | 2.06M | if (hashpos == chain[hashpos]) break; |
350 | 1.98M | hashpos = chain[hashpos]; |
351 | 1.98M | if (val[hashpos] != static_cast<int>(hashval)) { |
352 | | // outdated hash value |
353 | 0 | break; |
354 | 0 | } |
355 | 1.98M | } |
356 | 2.09M | } |
357 | 78.7k | } Unexecuted instantiation: enc_lz77.cc:void jxl::(anonymous namespace)::HashChain<1u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) constUnexecuted instantiation: enc_lz77.cc:void jxl::(anonymous namespace)::HashChain<3u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) constUnexecuted instantiation: enc_lz77.cc:void jxl::(anonymous namespace)::HashChain<8u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) constenc_lz77.cc:void jxl::(anonymous namespace)::HashChain<256u>::FindMatches<jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}>(unsigned long, int, jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1} const&) constLine | Count | Source | 299 | 78.7k | void FindMatches(size_t pos, int max_dist, const CB& found_match) const { | 300 | 78.7k | uint32_t wpos = pos & window_mask_; | 301 | 78.7k | uint32_t hashval = GetHash(pos, data_) & hash_mask_; | 302 | 78.7k | uint32_t hashpos = chain[wpos]; | 303 | | | 304 | 78.7k | int prev_dist = 0; | 305 | 78.7k | int end = std::min<int>(pos + max_length_, size_); | 306 | 78.7k | uint32_t chainlength = 0; | 307 | 78.7k | uint32_t best_len = 0; | 308 | 2.09M | for (;;) { | 309 | 2.09M | int dist = (hashpos <= wpos) ? (wpos - hashpos) | 310 | 2.09M | : (wpos - hashpos + window_mask_ + 1); | 311 | 2.09M | if (dist < prev_dist) break; | 312 | 2.09M | prev_dist = dist; | 313 | 2.09M | uint32_t len = 0; | 314 | 2.09M | if (dist > 0) { | 315 | 2.06M | int i = pos; | 316 | 2.06M | int j = pos - dist; | 317 | 2.06M | if (numzeros > 3) { | 318 | 1.36M | int r = std::min<int>(numzeros - 1, zeros[hashpos]); | 319 | 1.36M | if (i + r >= end) r = end - i - 1; | 320 | 1.36M | i += r; | 321 | 1.36M | j += r; | 322 | 1.36M | } | 323 | 8.54M | while (i < end && data_[i] == data_[j]) { | 324 | 6.47M | i++; | 325 | 6.47M | j++; | 326 | 6.47M | } | 327 | 2.06M | len = i - pos; | 328 | | // This can trigger even if the new length is slightly smaller than the | 329 | | // best length, because it is possible for a slightly cheaper distance | 330 | | // symbol to occur. | 331 | 2.06M | if (len >= min_length_ && len + 2 >= best_len) { | 332 | 1.21M | auto it = special_dist_table_.find(dist); | 333 | 1.21M | int dist_symbol = (it == special_dist_table_.end()) | 334 | 1.21M | ? (num_special_distances_ + dist - 1) | 335 | 1.21M | : it->second; | 336 | 1.21M | found_match(len, dist_symbol); | 337 | 1.21M | if (len > best_len) best_len = len; | 338 | 1.21M | } | 339 | 2.06M | } | 340 | | | 341 | 2.09M | chainlength++; | 342 | 2.09M | if (chainlength >= maxchainlength) break; | 343 | | | 344 | 2.09M | if (numzeros >= 3 && len > numzeros) { | 345 | 29.3k | if (hashpos == chainz[hashpos]) break; | 346 | 28.0k | hashpos = chainz[hashpos]; | 347 | 28.0k | if (zeros[hashpos] != numzeros) break; | 348 | 2.06M | } else { | 349 | 2.06M | if (hashpos == chain[hashpos]) break; | 350 | 1.98M | hashpos = chain[hashpos]; | 351 | 1.98M | if (val[hashpos] != static_cast<int>(hashval)) { | 352 | | // outdated hash value | 353 | 0 | break; | 354 | 0 | } | 355 | 1.98M | } | 356 | 2.09M | } | 357 | 78.7k | } |
|
358 | | void FindMatch(size_t pos, int max_dist, size_t* result_dist_symbol, |
359 | | size_t* result_len) const { |
360 | | *result_dist_symbol = 0; |
361 | | *result_len = 1; |
362 | | FindMatches(pos, max_dist, [&](size_t len, size_t dist_symbol) { |
363 | | if (len > *result_len || |
364 | | (len == *result_len && *result_dist_symbol > dist_symbol)) { |
365 | | *result_len = len; |
366 | | *result_dist_symbol = dist_symbol; |
367 | | } |
368 | | }); |
369 | | } |
370 | | }; |
371 | | |
372 | 12.3k | float LenCost(size_t len) { |
373 | 12.3k | uint32_t nbits, bits, tok; |
374 | 12.3k | HybridUintConfig(1, 0, 0).Encode(len, &tok, &nbits, &bits); |
375 | 12.3k | constexpr float kCostTable[] = { |
376 | 12.3k | 2.797667318563126, 3.213177690381199, 2.5706009246743737, |
377 | 12.3k | 2.408392498667534, 2.829649191872326, 3.3923087753324577, |
378 | 12.3k | 4.029267451554331, 4.415576699706408, 4.509357574741465, |
379 | 12.3k | 9.21481543803004, 10.020590190114898, 11.858671627804766, |
380 | 12.3k | 12.45853300490526, 11.713105831990857, 12.561996324849314, |
381 | 12.3k | 13.775477692278367, 13.174027068768641, |
382 | 12.3k | }; |
383 | 12.3k | size_t table_size = sizeof kCostTable / sizeof *kCostTable; |
384 | 12.3k | if (tok >= table_size) tok = table_size - 1; |
385 | 12.3k | return kCostTable[tok] + nbits; |
386 | 12.3k | } |
387 | | |
388 | | // TODO(veluca): this does not take into account usage or non-usage of distance |
389 | | // multipliers. |
390 | 12.3k | float DistCost(size_t dist) { |
391 | 12.3k | uint32_t nbits, bits, tok; |
392 | 12.3k | HybridUintConfig(7, 0, 0).Encode(dist, &tok, &nbits, &bits); |
393 | 12.3k | constexpr float kCostTable[] = { |
394 | 12.3k | 6.368282626312716, 5.680793277090298, 8.347404197105247, |
395 | 12.3k | 7.641619201599141, 6.914328374119438, 7.959808291537444, |
396 | 12.3k | 8.70023120759855, 8.71378518934703, 9.379132523982769, |
397 | 12.3k | 9.110472749092708, 9.159029569270908, 9.430936766731973, |
398 | 12.3k | 7.278284055315169, 7.8278514904267755, 10.026641158289236, |
399 | 12.3k | 9.976049229827066, 9.64351607048908, 9.563403863480442, |
400 | 12.3k | 10.171474111762747, 10.45950155077234, 9.994813912104219, |
401 | 12.3k | 10.322524683741156, 8.465808729388186, 8.756254166066853, |
402 | 12.3k | 10.160930174662234, 10.247329273413435, 10.04090403724809, |
403 | 12.3k | 10.129398517544082, 9.342311691539546, 9.07608009102374, |
404 | 12.3k | 10.104799540677513, 10.378079384990906, 10.165828974075072, |
405 | 12.3k | 10.337595322341553, 7.940557464567944, 10.575665823319431, |
406 | 12.3k | 11.023344321751955, 10.736144698831827, 11.118277044595054, |
407 | 12.3k | 7.468468230648442, 10.738305230932939, 10.906980780216568, |
408 | 12.3k | 10.163468216353817, 10.17805759656433, 11.167283670483565, |
409 | 12.3k | 11.147050200274544, 10.517921919244333, 10.651764778156886, |
410 | 12.3k | 10.17074446448919, 11.217636876224745, 11.261630721139484, |
411 | 12.3k | 11.403140815247259, 10.892472096873417, 11.1859607804481, |
412 | 12.3k | 8.017346947551262, 7.895143720278828, 11.036577113822025, |
413 | 12.3k | 11.170562110315794, 10.326988722591086, 10.40872184751056, |
414 | 12.3k | 11.213498225466386, 11.30580635516863, 10.672272515665442, |
415 | 12.3k | 10.768069466228063, 11.145257364153565, 11.64668307145549, |
416 | 12.3k | 10.593156194627339, 11.207499484844943, 10.767517766396908, |
417 | 12.3k | 10.826629811407042, 10.737764794499988, 10.6200448518045, |
418 | 12.3k | 10.191315385198092, 8.468384171390085, 11.731295299170432, |
419 | 12.3k | 11.824619886654398, 10.41518844301179, 10.16310536548649, |
420 | 12.3k | 10.539423685097576, 10.495136599328031, 10.469112847728267, |
421 | 12.3k | 11.72057686174922, 10.910326337834674, 11.378921834673758, |
422 | 12.3k | 11.847759036098536, 11.92071647623854, 10.810628276345282, |
423 | 12.3k | 11.008601085273893, 11.910326337834674, 11.949212023423133, |
424 | 12.3k | 11.298614839104337, 11.611603659010392, 10.472930394619985, |
425 | 12.3k | 11.835564720850282, 11.523267392285337, 12.01055816679611, |
426 | 12.3k | 8.413029688994023, 11.895784139536406, 11.984679534970505, |
427 | 12.3k | 11.220654278717394, 11.716311684833672, 10.61036646226114, |
428 | 12.3k | 10.89849965960364, 10.203762898863669, 10.997560826267238, |
429 | 12.3k | 11.484217379438984, 11.792836176993665, 12.24310468755171, |
430 | 12.3k | 11.464858097919262, 12.212747017409377, 11.425595666074955, |
431 | 12.3k | 11.572048533398757, 12.742093965163013, 11.381874288645637, |
432 | 12.3k | 12.191870445817015, 11.683156920035426, 11.152442115262197, |
433 | 12.3k | 11.90303691580457, 11.653292787169159, 11.938615382266098, |
434 | 12.3k | 16.970641701570223, 16.853602280380002, 17.26240782594733, |
435 | 12.3k | 16.644655390108507, 17.14310889757499, 16.910935455445955, |
436 | 12.3k | 17.505678976959697, 17.213498225466388, 2.4162310293553024, |
437 | 12.3k | 3.494587244462329, 3.5258600986408344, 3.4959806589517095, |
438 | 12.3k | 3.098390886949687, 3.343454654302911, 3.588847442290287, |
439 | 12.3k | 4.14614790111827, 5.152948641990529, 7.433696808092598, |
440 | 12.3k | 9.716311684833672, |
441 | 12.3k | }; |
442 | 12.3k | size_t table_size = sizeof kCostTable / sizeof *kCostTable; |
443 | 12.3k | if (tok >= table_size) tok = table_size - 1; |
444 | 12.3k | return kCostTable[tok] + nbits; |
445 | 12.3k | } |
446 | | |
447 | | |
448 | | // Fast hash map for LZ77 compression using a fixed-size ring buffer per bucket. |
449 | | // Overwrites the oldest candidates when a bucket reaches max capacity. |
450 | | // For optimal performance kBucketSize should be a power of 2 minus 1 |
451 | | template <uint16_t kBucketSize, int kHashSize> |
452 | | class LZ77HashMap { |
453 | | public: |
454 | | // Initializes the hash map mask, table size, and distance lookup table. |
455 | | explicit LZ77HashMap(const std::vector<uint32_t>& data, |
456 | | size_t distance_multiplier, uint32_t hash_bits = 14) |
457 | 334 | : data_(data), |
458 | 334 | hash_mask_((uint32_t{1} << hash_bits) - 1), |
459 | 334 | hash_table_(static_cast<size_t>(hash_mask_) + 1) { |
460 | 334 | num_special_distances_ = 0; |
461 | 334 | if (distance_multiplier) { |
462 | 0 | num_special_distances_ = kNumSpecialDistances; |
463 | 0 | special_dist_table_.assign(distance_multiplier * 8 + 8, -1); |
464 | 0 | for (int8_t i = 0; i < static_cast<int8_t>(kNumSpecialDistances); i++) { |
465 | 0 | special_dist_table_[SpecialDistance(i, distance_multiplier)] = i; |
466 | 0 | } |
467 | 0 | } |
468 | 334 | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) Line | Count | Source | 457 | 334 | : data_(data), | 458 | 334 | hash_mask_((uint32_t{1} << hash_bits) - 1), | 459 | 334 | hash_table_(static_cast<size_t>(hash_mask_) + 1) { | 460 | 334 | num_special_distances_ = 0; | 461 | 334 | if (distance_multiplier) { | 462 | 0 | num_special_distances_ = kNumSpecialDistances; | 463 | 0 | special_dist_table_.assign(distance_multiplier * 8 + 8, -1); | 464 | 0 | for (int8_t i = 0; i < static_cast<int8_t>(kNumSpecialDistances); i++) { | 465 | 0 | special_dist_table_[SpecialDistance(i, distance_multiplier)] = i; | 466 | 0 | } | 467 | 0 | } | 468 | 334 | } |
Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::LZ77HashMap(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, unsigned long, unsigned int) |
469 | | |
470 | | // Searches for the longest matching sequence in the bucket for 'pos'. |
471 | | // Updates length and dist_symbol with the best match found, then inserts 'pos'. |
472 | 63.6k | void FindMatch(size_t pos, size_t& len, size_t& dist_symbol, size_t min_len) { |
473 | 63.6k | const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_; |
474 | 63.6k | len = 0; |
475 | 63.6k | dist_symbol = 0; |
476 | | |
477 | | // Scan all existing entries in the bucket |
478 | 115k | for (uint16_t i = 0; i < hash_table_[h].size; i++) { |
479 | 51.9k | const uint32_t candidate = hash_table_[h].data[i]; |
480 | 51.9k | size_t dist = pos - candidate; |
481 | 51.9k | size_t cur_length = MatchLength(candidate, pos); |
482 | | |
483 | | // Skip matches shorter than current best or min_length |
484 | 51.9k | if (cur_length < len || cur_length < min_len) { |
485 | 14.6k | continue; |
486 | 14.6k | } |
487 | | |
488 | | // Map distance to a symbol encoding (prefers special distance codes if available) |
489 | 37.2k | size_t cur_dist_symbol = (num_special_distances_ + dist - 1); |
490 | 37.2k | if (dist < special_dist_table_.size()) { |
491 | 0 | int lookup = special_dist_table_[dist]; |
492 | 0 | if (lookup >= 0) { |
493 | 0 | cur_dist_symbol = lookup; |
494 | 0 | } |
495 | 0 | } |
496 | | |
497 | | // Reject if same length but requires a worse/longer distance symbol |
498 | | // Trying to compare the cost from cost estimation does not help with the current implementation of sce |
499 | 37.2k | if (cur_length == len && cur_dist_symbol >= dist_symbol) { |
500 | 7.99k | continue; |
501 | 7.99k | } |
502 | | |
503 | 29.2k | len = cur_length; |
504 | 29.2k | dist_symbol = cur_dist_symbol; |
505 | 29.2k | } |
506 | | |
507 | | // Insert 'pos' into the bucket ring buffer |
508 | 63.6k | hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize); |
509 | 63.6k | hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos); |
510 | 63.6k | hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize; |
511 | 63.6k | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) Line | Count | Source | 472 | 63.6k | void FindMatch(size_t pos, size_t& len, size_t& dist_symbol, size_t min_len) { | 473 | 63.6k | const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_; | 474 | 63.6k | len = 0; | 475 | 63.6k | dist_symbol = 0; | 476 | | | 477 | | // Scan all existing entries in the bucket | 478 | 115k | for (uint16_t i = 0; i < hash_table_[h].size; i++) { | 479 | 51.9k | const uint32_t candidate = hash_table_[h].data[i]; | 480 | 51.9k | size_t dist = pos - candidate; | 481 | 51.9k | size_t cur_length = MatchLength(candidate, pos); | 482 | | | 483 | | // Skip matches shorter than current best or min_length | 484 | 51.9k | if (cur_length < len || cur_length < min_len) { | 485 | 14.6k | continue; | 486 | 14.6k | } | 487 | | | 488 | | // Map distance to a symbol encoding (prefers special distance codes if available) | 489 | 37.2k | size_t cur_dist_symbol = (num_special_distances_ + dist - 1); | 490 | 37.2k | if (dist < special_dist_table_.size()) { | 491 | 0 | int lookup = special_dist_table_[dist]; | 492 | 0 | if (lookup >= 0) { | 493 | 0 | cur_dist_symbol = lookup; | 494 | 0 | } | 495 | 0 | } | 496 | | | 497 | | // Reject if same length but requires a worse/longer distance symbol | 498 | | // Trying to compare the cost from cost estimation does not help with the current implementation of sce | 499 | 37.2k | if (cur_length == len && cur_dist_symbol >= dist_symbol) { | 500 | 7.99k | continue; | 501 | 7.99k | } | 502 | | | 503 | 29.2k | len = cur_length; | 504 | 29.2k | dist_symbol = cur_dist_symbol; | 505 | 29.2k | } | 506 | | | 507 | | // Insert 'pos' into the bucket ring buffer | 508 | 63.6k | hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize); | 509 | 63.6k | hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos); | 510 | 63.6k | hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize; | 511 | 63.6k | } |
Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::FindMatch(unsigned long, unsigned long&, unsigned long&, unsigned long) |
512 | | |
513 | | // Inserts 'pos' into its bucket without performing a match search. |
514 | 102k | void Update(size_t pos) { |
515 | 102k | const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_; |
516 | 102k | hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize); |
517 | 102k | hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos); |
518 | 102k | hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize; |
519 | 102k | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::Update(unsigned long) enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::Update(unsigned long) Line | Count | Source | 514 | 102k | void Update(size_t pos) { | 515 | 102k | const uint32_t h = GetHash<kHashSize>(pos, data_) & hash_mask_; | 516 | 102k | hash_table_[h].size = std::min(static_cast<uint16_t>(hash_table_[h].size + 1), kBucketSize); | 517 | 102k | hash_table_[h].data[hash_table_[h].idx] = static_cast<uint32_t>(pos); | 518 | 102k | hash_table_[h].idx = hash_table_[h].idx + 1 - static_cast<uint16_t>(hash_table_[h].idx + 1 >= kBucketSize) * kBucketSize; | 519 | 102k | } |
Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::Update(unsigned long) Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::Update(unsigned long) |
520 | | |
521 | | private: |
522 | | // Fixed-capacity bucket stored as a ring buffer. |
523 | | struct HashBucket { |
524 | | std::array<uint32_t, kBucketSize> data; |
525 | | uint16_t idx = 0; // Insertion index |
526 | | uint16_t size = 0; // Current entry count (<= kBucketSize) |
527 | | }; |
528 | | |
529 | | // Measures matching prefix length between indices 'a' and 'b'. |
530 | 51.9k | size_t MatchLength(size_t a, size_t b) const { |
531 | 51.9k | JXL_DASSERT(a < b); |
532 | 51.9k | size_t len = 0; |
533 | 448k | while (b + len < data_.size() && data_[a + len] == data_[b + len]) { |
534 | 396k | ++len; |
535 | 396k | } |
536 | 51.9k | return len; |
537 | 51.9k | } Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)1, 3>::MatchLength(unsigned long, unsigned long) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)3, 3>::MatchLength(unsigned long, unsigned long) const enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)7, 3>::MatchLength(unsigned long, unsigned long) const Line | Count | Source | 530 | 51.9k | size_t MatchLength(size_t a, size_t b) const { | 531 | 51.9k | JXL_DASSERT(a < b); | 532 | 51.9k | size_t len = 0; | 533 | 448k | while (b + len < data_.size() && data_[a + len] == data_[b + len]) { | 534 | 396k | ++len; | 535 | 396k | } | 536 | 51.9k | return len; | 537 | 51.9k | } |
Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)15, 3>::MatchLength(unsigned long, unsigned long) const Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::LZ77HashMap<(unsigned short)31, 3>::MatchLength(unsigned long, unsigned long) const |
538 | | |
539 | | const std::vector<uint32_t>& data_; |
540 | | uint32_t hash_mask_; |
541 | | std::vector<HashBucket> hash_table_; |
542 | | std::vector<int8_t> special_dist_table_; |
543 | | size_t num_special_distances_; |
544 | | }; |
545 | | |
546 | | |
547 | | // Fast LZ77 compression on streams of tokens using cost-based matching decision. |
548 | | // Returns the compressed token streams if savings exceed the threshold. |
549 | | template<int kBucketSize = 7, int kHashSize = 3, bool kRuntimeCostComparison = false> |
550 | | std::vector<std::vector<Token>> ApplyLZ77_LZ77( |
551 | | const HistogramParams& params, size_t num_contexts, |
552 | | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77 |
553 | 334 | ) { |
554 | 334 | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); |
555 | 334 | SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77); |
556 | 334 | float bit_decrease = 0; |
557 | 334 | size_t total_symbols = 0; |
558 | 334 | HybridUintConfig uint_config; |
559 | 334 | std::vector<float> sym_cost; |
560 | | |
561 | 668 | for (size_t stream = 0; stream < tokens.size(); stream++) { |
562 | 334 | size_t distance_multiplier = |
563 | 334 | params.image_widths.size() > stream ? params.image_widths[stream] : 0; |
564 | 334 | const auto& in = tokens[stream]; |
565 | 334 | auto& out = tokens_lz77[stream]; |
566 | 334 | total_symbols += in.size(); |
567 | 334 | std::vector<uint32_t> data; |
568 | 334 | data.resize(in.size()); |
569 | | |
570 | | // Cumulative sum of bit costs for fast range estimation |
571 | 334 | sym_cost.resize(in.size() + 1); |
572 | 166k | for (size_t pos = 0; pos < in.size(); pos++) { |
573 | 166k | uint32_t tok, nbits, unused_bits; |
574 | 166k | uint_config.Encode(in[pos].value, &tok, &nbits, &unused_bits); |
575 | 166k | sym_cost[pos + 1] = sce.Bits(in[pos].context, tok) + nbits + sym_cost[pos]; |
576 | 166k | data[pos] = in[pos].value; |
577 | 166k | } |
578 | | |
579 | 334 | out.reserve(in.size()); |
580 | 334 | size_t min_length = lz77.min_length; |
581 | 334 | JXL_DASSERT(min_length >= 3); |
582 | | |
583 | 334 | LZ77HashMap<kBucketSize, kHashSize> hash_map = |
584 | 334 | LZ77HashMap<kBucketSize, kHashSize>(data, distance_multiplier); |
585 | | |
586 | 64.3k | for (size_t pos = 0; pos < in.size(); pos++) { |
587 | 64.0k | out.push_back(in[pos]); |
588 | | |
589 | | // Guard against reading past input bounds |
590 | 64.0k | if (pos + kHashSize >= in.size()) { |
591 | 334 | continue; |
592 | 334 | } |
593 | | |
594 | 63.6k | size_t len; |
595 | 63.6k | size_t dist_symbol; |
596 | | |
597 | | // Find longest sequence match in current bucket |
598 | 63.6k | hash_map.FindMatch(pos, len, dist_symbol, min_length); |
599 | 63.6k | if (len < min_length) continue; |
600 | | |
601 | | // Bit cost comparison: raw tokens vs LZ77 pair |
602 | 12.3k | float cost = sym_cost[pos + len] - sym_cost[pos]; |
603 | 12.3k | size_t lz77_len = len - lz77.min_length; |
604 | 12.3k | float lz77_cost = LenCost(lz77_len) + DistCost(dist_symbol); |
605 | | |
606 | 12.3k | if (kRuntimeCostComparison && lz77_cost > cost) { |
607 | 0 | for (size_t offset = 1; offset < len; offset++) { |
608 | 0 | out.push_back(in[pos+offset]); |
609 | 0 | hash_map.Update(pos + offset); |
610 | 0 | } |
611 | 0 | pos += len - 1; |
612 | 0 | continue; |
613 | 0 | } |
614 | | |
615 | | |
616 | 12.3k | bit_decrease += cost - lz77_cost - sce.AddSymbolCost(out.back().context); |
617 | | |
618 | | // Emit LZ77 length and distance tokens |
619 | 12.3k | out.back().value = len - min_length; |
620 | 12.3k | out.back().is_lz77_length = true; |
621 | 12.3k | out.emplace_back( |
622 | 12.3k | static_cast<uint32_t>(lz77.nonserialized_distance_context), |
623 | 12.3k | static_cast<uint32_t>(dist_symbol)); |
624 | | |
625 | | // Update hash map for skipped positions inside the match |
626 | 114k | for (size_t offset = 1; offset < len; offset++) { |
627 | 102k | hash_map.Update(pos + offset); |
628 | 102k | } |
629 | 12.3k | pos += len - 1; |
630 | 12.3k | } |
631 | 334 | } |
632 | | |
633 | | // Return LZ77 streams only if net bit savings justify overhead |
634 | 334 | if (bit_decrease > total_symbols * 0.2 + 16) { |
635 | 181 | return tokens_lz77; |
636 | 181 | } |
637 | 153 | return {}; |
638 | 334 | } Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<1, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<3, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<7, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Line | Count | Source | 553 | 334 | ) { | 554 | 334 | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); | 555 | 334 | SymbolCostEstimator sce(num_contexts, params.force_huffman, tokens, lz77); | 556 | 334 | float bit_decrease = 0; | 557 | 334 | size_t total_symbols = 0; | 558 | 334 | HybridUintConfig uint_config; | 559 | 334 | std::vector<float> sym_cost; | 560 | | | 561 | 668 | for (size_t stream = 0; stream < tokens.size(); stream++) { | 562 | 334 | size_t distance_multiplier = | 563 | 334 | params.image_widths.size() > stream ? params.image_widths[stream] : 0; | 564 | 334 | const auto& in = tokens[stream]; | 565 | 334 | auto& out = tokens_lz77[stream]; | 566 | 334 | total_symbols += in.size(); | 567 | 334 | std::vector<uint32_t> data; | 568 | 334 | data.resize(in.size()); | 569 | | | 570 | | // Cumulative sum of bit costs for fast range estimation | 571 | 334 | sym_cost.resize(in.size() + 1); | 572 | 166k | for (size_t pos = 0; pos < in.size(); pos++) { | 573 | 166k | uint32_t tok, nbits, unused_bits; | 574 | 166k | uint_config.Encode(in[pos].value, &tok, &nbits, &unused_bits); | 575 | 166k | sym_cost[pos + 1] = sce.Bits(in[pos].context, tok) + nbits + sym_cost[pos]; | 576 | 166k | data[pos] = in[pos].value; | 577 | 166k | } | 578 | | | 579 | 334 | out.reserve(in.size()); | 580 | 334 | size_t min_length = lz77.min_length; | 581 | 334 | JXL_DASSERT(min_length >= 3); | 582 | | | 583 | 334 | LZ77HashMap<kBucketSize, kHashSize> hash_map = | 584 | 334 | LZ77HashMap<kBucketSize, kHashSize>(data, distance_multiplier); | 585 | | | 586 | 64.3k | for (size_t pos = 0; pos < in.size(); pos++) { | 587 | 64.0k | out.push_back(in[pos]); | 588 | | | 589 | | // Guard against reading past input bounds | 590 | 64.0k | if (pos + kHashSize >= in.size()) { | 591 | 334 | continue; | 592 | 334 | } | 593 | | | 594 | 63.6k | size_t len; | 595 | 63.6k | size_t dist_symbol; | 596 | | | 597 | | // Find longest sequence match in current bucket | 598 | 63.6k | hash_map.FindMatch(pos, len, dist_symbol, min_length); | 599 | 63.6k | if (len < min_length) continue; | 600 | | | 601 | | // Bit cost comparison: raw tokens vs LZ77 pair | 602 | 12.3k | float cost = sym_cost[pos + len] - sym_cost[pos]; | 603 | 12.3k | size_t lz77_len = len - lz77.min_length; | 604 | 12.3k | float lz77_cost = LenCost(lz77_len) + DistCost(dist_symbol); | 605 | | | 606 | 12.3k | if (kRuntimeCostComparison && lz77_cost > cost) { | 607 | 0 | for (size_t offset = 1; offset < len; offset++) { | 608 | 0 | out.push_back(in[pos+offset]); | 609 | 0 | hash_map.Update(pos + offset); | 610 | 0 | } | 611 | 0 | pos += len - 1; | 612 | 0 | continue; | 613 | 0 | } | 614 | | | 615 | | | 616 | 12.3k | bit_decrease += cost - lz77_cost - sce.AddSymbolCost(out.back().context); | 617 | | | 618 | | // Emit LZ77 length and distance tokens | 619 | 12.3k | out.back().value = len - min_length; | 620 | 12.3k | out.back().is_lz77_length = true; | 621 | 12.3k | out.emplace_back( | 622 | 12.3k | static_cast<uint32_t>(lz77.nonserialized_distance_context), | 623 | 12.3k | static_cast<uint32_t>(dist_symbol)); | 624 | | | 625 | | // Update hash map for skipped positions inside the match | 626 | 114k | for (size_t offset = 1; offset < len; offset++) { | 627 | 102k | hash_map.Update(pos + offset); | 628 | 102k | } | 629 | 12.3k | pos += len - 1; | 630 | 12.3k | } | 631 | 334 | } | 632 | | | 633 | | // Return LZ77 streams only if net bit savings justify overhead | 634 | 334 | if (bit_decrease > total_symbols * 0.2 + 16) { | 635 | 181 | return tokens_lz77; | 636 | 181 | } | 637 | 153 | return {}; | 638 | 334 | } |
Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<15, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<31, 3, false>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<1, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<3, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<7, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<15, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_LZ77<31, 3, true>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) |
639 | | |
640 | | template <uint32_t kMaxChainLength> |
641 | | std::vector<std::vector<Token>> ApplyLZ77_Optimal( |
642 | | const HistogramParams& params, size_t num_contexts, |
643 | 334 | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) { |
644 | 334 | std::vector<std::vector<Token>> tokens_for_cost_estimate = |
645 | 334 | ApplyLZ77_LZ77(params, num_contexts, tokens, lz77); |
646 | | // If greedy-LZ77 does not give better compression than no-lz77, no reason to |
647 | | // run the optimal matching. |
648 | 334 | if (tokens_for_cost_estimate.empty()) return {}; |
649 | 181 | SymbolCostEstimator sce(num_contexts + 1, params.force_huffman, |
650 | 181 | tokens_for_cost_estimate, lz77); |
651 | 181 | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); |
652 | 181 | HybridUintConfig uint_config; |
653 | 181 | std::vector<float> sym_cost; |
654 | 181 | std::vector<uint32_t> dist_symbols; |
655 | 362 | for (size_t stream = 0; stream < tokens.size(); stream++) { |
656 | 181 | size_t distance_multiplier = |
657 | 181 | params.image_widths.size() > stream ? params.image_widths[stream] : 0; |
658 | 181 | const auto& in = tokens[stream]; |
659 | 181 | auto& out = tokens_lz77[stream]; |
660 | | // Cumulative sum of bit costs. |
661 | 181 | sym_cost.resize(in.size() + 1); |
662 | 92.7k | for (size_t i = 0; i < in.size(); i++) { |
663 | 92.5k | uint32_t tok, nbits, unused_bits; |
664 | 92.5k | uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits); |
665 | 92.5k | sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i]; |
666 | 92.5k | } |
667 | | |
668 | 181 | out.reserve(in.size()); |
669 | 181 | size_t max_distance = in.size(); |
670 | 181 | size_t min_length = lz77.min_length; |
671 | 181 | JXL_DASSERT(min_length >= 3); |
672 | 181 | size_t max_length = in.size(); |
673 | | |
674 | | // Use next power of two as window size. |
675 | 181 | size_t window_size = 1; |
676 | 1.92k | while (window_size < max_distance && window_size < kWindowSize) { |
677 | 1.74k | window_size <<= 1; |
678 | 1.74k | } |
679 | | |
680 | 181 | HashChain<kMaxChainLength> chain(in.data(), in.size(), window_size, min_length, max_length, |
681 | 181 | distance_multiplier); |
682 | | |
683 | 181 | struct MatchInfo { |
684 | 181 | uint32_t len; |
685 | 181 | uint32_t dist_symbol; |
686 | 181 | uint32_t ctx; |
687 | 181 | float total_cost = std::numeric_limits<float>::max(); |
688 | 181 | }; |
689 | | // Total cost to encode the first N symbols. |
690 | 181 | std::vector<MatchInfo> prefix_costs(in.size() + 1); |
691 | 181 | prefix_costs[0].total_cost = 0; |
692 | | |
693 | 181 | size_t rle_length = 0; |
694 | 181 | size_t skip_lz77 = 0; |
695 | 92.7k | for (size_t i = 0; i < in.size(); i++) { |
696 | 92.5k | chain.Update(i); |
697 | 92.5k | float lit_cost = |
698 | 92.5k | prefix_costs[i].total_cost + sym_cost[i + 1] - sym_cost[i]; |
699 | 92.5k | if (prefix_costs[i + 1].total_cost > lit_cost) { |
700 | 35.9k | prefix_costs[i + 1].dist_symbol = 0; |
701 | 35.9k | prefix_costs[i + 1].len = 1; |
702 | 35.9k | prefix_costs[i + 1].ctx = in[i].context; |
703 | 35.9k | prefix_costs[i + 1].total_cost = lit_cost; |
704 | 35.9k | } |
705 | 92.5k | if (skip_lz77 > 0) { |
706 | 13.7k | skip_lz77--; |
707 | 13.7k | continue; |
708 | 13.7k | } |
709 | 78.7k | dist_symbols.clear(); |
710 | 78.7k | chain.FindMatches(i, max_distance, |
711 | 1.21M | [&dist_symbols](size_t len, size_t dist_symbol) { |
712 | 1.21M | if (dist_symbols.size() <= len) { |
713 | 78.7k | dist_symbols.resize(len + 1, dist_symbol); |
714 | 78.7k | } |
715 | 1.21M | if (dist_symbol < dist_symbols[len]) { |
716 | 0 | dist_symbols[len] = dist_symbol; |
717 | 0 | } |
718 | 1.21M | }); Unexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) constUnexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) constUnexecuted instantiation: enc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) constenc_lz77.cc:jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) constLine | Count | Source | 711 | 1.21M | [&dist_symbols](size_t len, size_t dist_symbol) { | 712 | 1.21M | if (dist_symbols.size() <= len) { | 713 | 78.7k | dist_symbols.resize(len + 1, dist_symbol); | 714 | 78.7k | } | 715 | 1.21M | if (dist_symbol < dist_symbols[len]) { | 716 | 0 | dist_symbols[len] = dist_symbol; | 717 | 0 | } | 718 | 1.21M | }); |
|
719 | 78.7k | if (dist_symbols.size() <= min_length) continue; |
720 | 45.6k | { |
721 | 45.6k | size_t best_cost = dist_symbols.back(); |
722 | 894k | for (size_t j = dist_symbols.size() - 1; j >= min_length; j--) { |
723 | 848k | if (dist_symbols[j] < best_cost) { |
724 | 33.0k | best_cost = dist_symbols[j]; |
725 | 33.0k | } |
726 | 848k | dist_symbols[j] = best_cost; |
727 | 848k | } |
728 | 45.6k | } |
729 | 894k | for (size_t j = min_length; j < dist_symbols.size(); j++) { |
730 | | // Cost model that uses results from lazy LZ77. |
731 | 848k | float lz77_cost = sce.LenCost(in[i].context, j - min_length, lz77) + |
732 | 848k | sce.DistCost(dist_symbols[j], lz77); |
733 | 848k | float cost = prefix_costs[i].total_cost + lz77_cost; |
734 | 848k | if (prefix_costs[i + j].total_cost > cost) { |
735 | 88.1k | prefix_costs[i + j].len = j; |
736 | 88.1k | prefix_costs[i + j].dist_symbol = dist_symbols[j] + 1; |
737 | 88.1k | prefix_costs[i + j].ctx = in[i].context; |
738 | 88.1k | prefix_costs[i + j].total_cost = cost; |
739 | 88.1k | } |
740 | 848k | } |
741 | | // We are in a RLE sequence: skip all the symbols except the first 8 and |
742 | | // the last 8. This avoid quadratic costs for sequences with long runs of |
743 | | // the same symbol. |
744 | 45.6k | if ((dist_symbols.back() == 0 && distance_multiplier == 0) || |
745 | 31.4k | (dist_symbols.back() == 1 && distance_multiplier != 0)) { |
746 | 14.1k | rle_length++; |
747 | 31.4k | } else { |
748 | 31.4k | rle_length = 0; |
749 | 31.4k | } |
750 | 45.6k | if (rle_length >= 8 && dist_symbols.size() > 9) { |
751 | 631 | skip_lz77 = dist_symbols.size() - 10; |
752 | 631 | rle_length = 0; |
753 | 631 | } |
754 | 45.6k | } |
755 | 181 | size_t pos = in.size(); |
756 | 31.1k | while (pos > 0) { |
757 | 30.9k | bool is_lz77_length = prefix_costs[pos].dist_symbol != 0; |
758 | 30.9k | if (is_lz77_length) { |
759 | 6.00k | size_t dist_symbol = prefix_costs[pos].dist_symbol - 1; |
760 | 6.00k | out.emplace_back( |
761 | 6.00k | static_cast<uint32_t>(lz77.nonserialized_distance_context), |
762 | 6.00k | static_cast<uint32_t>(dist_symbol)); |
763 | 6.00k | } |
764 | 30.9k | uint32_t val = |
765 | 30.9k | is_lz77_length |
766 | 30.9k | ? (prefix_costs[pos].len - static_cast<uint32_t>(min_length)) |
767 | 30.9k | : in[pos - 1].value; |
768 | 30.9k | out.emplace_back(prefix_costs[pos].ctx, val); |
769 | 30.9k | out.back().is_lz77_length = is_lz77_length; |
770 | 30.9k | pos -= prefix_costs[pos].len; |
771 | 30.9k | } |
772 | 181 | if (!out.empty()) std::reverse(out.begin(), out.end()); |
773 | 181 | } |
774 | 181 | return tokens_lz77; |
775 | 334 | } Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<1u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<3u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Unexecuted instantiation: enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<8u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) enc_lz77.cc:std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > jxl::(anonymous namespace)::ApplyLZ77_Optimal<256u>(jxl::HistogramParams const&, unsigned long, std::__1::vector<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> >, std::__1::allocator<std::__1::vector<jxl::Token, std::__1::allocator<jxl::Token> > > > const&, jxl::LZ77Params const&) Line | Count | Source | 643 | 334 | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) { | 644 | 334 | std::vector<std::vector<Token>> tokens_for_cost_estimate = | 645 | 334 | ApplyLZ77_LZ77(params, num_contexts, tokens, lz77); | 646 | | // If greedy-LZ77 does not give better compression than no-lz77, no reason to | 647 | | // run the optimal matching. | 648 | 334 | if (tokens_for_cost_estimate.empty()) return {}; | 649 | 181 | SymbolCostEstimator sce(num_contexts + 1, params.force_huffman, | 650 | 181 | tokens_for_cost_estimate, lz77); | 651 | 181 | std::vector<std::vector<Token>> tokens_lz77(tokens.size()); | 652 | 181 | HybridUintConfig uint_config; | 653 | 181 | std::vector<float> sym_cost; | 654 | 181 | std::vector<uint32_t> dist_symbols; | 655 | 362 | for (size_t stream = 0; stream < tokens.size(); stream++) { | 656 | 181 | size_t distance_multiplier = | 657 | 181 | params.image_widths.size() > stream ? params.image_widths[stream] : 0; | 658 | 181 | const auto& in = tokens[stream]; | 659 | 181 | auto& out = tokens_lz77[stream]; | 660 | | // Cumulative sum of bit costs. | 661 | 181 | sym_cost.resize(in.size() + 1); | 662 | 92.7k | for (size_t i = 0; i < in.size(); i++) { | 663 | 92.5k | uint32_t tok, nbits, unused_bits; | 664 | 92.5k | uint_config.Encode(in[i].value, &tok, &nbits, &unused_bits); | 665 | 92.5k | sym_cost[i + 1] = sce.Bits(in[i].context, tok) + nbits + sym_cost[i]; | 666 | 92.5k | } | 667 | | | 668 | 181 | out.reserve(in.size()); | 669 | 181 | size_t max_distance = in.size(); | 670 | 181 | size_t min_length = lz77.min_length; | 671 | 181 | JXL_DASSERT(min_length >= 3); | 672 | 181 | size_t max_length = in.size(); | 673 | | | 674 | | // Use next power of two as window size. | 675 | 181 | size_t window_size = 1; | 676 | 1.92k | while (window_size < max_distance && window_size < kWindowSize) { | 677 | 1.74k | window_size <<= 1; | 678 | 1.74k | } | 679 | | | 680 | 181 | HashChain<kMaxChainLength> chain(in.data(), in.size(), window_size, min_length, max_length, | 681 | 181 | distance_multiplier); | 682 | | | 683 | 181 | struct MatchInfo { | 684 | 181 | uint32_t len; | 685 | 181 | uint32_t dist_symbol; | 686 | 181 | uint32_t ctx; | 687 | 181 | float total_cost = std::numeric_limits<float>::max(); | 688 | 181 | }; | 689 | | // Total cost to encode the first N symbols. | 690 | 181 | std::vector<MatchInfo> prefix_costs(in.size() + 1); | 691 | 181 | prefix_costs[0].total_cost = 0; | 692 | | | 693 | 181 | size_t rle_length = 0; | 694 | 181 | size_t skip_lz77 = 0; | 695 | 92.7k | for (size_t i = 0; i < in.size(); i++) { | 696 | 92.5k | chain.Update(i); | 697 | 92.5k | float lit_cost = | 698 | 92.5k | prefix_costs[i].total_cost + sym_cost[i + 1] - sym_cost[i]; | 699 | 92.5k | if (prefix_costs[i + 1].total_cost > lit_cost) { | 700 | 35.9k | prefix_costs[i + 1].dist_symbol = 0; | 701 | 35.9k | prefix_costs[i + 1].len = 1; | 702 | 35.9k | prefix_costs[i + 1].ctx = in[i].context; | 703 | 35.9k | prefix_costs[i + 1].total_cost = lit_cost; | 704 | 35.9k | } | 705 | 92.5k | if (skip_lz77 > 0) { | 706 | 13.7k | skip_lz77--; | 707 | 13.7k | continue; | 708 | 13.7k | } | 709 | 78.7k | dist_symbols.clear(); | 710 | 78.7k | chain.FindMatches(i, max_distance, | 711 | 78.7k | [&dist_symbols](size_t len, size_t dist_symbol) { | 712 | 78.7k | if (dist_symbols.size() <= len) { | 713 | 78.7k | dist_symbols.resize(len + 1, dist_symbol); | 714 | 78.7k | } | 715 | 78.7k | if (dist_symbol < dist_symbols[len]) { | 716 | 78.7k | dist_symbols[len] = dist_symbol; | 717 | 78.7k | } | 718 | 78.7k | }); | 719 | 78.7k | if (dist_symbols.size() <= min_length) continue; | 720 | 45.6k | { | 721 | 45.6k | size_t best_cost = dist_symbols.back(); | 722 | 894k | for (size_t j = dist_symbols.size() - 1; j >= min_length; j--) { | 723 | 848k | if (dist_symbols[j] < best_cost) { | 724 | 33.0k | best_cost = dist_symbols[j]; | 725 | 33.0k | } | 726 | 848k | dist_symbols[j] = best_cost; | 727 | 848k | } | 728 | 45.6k | } | 729 | 894k | for (size_t j = min_length; j < dist_symbols.size(); j++) { | 730 | | // Cost model that uses results from lazy LZ77. | 731 | 848k | float lz77_cost = sce.LenCost(in[i].context, j - min_length, lz77) + | 732 | 848k | sce.DistCost(dist_symbols[j], lz77); | 733 | 848k | float cost = prefix_costs[i].total_cost + lz77_cost; | 734 | 848k | if (prefix_costs[i + j].total_cost > cost) { | 735 | 88.1k | prefix_costs[i + j].len = j; | 736 | 88.1k | prefix_costs[i + j].dist_symbol = dist_symbols[j] + 1; | 737 | 88.1k | prefix_costs[i + j].ctx = in[i].context; | 738 | 88.1k | prefix_costs[i + j].total_cost = cost; | 739 | 88.1k | } | 740 | 848k | } | 741 | | // We are in a RLE sequence: skip all the symbols except the first 8 and | 742 | | // the last 8. This avoid quadratic costs for sequences with long runs of | 743 | | // the same symbol. | 744 | 45.6k | if ((dist_symbols.back() == 0 && distance_multiplier == 0) || | 745 | 31.4k | (dist_symbols.back() == 1 && distance_multiplier != 0)) { | 746 | 14.1k | rle_length++; | 747 | 31.4k | } else { | 748 | 31.4k | rle_length = 0; | 749 | 31.4k | } | 750 | 45.6k | if (rle_length >= 8 && dist_symbols.size() > 9) { | 751 | 631 | skip_lz77 = dist_symbols.size() - 10; | 752 | 631 | rle_length = 0; | 753 | 631 | } | 754 | 45.6k | } | 755 | 181 | size_t pos = in.size(); | 756 | 31.1k | while (pos > 0) { | 757 | 30.9k | bool is_lz77_length = prefix_costs[pos].dist_symbol != 0; | 758 | 30.9k | if (is_lz77_length) { | 759 | 6.00k | size_t dist_symbol = prefix_costs[pos].dist_symbol - 1; | 760 | 6.00k | out.emplace_back( | 761 | 6.00k | static_cast<uint32_t>(lz77.nonserialized_distance_context), | 762 | 6.00k | static_cast<uint32_t>(dist_symbol)); | 763 | 6.00k | } | 764 | 30.9k | uint32_t val = | 765 | 30.9k | is_lz77_length | 766 | 30.9k | ? (prefix_costs[pos].len - static_cast<uint32_t>(min_length)) | 767 | 30.9k | : in[pos - 1].value; | 768 | 30.9k | out.emplace_back(prefix_costs[pos].ctx, val); | 769 | 30.9k | out.back().is_lz77_length = is_lz77_length; | 770 | 30.9k | pos -= prefix_costs[pos].len; | 771 | 30.9k | } | 772 | 181 | if (!out.empty()) std::reverse(out.begin(), out.end()); | 773 | 181 | } | 774 | 181 | return tokens_lz77; | 775 | 334 | } |
|
776 | | |
777 | | } // namespace |
778 | | |
779 | | std::vector<std::vector<Token>> ApplyLZ77( |
780 | | const HistogramParams& params, size_t num_contexts, |
781 | 40.4k | const std::vector<std::vector<Token>>& tokens, const LZ77Params& lz77) { |
782 | 40.4k | switch (params.lz77_method) { |
783 | 29.3k | case HistogramParams::LZ77Method::kRLE: |
784 | 29.3k | return ApplyLZ77_RLE(params, num_contexts, tokens, lz77); |
785 | 0 | case HistogramParams::LZ77Method::kLZ77b1w3f: |
786 | 0 | return ApplyLZ77_LZ77<1, 3, false>(params, num_contexts, tokens, lz77); |
787 | 0 | case HistogramParams::LZ77Method::kLZ77b3w3f: |
788 | 0 | return ApplyLZ77_LZ77<3, 3, false>(params, num_contexts, tokens, lz77); |
789 | 0 | case HistogramParams::LZ77Method::kLZ77b7w3f: |
790 | 0 | return ApplyLZ77_LZ77<7, 3, false>(params, num_contexts, tokens, lz77); |
791 | 0 | case HistogramParams::LZ77Method::kLZ77b15w3f: |
792 | 0 | return ApplyLZ77_LZ77<15, 3, false>(params, num_contexts, tokens, lz77); |
793 | 0 | case HistogramParams::LZ77Method::kLZ77b31w3f: |
794 | 0 | return ApplyLZ77_LZ77<31, 3, false>(params, num_contexts, tokens, lz77); |
795 | 0 | case HistogramParams::LZ77Method::kLZ77b1w3t: |
796 | 0 | return ApplyLZ77_LZ77<1, 3, true>(params, num_contexts, tokens, lz77); |
797 | 0 | case HistogramParams::LZ77Method::kLZ77b3w3t: |
798 | 0 | return ApplyLZ77_LZ77<3, 3, true>(params, num_contexts, tokens, lz77); |
799 | 0 | case HistogramParams::LZ77Method::kLZ77b7w3t: |
800 | 0 | return ApplyLZ77_LZ77<7, 3, true>(params, num_contexts, tokens, lz77); |
801 | 0 | case HistogramParams::LZ77Method::kLZ77b15w3t: |
802 | 0 | return ApplyLZ77_LZ77<15, 3, true>(params, num_contexts, tokens, lz77); |
803 | 0 | case HistogramParams::LZ77Method::kLZ77b31w3t: |
804 | 0 | return ApplyLZ77_LZ77<31, 3, true>(params, num_contexts, tokens, lz77); |
805 | 0 | case HistogramParams::LZ77Method::kOptc1: |
806 | 0 | return ApplyLZ77_Optimal<1>(params, num_contexts, tokens, lz77); |
807 | 0 | case HistogramParams::LZ77Method::kOptc3: |
808 | 0 | return ApplyLZ77_Optimal<3>(params, num_contexts, tokens, lz77); |
809 | 0 | case HistogramParams::LZ77Method::kOptc8: |
810 | 0 | return ApplyLZ77_Optimal<8>(params, num_contexts, tokens, lz77); |
811 | 334 | case HistogramParams::LZ77Method::kOptc256: |
812 | 334 | return ApplyLZ77_Optimal<256>(params, num_contexts, tokens, lz77); |
813 | 10.8k | default: |
814 | 10.8k | return {}; |
815 | 40.4k | } |
816 | 40.4k | } |
817 | | |
818 | | } // namespace jxl |