Skip to content

Commit a53da92

Browse files
feat: Enhance Huffman encoding in pzip for improved compression efficiency
- Added new methods for histogram calculation, dynamic header writing, and code generation in HuffmanBitWriter. - Introduced a new writeBlockHuff method to handle Huffman-only compression for low token counts. - Implemented quick entropy detection to optimize compression decisions based on data characteristics. - Updated FastDeflate to utilize the new Huffman encoding features for better performance. Log: Improve Huffman encoding and compression efficiency in pzip bug: https://pms.uniontech.com/bug-view-346679.html
1 parent 28555e3 commit a53da92

5 files changed

Lines changed: 394 additions & 90 deletions

File tree

3rdparty/pzip/CMakeLists.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,9 @@ target_link_libraries(pzip_core_lib ${ZLIB_LIBRARIES} Threads::Threads)
3535
target_compile_features(pzip_core_lib PUBLIC cxx_std_17)
3636

3737
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm")
38-
# ARM 架构优化
39-
target_compile_options(pzip_core_lib PRIVATE -O3 -ffast-math -funroll-loops)
38+
target_compile_options(pzip_core_lib PRIVATE -O3 -funroll-loops)
4039
else()
41-
# x86 架构优化
42-
target_compile_options(pzip_core_lib PRIVATE -O3 -march=native -ffast-math -funroll-loops)
40+
target_compile_options(pzip_core_lib PRIVATE -O3 -mtune=generic -funroll-loops)
4341
endif()
4442

4543
add_executable(pzip-bin cmd/pzip_main.cpp)

3rdparty/pzip/include/pzip/fast_deflate.h

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717

1818
#include <cstdint>
1919
#include <cstring>
20+
#include <functional>
2021
#include <vector>
2122
#include <array>
2223
#include <algorithm>
2324
#include <memory>
25+
#include <utility>
2426

2527
#if defined(__GNUC__) || defined(__clang__)
2628
#define PZIP_FORCE_INLINE __attribute__((always_inline)) inline
@@ -304,6 +306,13 @@ class HuffmanEncoder {
304306
class HuffmanBitWriter {
305307
public:
306308
explicit HuffmanBitWriter();
309+
~HuffmanBitWriter() = default;
310+
311+
HuffmanBitWriter(const HuffmanBitWriter&) = delete;
312+
HuffmanBitWriter& operator=(const HuffmanBitWriter&) = delete;
313+
314+
HuffmanBitWriter(HuffmanBitWriter&&) = default;
315+
HuffmanBitWriter& operator=(HuffmanBitWriter&&) = default;
307316

308317
void reset();
309318
void flush();
@@ -330,32 +339,46 @@ class HuffmanBitWriter {
330339

331340
void writeBlock(Tokens* tokens, bool eof, const uint8_t* input, size_t inputLen);
332341
void writeBlockDynamic(Tokens* tokens, bool eof, const uint8_t* input, size_t inputLen, bool sync);
342+
void writeBlockHuff(bool eof, const uint8_t* input, size_t inputLen, bool sync);
333343

334344
void writeTokens(const Token* tokens, size_t n, const HCode* leCodes, const HCode* oeCodes);
335345

336346
const std::vector<uint8_t>& data() const { return output_; }
337347
std::vector<uint8_t>& data() { return output_; }
338348

349+
void setLogNewTablePenalty(int penalty) { logNewTablePenalty_ = penalty; }
350+
339351
private:
340352
void writeOutBits();
341353
void indexTokens(Tokens* t, bool alwaysEOB);
342354
void generate();
343355
int extraBitSize();
344356
int fixedSize(int extraBits);
345357
int storedSize(const uint8_t* input, size_t len, bool* storable);
358+
void histogram(const uint8_t* input, size_t len);
359+
std::pair<int, int> headerSize();
360+
void generateCodegen(int numLiterals, int numOffsets, HuffmanEncoder* litEnc, HuffmanEncoder* offEnc);
361+
int codegens();
362+
void writeDynamicHeader(int numLiterals, int numOffsets, int numCodegens, bool isEof);
346363

347364
std::vector<uint8_t> output_;
348365
uint64_t bits_ = 0;
349366
uint8_t nbits_ = 0;
350367
uint8_t nbytes_ = 0;
351368
int lastHeader_ = 0;
369+
bool lastHuffMan_ = false;
370+
int logNewTablePenalty_ = 7;
352371

353372
std::array<uint8_t, 256 + 8> bytes_;
354373
std::array<uint16_t, LENGTH_CODES_START + 32> literalFreq_;
355374
std::array<uint16_t, 32> offsetFreq_;
375+
std::array<uint16_t, 19> codegenFreq_;
376+
std::array<uint8_t, LITERAL_COUNT + OFFSET_CODE_COUNT + 1> codegen_;
356377

357378
std::unique_ptr<HuffmanEncoder> literalEncoding_;
358379
std::unique_ptr<HuffmanEncoder> offsetEncoding_;
380+
std::unique_ptr<HuffmanEncoder> tmpLitEncoding_;
381+
std::unique_ptr<HuffmanEncoder> codegenEncoding_;
359382
};
360383

361384
// ============================================================================
@@ -375,9 +398,13 @@ class FastGen {
375398
FastGen() : cur_(MAX_STORE_BLOCK_SIZE) {
376399
hist_.reserve(ALLOC_HISTORY);
377400
}
401+
virtual ~FastGen() = default;
378402

379403
int32_t addBlock(const uint8_t* src, size_t len);
380-
void reset();
404+
virtual void reset();
405+
406+
// 纯虚函数,由子类实现
407+
virtual void encode(Tokens* dst, const uint8_t* src, size_t len) = 0;
381408

382409
protected:
383410
std::vector<uint8_t> hist_;
@@ -392,8 +419,8 @@ class FastEncL1 : public FastGen {
392419
public:
393420
FastEncL1() { table_.fill({}); }
394421

395-
void encode(Tokens* dst, const uint8_t* src, size_t len);
396-
void reset();
422+
void encode(Tokens* dst, const uint8_t* src, size_t len) override;
423+
void reset() override;
397424

398425
private:
399426
std::array<TableEntry, TABLE_SIZE> table_;
@@ -410,8 +437,8 @@ class FastEncL4 : public FastGen {
410437
bTable_.fill({});
411438
}
412439

413-
void encode(Tokens* dst, const uint8_t* src, size_t len);
414-
void reset();
440+
void encode(Tokens* dst, const uint8_t* src, size_t len) override;
441+
void reset() override;
415442

416443
private:
417444
std::array<TableEntry, TABLE_SIZE> table_;
@@ -448,22 +475,46 @@ size_t deflateCompress(const uint8_t* input, size_t inputSize,
448475
CompressionLevel level = CompressionLevel::DefaultCompression);
449476

450477
// ============================================================================
451-
// DeflateStream
478+
// FlateWriter - 流式压缩器(参照 Go klauspost/compress flate.Writer)
452479
// ============================================================================
453480

454-
class DeflateStream {
481+
// 输出回调类型(参照 Go io.Writer)
482+
using WriteFunc = std::function<void(const uint8_t*, size_t)>;
483+
484+
class FlateWriter {
455485
public:
456-
explicit DeflateStream(CompressionLevel level = CompressionLevel::DefaultCompression);
457-
~DeflateStream();
486+
// 接收输出目标(参照 Go flate.NewWriter(w io.Writer, level int))
487+
explicit FlateWriter(WriteFunc output, CompressionLevel level = CompressionLevel::BestSpeed);
488+
~FlateWriter() = default;
458489

490+
// 流式写入数据(参照 Go compressor.write)
459491
size_t write(const uint8_t* data, size_t size);
460-
size_t finish(std::vector<uint8_t>& output);
461-
void reset();
492+
493+
// 完成压缩(参照 Go compressor.Close)
494+
void close();
495+
496+
// 重置并设置新的输出目标(参照 Go compressor.Reset)
497+
void reset(WriteFunc output);
462498

463499
private:
464-
std::unique_ptr<FastDeflate> deflate_;
465-
std::vector<uint8_t> buffer_;
466-
static constexpr size_t BUFFER_SIZE = 128 * 1024;
500+
void storeFast();
501+
size_t fillBlock(const uint8_t* data, size_t size);
502+
void flushOutput();
503+
504+
// 获取当前使用的编码器
505+
FastGen* encoder() const { return useL1_ ? static_cast<FastGen*>(encoderL1_.get())
506+
: static_cast<FastGen*>(encoderL4_.get()); }
507+
508+
WriteFunc output_;
509+
std::vector<uint8_t> window_;
510+
size_t windowEnd_ = 0;
511+
512+
CompressionLevel level_;
513+
bool useL1_ = true; // Level 1-3 使用 L1,Level 4+ 使用 L4
514+
std::unique_ptr<FastEncL1> encoderL1_;
515+
std::unique_ptr<FastEncL4> encoderL4_;
516+
std::unique_ptr<HuffmanBitWriter> writer_;
517+
Tokens tokens_;
467518
};
468519

469520
} // namespace pzip

3rdparty/pzip/src/archiver.cpp

Lines changed: 30 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -180,75 +180,52 @@ Error Archiver::compressFile(FileTask* task) {
180180
}
181181

182182
Error Archiver::compress(FileTask* task) {
183-
// 目录不需要压缩
184183
if (fs::is_directory(task->status)) {
185184
return Error();
186185
}
187186

188-
// 打开源文件
189187
std::ifstream file(task->path, std::ios::binary);
190188
if (!file.is_open()) {
191189
return Error(ErrorCode::FILE_OPEN_ERROR, "Cannot open file: " + task->path.string());
192190
}
193191

194-
// 读取整个文件到内存
195-
std::vector<uint8_t> fileData(task->fileSize);
196-
file.read(reinterpret_cast<char*>(fileData.data()), task->fileSize);
197-
if (static_cast<size_t>(file.gcount()) != task->fileSize) {
198-
return Error(ErrorCode::FILE_READ_ERROR, "Failed to read file: " + task->path.string());
192+
FlateWriter writer([task](const uint8_t* data, size_t size) {
193+
task->write(data, size);
194+
});
195+
196+
constexpr size_t BUFFER_SIZE = 32 * 1024;
197+
std::vector<uint8_t> buf(BUFFER_SIZE);
198+
uint32_t crc = 0;
199+
uint64_t totalBytesRead = 0;
200+
201+
while (file.good() && !file.eof()) {
202+
file.read(reinterpret_cast<char*>(buf.data()), buf.size());
203+
auto bytesRead = file.gcount();
204+
if (bytesRead > 0) {
205+
crc = ::crc32(crc, buf.data(), bytesRead);
206+
writer.write(buf.data(), bytesRead);
207+
totalBytesRead += bytesRead;
208+
}
199209
}
200-
file.close();
201-
202-
#ifdef USE_LIBDEFLATE
203-
// 使用 libdeflate(高性能)
204-
// 注意:libdeflate level 1 最快,level 12 压缩率最高
205-
// 默认使用 level 1(最快),用户可以通过 -6 等参数调整
206-
task->header.crc32 = libdeflate_crc32(0, fileData.data(), fileData.size());
207210

208-
int level = options_.compressionLevel;
209-
if (level < 1 || level > 12) level = 1; // 默认使用最快级别
210-
211-
struct libdeflate_compressor* compressor = libdeflate_alloc_compressor(level);
212-
if (!compressor) {
213-
return Error(ErrorCode::COMPRESSION_ERROR, "Failed to create compressor");
211+
// 检查 I/O 错误(bad() 表示严重错误,fail() 在 eof 时也会设置所以需要排除)
212+
if (file.bad()) {
213+
file.close();
214+
return Error(ErrorCode::FILE_READ_ERROR, "I/O error reading file: " + task->path.string());
214215
}
215216

216-
size_t maxCompressedSize = libdeflate_deflate_compress_bound(compressor, fileData.size());
217-
std::vector<uint8_t> compressed(maxCompressedSize);
218-
219-
size_t compressedSize = libdeflate_deflate_compress(
220-
compressor,
221-
fileData.data(), fileData.size(),
222-
compressed.data(), compressed.size()
223-
);
224-
225-
libdeflate_free_compressor(compressor);
226-
227-
if (compressedSize == 0 && !fileData.empty()) {
228-
return Error(ErrorCode::COMPRESSION_ERROR, "Compression failed");
217+
// 检查是否读取了完整文件(防止静默截断)
218+
if (totalBytesRead != task->fileSize) {
219+
file.close();
220+
return Error(ErrorCode::FILE_READ_ERROR,
221+
"Short read: expected " + std::to_string(task->fileSize) +
222+
" bytes, got " + std::to_string(totalBytesRead) + " for: " + task->path.string());
229223
}
230224

231-
task->write(compressed.data(), compressedSize);
232-
#else
233-
// 使用内置压缩器 - 使用 thread_local 避免每次创建新对象
234-
task->header.crc32 = ::crc32(0L, fileData.data(), fileData.size());
235-
236-
// thread_local 压缩器(使用最快级别)和输出缓冲区,避免重复分配
237-
thread_local FastDeflate deflate(CompressionLevel::BestSpeed);
238-
thread_local std::vector<uint8_t> compressed;
239-
240-
// 重置压缩器状态并清空缓冲区
241-
deflate.reset();
242-
compressed.clear();
243-
244-
size_t compressedSize = deflate.compress(fileData.data(), fileData.size(), compressed);
245-
246-
if (compressedSize == 0 && !fileData.empty()) {
247-
return Error(ErrorCode::COMPRESSION_ERROR, "Compression failed");
248-
}
225+
file.close();
249226

250-
task->write(compressed.data(), compressed.size());
251-
#endif
227+
task->header.crc32 = crc;
228+
writer.close();
252229

253230
return Error();
254231
}

0 commit comments

Comments
 (0)