Giter VIP home page Giter VIP logo

go-libdeflate's Introduction

libdeflate (optimized zlib) for go

License: MIT

This ultra fast Go zlib library wraps the libdeflate zlib-, gzip- and deflate-(de)compression library for Go, using cgo.

It is significantly faster (4-5 times) than Go's standard compress/zlib/gzip/flate libraries (see benchmarks) at the expense of not being able to stream data (e.g. from disk). Therefore, this library is optimal for the use case of (de)compressing whole-buffered in-memory data: If it fits into your memory, this library can (de)compress it much faster than the standard libraries (or even C zlib) can.

Important

Use V2 of this library via go get github.com/4kills/go-libdeflate/v2!

Warning

V1 will be deprecated by January 19, 2025. Until then it will receive bug fixes but no feature improvements.

Table of Contents

Features

  • Super fast zlib, gzip, and raw deflate compression / decompression
  • Convenience functions for quicker one-time compression / decompression
  • More (zlib/gzip/flate compatible) compression levels for better compression ratios than with standard zlib/gzip/flate
  • Simple and clean API

Availability of the original libdeflate API:

  • zlib/gzip/deflate compression
  • zlib/gzip/deflate decompression
  • Definite upper bound of compressed size
  • Decompression w/ info about number of consumed bytes
  • adler32 and crc32 checksums
  • Custom memory allocator : no implementation planned, due to too little relevance for a high level Go API

Installation

For the library to work, you need cgo, libedflate (which is used by this library under the hood), and pkg-config (linker):

Install cgo

TL;DR: Get cgo working.

In order to use this library with your Go source code, you must be able to use the Go tool cgo, which, in turn, requires a GCC compiler.

If you are on Linux, there is a good chance you already have GCC installed, otherwise just get it with your favorite package manager.

If you are on MacOS, Xcode - for instance - supplies the required tools.

If you are on Windows, you will need to install GCC. I can recommend tdm-gcc which is based off of MinGW. Please note that cgo requires the 64-bit version (as stated here).

For any other the procedure should be about the same. Just google.

This SDK uses libdeflate under the hood. For the SDK to work, you need to install libdeflate on your system which is super easy! Additionally we require pkg-config which facilitates linking libdeflate with this (cgo) SDK. How exactly you install these two packages depends on your operating system.

MacOS (HomeBrew):

brew install libdeflate
brew install pkg-config

Linux:

Use the package manager available on your distro to install the required packages.

Windows (MinGW/WSL2):

Here, you can either use WSL2 or MinGW and from there install the required packages.

Usage

Compress

First, you need to create a compressor that can be used for any type of compression.

You can also specify a level of compression for which holds true: The higher the level, the higher the compression at the expense of speed. -> lower level = fast, bad compression; higher level = slow, good compression. Test what works best for your application but generally the DefaultCompressionLevel is fine most of the time.

// Compressor with default compression level. Errors if out of memory
c, err := libdeflate.NewCompressor()

// Compressor with custom compression level. Errors if out of memory or if an illegal level was passed. 
c, err = libdeflate.NewCompressorLevel(2)

Then you can compress the actual data with a given mode of compression (currently supported: zlib, gzip, raw deflate):

decomp := []byte(`Some data to compress: May be anything,  
    but it might be a good idea to only compress data that exceeds a certain threshold in size, 
    as compressed data can become larger (due to overhead)`)
comp := make([]byte, len(decomp)) // supplying a fitting buffer is in all cases the fastest approach

n, _, err := c.Compress(decomp, comp, libdeflate.ModeZlib) // Errors if buffer was too short
comp = comp[:n]

You can also pass nil for out, and the function will allocate a fitting buffer by itself:

_, comp, err = c.Compress(decomp, nil, libdeflate.ModeZlib)

After you are done with the compressor, do not forget to close it to free c-allocated-memory:

c.Close()

Decompress

As with compression, you need to create a decompressor which can also be used for any type of decompression at any compression level:

// Doesn't need a compression level; works universally. Errors if out of memory.
dc, err := libdeflate.NewDecompressor() 

Then you can decompress the actual data with a given mode of compression (currently supported: zlib, gzip, raw deflate):

// must be exactly the size of the output, if unknown, pass nil for out(see below)
decompressed := make([]byte, len(decomp)) 

_, err = dc.Decompress(comp, decompressed, ModeZlib) 

Just like with compress you can also pass nil and get a fitting buffer:

decompressed, err = dc.Decompress(comp, nil, ModeZlib)

After you are done with the decompressor, do not forget to close it to free c-allocated-memory:

dc.Close()

There are also convenience functions that allow one-time compression to be easier, as well as functions to directly compress to zlib format.

Notes

  • Do NOT use the same Compressor / Decompressor across multiple threads simultaneously. However, you can create as many of them as you like, so if you want to parallelize your application, just create a compressor / decompressor for each thread. (See Memory Usage down below for more info)

  • Always Close() your Compressor / Decompressor when you are done with it - especially if you create a new compressor/decompressor for each compression/decompression you undertake (which is generally discouraged anyway). As the C-part of this library is not subject to the Go garbage collector, the memory allocated by it must be released manually (by a call to Close()) to avoid memory leakage.

  • Memory Usage: Compressing requires at least ~32 KiB of additional memory during execution, while Decompressing also requires at least ~32 KiB of additional memory during execution.

Benchmarks

These benchmarks were conducted with "real-life-type data" to ensure that these tests are most representative for an actual use case in a practical production environment. As the zlib standard has been traditionally used for compressing smaller chunks of data, I have decided to follow suite by opting for Minecraft client-server communication packets, as they represent the optimal use case for this library.

To that end, I have recorded 930 individual Minecraft packets, totalling 11,445,993 bytes in uncompressed data and 1,564,159 bytes in compressed data. These packets represent actual client-server communication and were recorded using this software.

The benchmarks were executed on different hardware and operating systems, including AMD and Intel processors, as well as all the out-of-the-box supported operating systems (Windows, Linux, MacOS). All the benchmarked functions/methods were executed hundreds of times, and the numbers you are about to see are the averages over all these executions.

The data was compressed using compression level 6 (current default of zlib).

These benchmarks compare this library (blue) to the Go standard library (yellow) and show that this library performs way better in all cases.

  • (A note regarding testing on your machine)

    Please note that you will need an Internet connection for some benchmarks to function. This is because these benchmarks will download the mc packets from here and temporarily store them in memory for the duration of the benchmark tests, so this repository won't have to include the data in order save space on your machine and to make it a lightweight library.

Compression

compression total

This chart shows how long it took for the methods of this library (blue), and the standard library (yellow) to compress all of the 930 packets (~11.5 MB) on different systems in milliseconds. Note that the two rightmost data points were tested on exactly the same hardware in a dual-boot setup and that Linux seems to generally perform better than Windows.

compression relative

This chart shows the time it took for this library's Compress (blue) to compress the data in nanoseconds, as well as the time it took for the standard library's Write (WriteStd, yellow) to compress the data in nanoseconds. The vertical axis shows percentages relative to the time needed by the standard library, thus you can see how much faster this library is.

For example: This library only needed ~29% of the time required by the standard library to compress the packets on an Intel Core i5-6600K on Windows. That makes the standard library a substantial ~244.8% slower than this library.

Decompression

compression total

This chart shows how long it took for the methods of this library (blue), and the standard library (yellow) to decompress all of the 930 packets (~1.5 MB) on different systems in milliseconds. Note that the two rightmost data points were tested on exactly the same hardware in a dual-boot setup and that Linux seems to generally perform better than Windows.

decompression relative

This chart shows the time it took for this library's Decompress (blue) to decompress the data in nanoseconds, as well as the time it took for the standard library's Read (ReadStd, Yellow) to decompress the data in nanoseconds. The vertical axis shows percentages relative to the time needed by the standard library, thus you can see how much faster this library is.

For example: This library only needed ~34% of the time required by the standard library to decompress the packets on an Intel Core i5-6600K on Windows. That makes the standard library a substantial ~194.1% slower than this library.

Compression Ratio

Across all the benchmarks on all the different hardware / operating systems the compression ratios were consistent: This library had a compression ratio of 5.77 while the standard library had a compression ratio of 5.75, which is a negligible difference.

The compression ratio r is calculated as r = ucs / cs, where ucs = uncompressed size and cs = compressed size.

License

MIT License

Copyright (c) 2020 Dominik Ochs 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

go-libdeflate's People

Contributors

4kills avatar haveachin avatar oflebbe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

go-libdeflate's Issues

Support for large buffers (>2G/4G)

Hi there,

Thank you for making this package, libdeflate is very fast. Have you tried to decompress buffers that are larger than 4G ?

Some functions work with ints, so I wonder how things will work if the decompressed buffer is larger than what an int can hold.

// DecompressGzip interfaces with c libdeflate for gzip decompression
func DecompressGzip(dc *C.decomp, inAddr, outAddr *byte, inSize, outSize int, sPtr uintptr) error {
    return parseResult(C.res(C.libdeflate_gzip_decompress(dc,
        unsafe.Pointer(inAddr), intToInt64(inSize),
        unsafe.Pointer(outAddr), intToInt64(outSize),
        C.mkPtr(C.size_t(sPtr)),
    )))
}

go.sum is not synced with your go.mod

When trying to use the latest version with go modules, I encountered a problem with your go modules:

github.com/4kills/go-libdeflate: github.com/4kills/[email protected]: parsing go.mod:
        module declares its path as: github.com/4kills/libdeflate
                but was required as: github.com/4kills/go-libdeflate

Try using go mod tidy to resolve it.

deadly data example

package main

import (
	"bytes"
	"compress/zlib"
	"encoding/hex"
	"fmt"
	"log"

	"github.com/4kills/go-libdeflate/v2"
	czlib "github.com/4kills/go-zlib"
)

var (
	s = "789c7d90316f83301085f7fc0ac45cac3b1b63cc0685542c55a4264b1784825bd1024686284851fe7b8104c454c" +
		"b8bdff7de9def6e3b6b3cf6db298dedc0b26f02384014858efb2af78ecb1573c224e24e92301e4919d158c0dd7e7" +
		"9a40a5de765f39ed76aca2e6a364caf5a17cacdbe2a7dcd8c6ab5e9d790eafab49d2ce87ac4f308724a5c5c70553" +
		"6bfe1b93f968fa214287570bcfc882c6034a0fc736bfde873f38f99aee666fccfb3ad0f043d4604122917dc1abdc" +
		"c6178deaa81a8416d60afcfba9ae8293e2cf2a5536609753f04194881be106c1de6d2eec7058c9c3180cdfc87691" +
		"f814505305c77796d9e66042e372dd2622ec001c143ff09bec7bea151f90c05f3e916cc22f57cc976f73fc74374"
	comp, _ = hex.DecodeString(s)
)

func main() {
	deWithZlib()       // nice, return error
	deWithLibdeflate() // hang, endless loop
	deWithCgoZlib()    // hang, endless loop
}

func deWithLibdeflate() {
	dc, _ := libdeflate.NewDecompressor()
	_, decompressed, err := dc.Decompress(comp, nil, libdeflate.ModeZlib)
	if err != nil {
		log.Println("deWithLibdeflate", err)
		return
	}
	fmt.Println(decompressed)
	dc.Close()
}

func deWithCgoZlib() {
	dc, _ := czlib.NewReader(nil)
	_, decompressed, err := dc.ReadBuffer(comp, nil)
	if err != nil {
		log.Println("deWithCgoZlib", err)
		return
	}
	fmt.Println(decompressed)
	_ = dc.Close()
}

func deWithZlib() {
	var decompressed bytes.Buffer
	dc, _ := zlib.NewReader(bytes.NewReader(comp))
	_, err := decompressed.ReadFrom(dc)
	if err != nil {
		log.Println("deWithZlib", err) // unexpected EOF
		return
	}
	fmt.Println(decompressed)
	_ = dc.Close()
}

go mod vendor support

I tried to vendor my dependencies but it failed because the libdeflate darwin library is not installed / copied over. Not sure how to fix that.

# github.com/4kills/go-libdeflate/native
clang: error: no such file or directory: '/xxx/vendor/github.com/4kills/go-libdeflate/native/libs/libdeflate_darwin_amd64.a'
FAIL	xxx/yyy [build failed]

Idea: Using sync.Pool for libdeflate.Compress (or Decompress)

Hey, first of all, great library, incredibly fast!

In my use case, I needed to compress relatively small chunks of data from many go routines. Obviously one doesn't want to create a new Compressor for each such chunk of data. Usually sync.Pool would be great to pool the Compressors, the only issue is that a free-list-based structure wouldn't be able to call Close to deallocate the data when the sync.Pool stuff gets periodically garbage collected and there would be leaks.

But I came across a quite nice solution to that problem (actually, credit where credit is due, I got the idea from ChatGPT) - using runtime.SetFinalizer to register a hook executed before the Compressor is garbage collected that will call Close. It works pretty great in my benchmarks - very fast and I see no leaks. So I mostly just wanted to share the idea with anyone else dealing with a similar problem.

In addition, I think you could consider adding a pooled version of libdeflate.Compress and libdeflate.Decompress using this trick. There would be an issue supporting multiple compression levels - might need a pool for each level, alternatively only the default level would have this pooled version. Here is an example of how this could be done:

var compressorPool = sync.Pool{
	New: func() interface{} {
		compressor, err := libdeflate.NewCompressor()
		if err != nil {
			panic(err) // Can only happen if the program ran out of memory - a panic is appropriate
		}
		compressorPtr := &compressor // Pointer needed for SetFinalizer, also helps avoid interface value allocations
		runtime.SetFinalizer(compressorPtr, func(finalized *libdeflate.Compressor) {
			finalized.Close()
		})
		return &compressor
	},
}

func CompressPooled(in, out []byte, m libdeflate.Mode) (int, []byte, error) {
	compressor := compressorPool.Get().(*libdeflate.Compressor)
	defer compressorPool.Put(compressor)
	return compressor.Compress(in, out, m)
}

Do you see any issues with this approach?

I even thought about the option of registering the finalizer for all Compressors or Decompressors created by the library, thus outright negating the need to call Close (could become a deprecated noop without breaking the API), but perhaps that's going a bit too far - especially since Go may take some time before finalizers are called and the C-allocated memory will linger for a bit.

Fatal error: unknown pc caller

Panic occurs after about 10 minutes of running tool

runtime: g 1978: unexpected return pc for github.com/valyala/fasthttp.(*HostClient).Do called from 0x2c5d7
stack: frame={sp:0xc004605d18, fp:0xc004605d78} stack=[0xc004604000,0xc004606000)
0x000000c004605c18:  0x000000c00270fc80  0x000000000041023f <runtime.mallocgc+0x000000000000063f>
0x000000c004605c28:  0x0000000000000000  0x0000000000000000
0x000000c004605c38:  0x000000c0049ff500  0x000000c0018e8a00
0x000000c004605c48:  0x000000c001d23340  0x000000c001f47980
0x000000c004605c58:  0x00007fccdafb8d28  0x0000000000000040
0x000000c004605c68:  0x000000c000880400  0x000000c00a79cf80
0x000000c004605c78:  0x000000c00118fa00  0x000000c00270fca8
0x000000c004605c88:  0xc116af33d41fb7c8  0x0000005c362483be
0x000000c004605c98:  0x000000000095b120  0x000000c004605d08
0x000000c004605ca8:  0x00000000006465ba <github.com/valyala/fasthttp.(*HostClient).do+0x00000000000000ba>  0x000000c0015111e0
0x000000c004605cb8:  0x000000c001513880  0x000000c00151e380
0x000000c004605cc8:  0x000000000070d8d0  0x0000000000000006
0x000000c004605cd8:  0x000000000000003f  0x0000000000000000
0x000000c004605ce8:  0x0000000000000000  0x0000000000000001
0x000000c004605cf8:  0x000000c00a79cf80  0x0000000000000000
0x000000c004605d08:  0x000000c004605d68  0x0000000000646311 <github.com/valyala/fasthttp.(*HostClient).Do+0x0000000000000091>
0x000000c004605d18: <0x000000c00270fd68  0x0000000000451ed0 <runtime.concatstring2+0x0000000000000050>
0x000000c004605d28:  0x000000c00118fb48  0x0000000000000005
0x000000c004605d38:  0x0000000000000002  0x0000000000000000
0x000000c004605d48:  0x0000000000000000  0x000000000070d948
0x000000c004605d58:  0x000000000000003f  0x000000c00151e380
0x000000c004605d68:  0x000000c004605fb8 !0x000000000002c5d7
0x000000c004605d78: >0x0000000000004aba  0x000000c001513880
0x000000c004605d88:  0x00000000006df950  0x000000c0002ebdd0
0x000000c004605d98:  0x0000000000000000  0x0000000000000000
0x000000c004605da8:  0x0000000000000000  0x0000000000000002
0x000000c004605db8:  0x0000000000000000  0x0000000000000000
0x000000c004605dc8:  0x0000000000000000  0x0000000000000006
0x000000c004605dd8:  0x0000000000000000  0x0000000000000041
0x000000c004605de8:  0x0000000000000000  0x000000000000003f
0x000000c004605df8:  0x0000000000000050  0x0000000000000000
0x000000c004605e08:  0x0000000000000000  0x0000000000000000
0x000000c004605e18:  0x0000000000000000  0x0000000000000000
0x000000c004605e28:  0x0000000000000000  0x000000c00151e380
0x000000c004605e38:  0x000000c0015111e0  0x000000c0002ebdd0
0x000000c004605e48:  0x0000000000000000  0x000000c0013c00a0
0x000000c004605e58:  0x000000c001513880  0x0000000000000000
0x000000c004605e68:  0x0000000000000000  0x000000c0013c00f0
fatal error: unknown caller pc

Stack:

untime stack:
runtime.throw({0x6e16d4?, 0x8d0220?})
        /usr/local/go/src/runtime/panic.go:1047 +0x5d fp=0x7fcc91ffa770 sp=0x7fcc91ffa740 pc=0x436d7d
runtime.gentraceback(0x10?, 0x751070?, 0x0?, 0xc00118fa00, 0x0, 0x0, 0x7fffffff, 0x7fcc91ffaca8, 0x7fcc9062ce18?, 0x0)
        /usr/local/go/src/runtime/traceback.go:270 +0x1bb0 fp=0x7fcc91ffaac8 sp=0x7fcc91ffa770 pc=0x45b4f0
runtime.scanstack(0xc00118fa00, 0xc000029238)
        /usr/local/go/src/runtime/mgcmark.go:804 +0x1f2 fp=0x7fcc91ffacd0 sp=0x7fcc91ffaac8 pc=0x4204f2
runtime.markroot.func1()
        /usr/local/go/src/runtime/mgcmark.go:239 +0xb5 fp=0x7fcc91ffad20 sp=0x7fcc91ffacd0 pc=0x41f2f5
runtime.markroot(0xc000029238, 0x794, 0x1)
        /usr/local/go/src/runtime/mgcmark.go:213 +0x1a5 fp=0x7fcc91ffadc0 sp=0x7fcc91ffad20 pc=0x41efa5
runtime.gcDrain(0xc000029238, 0x3)
        /usr/local/go/src/runtime/mgcmark.go:1069 +0x39f fp=0x7fcc91ffae20 sp=0x7fcc91ffadc0 pc=0x42109f
runtime.gcBgMarkWorker.func2()
        /usr/local/go/src/runtime/mgc.go:1348 +0xad fp=0x7fcc91ffae70 sp=0x7fcc91ffae20 pc=0x41d5ad
runtime.systemstack()
        /usr/local/go/src/runtime/asm_amd64.s:496 +0x49 fp=0x7fcc91ffae78 sp=0x7fcc91ffae70 pc=0x465969

Upgrade to libdeflate 1.18

Please stay up to date with the latest version of libdeflate so that people don't run into issues that have already been fixed. Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.