3
\$\begingroup\$

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_stream every 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;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.