Further performance improvements
Further removed multiple pointer instances Re-use decompression readers (except zstd due to bugs)
This commit is contained in:
@@ -28,15 +28,14 @@ As of `v1.0`, FUSE capabilities has been moved to [a separate library](https://g
|
|||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
* Significantly slower then `unsquashfs` when nested images
|
* Noticably slower then `unsquashfs` for extraction, especially on larger images.
|
||||||
* This seems to be related to above along with the general optimization of `unsquashfs` and it's compression libraries.
|
* This seems to be related to above along with the general optimization of `unsquashfs` and it's compression libraries.
|
||||||
* Not to mention it's written in C
|
|
||||||
* Times seem to be largely dependent on file tree size and compression type.
|
* Times seem to be largely dependent on file tree size and compression type.
|
||||||
* My main testing image (~100MB) using Zstd takes about 5x longer.
|
* My main testing image (~100MB) using Zstd takes ~2x longer.
|
||||||
* An Arch Linux airootfs image (~780MB) using XZ compression with LZMA filters takes about 30x longer.
|
* An Arch Linux airootfs image (~780MB) using XZ compression with LZMA filters takes ~28x longer.
|
||||||
* A Tensorflow docker image (~3.3GB) using Zstd takes about 12x longer.
|
* A Tensorflow docker image (~3.3GB) using Zstd takes ~3x longer.
|
||||||
|
|
||||||
Note: These numbers are using `FastOptions()`. `DefaultOptions()` takes about 2x longer.
|
Note: These numbers are using `FastOptions()`. `DefaultOptions()` takes ~2x longer.
|
||||||
|
|
||||||
## Recommendations on Usage
|
## Recommendations on Usage
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,28 @@ package decompress
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/pierrec/lz4/v4"
|
"github.com/pierrec/lz4/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Lz4 struct{}
|
type Lz4 struct {
|
||||||
|
pool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
func (l Lz4) Decompress(data []byte) ([]byte, error) {
|
func NewLz4() *Lz4 {
|
||||||
rdr := lz4.NewReader(bytes.NewReader(data))
|
return &Lz4{
|
||||||
|
pool: sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return lz4.NewReader(nil)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lz4) Decompress(data []byte) ([]byte, error) {
|
||||||
|
rdr := l.pool.Get().(*lz4.Reader)
|
||||||
|
defer l.pool.Put(rdr)
|
||||||
|
rdr.Reset(bytes.NewReader(data))
|
||||||
return io.ReadAll(rdr)
|
return io.ReadAll(rdr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,30 @@ package decompress
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/therootcompany/xz"
|
"github.com/therootcompany/xz"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Xz struct{}
|
type Xz struct {
|
||||||
|
pool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
func (x Xz) Decompress(data []byte) ([]byte, error) {
|
func NewXz() *Xz {
|
||||||
rdr, err := xz.NewReader(bytes.NewReader(data), 0)
|
return &Xz{
|
||||||
|
pool: sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
rdr, _ := xz.NewReader(nil, 0)
|
||||||
|
return rdr
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xz) Decompress(data []byte) ([]byte, error) {
|
||||||
|
rdr := x.pool.Get().(*xz.Reader)
|
||||||
|
defer x.pool.Put(rdr)
|
||||||
|
err := rdr.Reset(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
package decompress
|
package decompress
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Zstd struct{}
|
type Zstd struct{}
|
||||||
|
|
||||||
func (z Zstd) Decompress(data []byte) ([]byte, error) {
|
func (z Zstd) Decompress(data []byte) ([]byte, error) {
|
||||||
rdr, err := zstd.NewReader(bytes.NewReader(data))
|
rdr, err := zstd.NewReader(nil, zstd.WithDecoderLowmem(true), zstd.WithDecoderConcurrency(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rdr.Close()
|
defer rdr.Close()
|
||||||
return io.ReadAll(rdr)
|
return rdr.DecodeAll(data, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ type Reader struct {
|
|||||||
curOffset uint16
|
curOffset uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReader(r io.Reader, d decompress.Decompressor) *Reader {
|
func NewReader(r io.Reader, d decompress.Decompressor) Reader {
|
||||||
return &Reader{
|
return Reader{
|
||||||
r: r,
|
r: r,
|
||||||
d: d,
|
d: d,
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ func (r Reader) directoryFromRef(ref uint64, name string) (Directory, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return Directory{}, err
|
return Directory{}, err
|
||||||
}
|
}
|
||||||
entries, err := directory.ReadDirectory(dirRdr, size)
|
entries, err := directory.ReadDirectory(&dirRdr, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Directory{}, err
|
return Directory{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -70,7 +70,7 @@ func (b FileBase) ToDir(r Reader) (Directory, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return Directory{}, err
|
return Directory{}, err
|
||||||
}
|
}
|
||||||
entries, err := directory.ReadDirectory(dirRdr, size)
|
entries, err := directory.ReadDirectory(&dirRdr, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Directory{}, err
|
return Directory{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -15,12 +15,12 @@ func (r Reader) InodeFromRef(ref uint64) (inode.Inode, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return inode.Inode{}, err
|
return inode.Inode{}, err
|
||||||
}
|
}
|
||||||
return inode.Read(rdr, r.Superblock.BlockSize)
|
return inode.Read(&rdr, r.Superblock.BlockSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Reader) InodeFromEntry(e directory.Entry) (inode.Inode, error) {
|
func (r Reader) InodeFromEntry(e directory.Entry) (inode.Inode, error) {
|
||||||
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.InodeTableStart)+int64(e.BlockStart)), r.d)
|
rdr := metadata.NewReader(toreader.NewReader(r.r, int64(r.Superblock.InodeTableStart)+int64(e.BlockStart)), r.d)
|
||||||
defer rdr.Close()
|
defer rdr.Close()
|
||||||
rdr.Read(make([]byte, e.Offset))
|
rdr.Read(make([]byte, e.Offset))
|
||||||
return inode.Read(rdr, r.Superblock.BlockSize)
|
return inode.Read(&rdr, r.Superblock.BlockSize)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-8
@@ -68,9 +68,9 @@ func NewReader(r io.ReaderAt) (rdr Reader, err error) {
|
|||||||
return rdr, err
|
return rdr, err
|
||||||
}
|
}
|
||||||
case XZCompression:
|
case XZCompression:
|
||||||
rdr.d = decompress.Xz{}
|
rdr.d = decompress.NewXz()
|
||||||
case LZ4Compression:
|
case LZ4Compression:
|
||||||
rdr.d = decompress.Lz4{}
|
rdr.d = decompress.NewLz4()
|
||||||
case ZSTDCompression:
|
case ZSTDCompression:
|
||||||
rdr.d = decompress.Zstd{}
|
rdr.d = decompress.Zstd{}
|
||||||
default:
|
default:
|
||||||
@@ -104,7 +104,8 @@ func (r *Reader) Id(i uint16) (uint32, error) {
|
|||||||
var idsToRead uint16
|
var idsToRead uint16
|
||||||
var idsTmp []uint32
|
var idsTmp []uint32
|
||||||
var err error
|
var err error
|
||||||
var rdr *metadata.Reader
|
var rdr metadata.Reader
|
||||||
|
// We can *maybe* have a slight speed increase by manually decoding instead of using reflection via binary.Read
|
||||||
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
|
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
|
||||||
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.IdTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.IdTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,7 +114,7 @@ func (r *Reader) Id(i uint16) (uint32, error) {
|
|||||||
idsToRead = min(r.Superblock.IdCount-uint16(len(r.idTable)), 2048)
|
idsToRead = min(r.Superblock.IdCount-uint16(len(r.idTable)), 2048)
|
||||||
idsTmp = make([]uint32, idsToRead)
|
idsTmp = make([]uint32, idsToRead)
|
||||||
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||||
err = binary.Read(rdr, binary.LittleEndian, &idsTmp)
|
err = binary.Read(&rdr, binary.LittleEndian, &idsTmp)
|
||||||
rdr.Close()
|
rdr.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -144,7 +145,8 @@ func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
|
|||||||
var fragsToRead uint32
|
var fragsToRead uint32
|
||||||
var fragsTmp []fragEntry
|
var fragsTmp []fragEntry
|
||||||
var err error
|
var err error
|
||||||
var rdr *metadata.Reader
|
var rdr metadata.Reader
|
||||||
|
// We can *maybe* have a slight speed increase by manually decoding instead of using reflection via binary.Read
|
||||||
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
|
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
|
||||||
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.FragTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.FragTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -153,7 +155,7 @@ func (r *Reader) fragEntry(i uint32) (fragEntry, error) {
|
|||||||
fragsToRead = min(r.Superblock.FragCount-uint32(len(r.fragTable)), 512)
|
fragsToRead = min(r.Superblock.FragCount-uint32(len(r.fragTable)), 512)
|
||||||
fragsTmp = make([]fragEntry, fragsToRead)
|
fragsTmp = make([]fragEntry, fragsToRead)
|
||||||
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||||
err = binary.Read(rdr, binary.LittleEndian, &fragsTmp)
|
err = binary.Read(&rdr, binary.LittleEndian, &fragsTmp)
|
||||||
rdr.Close()
|
rdr.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fragEntry{}, err
|
return fragEntry{}, err
|
||||||
@@ -187,7 +189,8 @@ func (r *Reader) inodeRef(i uint32) (uint64, error) {
|
|||||||
var refsToRead uint32
|
var refsToRead uint32
|
||||||
var refsTmp []uint64
|
var refsTmp []uint64
|
||||||
var err error
|
var err error
|
||||||
var rdr *metadata.Reader
|
var rdr metadata.Reader
|
||||||
|
// We can *maybe* have a slight speed increase by manually decoding instead of using reflection via binary.Read
|
||||||
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
|
for i := blocksRead; i < int(blocksRead)+blocksToRead; i++ {
|
||||||
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.ExportTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
err = binary.Read(toreader.NewReader(r.r, int64(r.Superblock.ExportTableStart)+int64(8*i)), binary.LittleEndian, &offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -196,7 +199,7 @@ func (r *Reader) inodeRef(i uint32) (uint64, error) {
|
|||||||
refsToRead = min(r.Superblock.InodeCount-uint32(len(r.exportTable)), 1024)
|
refsToRead = min(r.Superblock.InodeCount-uint32(len(r.exportTable)), 1024)
|
||||||
refsTmp = make([]uint64, refsToRead)
|
refsTmp = make([]uint64, refsToRead)
|
||||||
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
rdr = metadata.NewReader(toreader.NewReader(r.r, int64(offset)), r.d)
|
||||||
err = binary.Read(rdr, binary.LittleEndian, &refsTmp)
|
err = binary.Read(&rdr, binary.LittleEndian, &refsTmp)
|
||||||
rdr.Close()
|
rdr.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
Binary file not shown.
+5
-5
@@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
|
squashfsURL = "https://darkstorm.tech/files/LinuxPATest.sfs"
|
||||||
squashfsName = "LinuxPATest.sfs"
|
squashfsName = "tensorflow.sqfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func preTest(dir string) (fil *os.File, err error) {
|
func preTest(dir string) (fil *os.File, err error) {
|
||||||
@@ -111,7 +111,7 @@ func BenchmarkRace(b *testing.B) {
|
|||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
libTime = time.Since(start)
|
libTime = time.Since(start)
|
||||||
cmd := exec.Command("unsquashfs", "-d", unsquashPath, fil.Name())
|
cmd := exec.Command("unsquashfs", "-q", "-d", unsquashPath, fil.Name())
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
@@ -187,7 +187,7 @@ func TestExtractQuick(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var filePath = "Start.exe"
|
var filePath = "usr/sbin/add-shell"
|
||||||
|
|
||||||
func TestSingleFile(t *testing.T) {
|
func TestSingleFile(t *testing.T) {
|
||||||
tmpDir := "testing"
|
tmpDir := "testing"
|
||||||
@@ -195,7 +195,7 @@ func TestSingleFile(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
os.Remove(filepath.Join(tmpDir, filePath))
|
os.RemoveAll("testing/stuff")
|
||||||
rdr, err := NewReader(fil)
|
rdr, err := NewReader(fil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -206,7 +206,7 @@ func TestSingleFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
op := DefaultOptions()
|
op := DefaultOptions()
|
||||||
op.Verbose = true
|
op.Verbose = true
|
||||||
err = f.(*File).ExtractWithOptions("testing", op)
|
err = f.(*File).ExtractWithOptions("testing/stuff", op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user