I needed to write zlib wrapper class for my application, so I can change it with different algorithm later if I want to do so.
Here is the simple version:
struct ZLibEasyCompressor{
bool operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out);
};
struct ZLibEasyDeCompressor{
bool operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out);
};
// ===========================
bool ZLibEasyCompressor::operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out){
if (data_in == nullptr || size_in == 0 || data_out == nullptr || size_out == 0)
return false;
z_stream zs;
memset(&zs, 0, sizeof(z_stream));
if ( deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK )
return false;
zs.next_in = reinterpret_cast<Bytef *>( const_cast<char *>( data_in ) );
zs.avail_in = static_cast<uInt>( size_in );
zs.next_out = reinterpret_cast<Bytef *>( data_out );
zs.avail_out = static_cast<uInt>( size_out );
int const result = deflate(&zs, Z_FINISH);
size_out = zs.total_out;
deflateEnd(&zs);
return result == Z_STREAM_END;
}
// ===========================
bool ZLibEasyDeCompressor::operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out){
if (data_in == nullptr || size_in == 0 || data_out == nullptr || size_out == 0)
return false;
z_stream zs;
memset(&zs, 0, sizeof(z_stream));
if ( inflateInit(&zs) != Z_OK )
return false;
zs.next_in = reinterpret_cast<Bytef *>( const_cast<char *>( data_in ) );
zs.avail_in = static_cast<uInt>( size_in );
zs.next_out = reinterpret_cast<Bytef *>( data_out );
zs.avail_out = static_cast<uInt>( size_out );
int const result = inflate(&zs, Z_FINISH);
size_out = zs.total_out;
inflateEnd(&zs);
return result == Z_STREAM_END;
}
This all works well, but:
- have repetition in the code that can be omit using templates or something.
- it initialize the control structure
z_streamevery time. As we can see later, this degrade the performance with 33%.
However, if I move z_stream to header file, there is a huge problem - I need to include zlib.h into the header and it will "spill" lots of constants and defines all around.
I wanted to avoid this at all costs:
https://stackoverflow.com/questions/39534366/how-to-keep-global-namespace-clean-from-third-party-lib
The dynamic allocation with opaque / void pointer can fix this, but there will be one more call for allocation / de-allocation.
This is why I did something crazy - I used a buffer + cast (we discussed it as placement new).
So here is the final version. It is:
- no runtime overhead. I compared against plain C version and speed is same.
- faster compression when you reuse the same class.
- 33% - 40% faster decompression when you reuse the same class (depends of optimization).
- no code repetition.
- will refuse to compile if something is changed in
z_stream.
zlib.h
#ifndef ZLIB_COMPRESSOR_H_
#define ZLIB_COMPRESSOR_H_
#include <cstddef>
struct ZLibBase_{
constexpr static size_t BUFFER_SIZE = 128;
};
class ZLibCompressor : public ZLibBase_{
public:
const static int NO_COMPRESSION ;
const static int BEST_SPEED ;
const static int BEST_COMPRESSION ;
const static int DEFAULT_COMPRESSION ;
public:
ZLibCompressor(int compressionLevel = DEFAULT_COMPRESSION);
~ZLibCompressor();
bool operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out);
private:
char buffer_[BUFFER_SIZE];
};
// ===========================
class ZLibDeCompressor : public ZLibBase_{
public:
ZLibDeCompressor();
~ZLibDeCompressor();
bool operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out);
private:
char buffer_[BUFFER_SIZE];
};
#endif
zlib.cc
#include "zlib.h"
#include <stdexcept>
#include <cstring>
#include <zlib.h>
template<bool OP>
struct ZLibOpsBase_;
template<>
struct ZLibOpsBase_<true>{
static bool init(z_stream *zs, int const compressionLevel){
return deflateInit(zs, compressionLevel) == Z_OK;
}
static int done(z_stream *zs){
return deflateEnd(zs);
}
static int reset(z_stream *zs){
return deflateReset(zs);
}
static int process(z_stream *zs){
return deflate(zs, Z_FINISH);
}
};
// ===========================
template<>
struct ZLibOpsBase_<false>{
static bool init(z_stream *zs, int const){
return inflateInit(zs) == Z_OK;
}
static int done(z_stream *zs){
return inflateEnd(zs);
}
static int reset(z_stream *zs){
return inflateReset(zs);
}
static int process(z_stream *zs){
return inflate(zs, Z_FINISH);
}
};
// ===========================
template<bool OP>
struct ZLibOps_{
static void init(z_stream *zs, int const compressionLevel){
memset(zs, 0, sizeof(z_stream));
int const result = ZLibOpsBase_<OP>::init(zs, compressionLevel);
if (!result){
std::bad_alloc exception;
throw exception;
}
}
static int done(z_stream *zs){
return ZLibOpsBase_<OP>::done(zs);
}
static bool process(z_stream *zs, const char *data, size_t const size, char *data_out, size_t &size_out){
if (data == nullptr || size == 0 || data_out == nullptr || size_out == 0)
return false;
ZLibOpsBase_<OP>::reset(zs);
zs->next_in = reinterpret_cast<Bytef *>( const_cast<char *>( data ) );
zs->avail_in = static_cast<uInt>( size );
zs->next_out = reinterpret_cast<Bytef *>( data_out );
zs->avail_out = static_cast<uInt>( size_out );
int const result = ZLibOpsBase_<OP>::process(zs);
size_out = zs->total_out;
return result == Z_STREAM_END;
}
};
// ===========================
inline z_stream *cast_(char *buffer){
static_assert( sizeof(z_stream) < ZLibBase_::BUFFER_SIZE, "Increase the buffer");
return reinterpret_cast<z_stream *>(buffer);
}
// ===========================
const int ZLibCompressor::NO_COMPRESSION = Z_NO_COMPRESSION ;
const int ZLibCompressor::BEST_SPEED = Z_BEST_SPEED ;
const int ZLibCompressor::BEST_COMPRESSION = Z_BEST_COMPRESSION ;
const int ZLibCompressor::DEFAULT_COMPRESSION = Z_DEFAULT_COMPRESSION ;
ZLibCompressor::ZLibCompressor(int const compressionLevel){
ZLibOps_<true>::init(cast_(buffer_), compressionLevel);
}
ZLibCompressor::~ZLibCompressor(){
ZLibOps_<true>::done(cast_(buffer_));
}
bool ZLibCompressor::operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out){
return ZLibOps_<true>::process(cast_(buffer_), data_in, size_in, data_out, size_out);
}
// ===========================
ZLibDeCompressor::ZLibDeCompressor(){
ZLibOps_<false>::init(cast_(buffer_), 0);
}
ZLibDeCompressor::~ZLibDeCompressor(){
ZLibOps_<false>::done(cast_(buffer_));
}
bool ZLibDeCompressor::operator()(const char *data_in, size_t const size_in, char *data_out, size_t &size_out){
return ZLibOps_<false>::process(cast_(buffer_), data_in, size_in, data_out, size_out);
}
Finally the example usage:
#if 0
#include "zlibeasy.h"
using Compressor = ZLibEasyCompressor;
using DeCompressor = ZLibEasyDeCompressor;
#else
#include "zlib.h"
using Compressor = ZLibCompressor;
using DeCompressor = ZLibDeCompressor;
#endif
#include <cstring>
#include <iostream>
#include <iomanip>
#include <cstdio>
constexpr size_t SIZE = 1024;
int main(){
Compressor compressor;
DeCompressor decompressor;
const char *odata = "Hello World";
size_t osize = strlen(odata);
char cbuffer[SIZE];
size_t csize = SIZE;
char dbuffer[SIZE];
size_t dsize = SIZE;
bool const cr = compressor(odata, osize, cbuffer, csize);
bool const dr = decompressor(cbuffer, csize, dbuffer, dsize);
std::cout << std::fixed << std::setprecision(2) << ( float(osize) / float(csize) ) << std::endl;
if ( ! cr || ! dr || osize != dsize || memcmp(odata, dbuffer, osize) ){
std::cout << "Error" << std::endl;
}
}