Skip to content

Performance of gzip compared to node-zopfli #13

@csvn

Description

@csvn

Hello!

Thanks for creating this project. I did read the line (below) from the README.md, and feel free to close this issue if this is not in the scope.

This library is a JavaScript binding to zopfli with WebAssembly. This is slower than native extensions for zopfli, but because wasm is a portable binary format, its installation is much easier than native extensions.

I was using node-zopfli to gzip the files in our dist folder. Due to the hassle of using node-gyp, I decided to switch to this package. I noticed though that for our artifacts, it took roughly 4 times longer to gzip all the files (~6s to ~22s).

Input files gtag.js (65KB) Medium1 (578 KB) Large1 (3.1MB) 8 files2 (3.5MB) 8 build files3 (3.5MB)
node-zopfli (s) 0.55 2.1 8.8 4.5 6
@gfx-zopfli (s) 0.48 2.4 10.0 15.5 22

1 The gtag script was copied until it reached the target size
2 The gtag script was copied into 8 files with sizes varying from 100KB to 1MB
3 Our project has 4 javascript files with their respective .map files, with sizes between 100KB and 1MB

Scripts

Below is the script used to transform files (executed with node --experimental-modules <file>). I also tried using @gfx/zopfli with a stream transform (inpsired by node-zopfli), but it did not seem to affect the speed (I had a theory that streams due to backpressure could be more effective).

node-zopfli

import fs from 'fs';
import glob from 'glob';
import zopfli from 'node-zopfli';
import { join } from 'path';
import { promisify } from 'util';
import { pipeline } from 'stream';


main()
  .then(() => console.log('Files have been successfully gzipped.'))
  .catch(err => console.error(err));

async function main() {
  const distFolder = join(process.cwd(), 'dist');
  const gzipFolder = join(distFolder, 'gzip');

  try {
    await fs.promises.mkdir(gzipFolder, { recursive: true });
  } catch (err) {
    if (err.code !== 'EEXIST') throw err;
  }

  const pipe = promisify(pipeline);
  const files = await promisify(glob)('*!(.d.ts)', { nodir: true, cwd: distFolder });

  await Promise.all(files.map(p => pipe(
    fs.createReadStream(join(distFolder, p)),
    zopfli.createGzip(),
    fs.createWriteStream(join(gzipFolder, p))
  )));
}

@gfx/zopfli

import fs from 'fs';
import glob from 'glob';
import zopfli from '@gfx/zopfli';
import { join } from 'path';
import { promisify } from 'util';


main()
  .then(() => console.log('Files have been successfully gzipped.'))
  .catch(err => console.log(err));

async function main() {
  const distFolder = join(process.cwd(), 'dist');
  const gzipFolder = join(distFolder, 'gzip');

  try {
    await fs.promises.mkdir(gzipFolder, { recursive: true });
  } catch (err) {
    if (err.code !== 'EEXIST') throw err;
  }

  const files = await promisify(glob)('*!(.d.ts)', { nodir: true, cwd: distFolder });

  await Promise.all(files.map(async p => {
    const buffer = await fs.promises.readFile(join(distFolder, p));
    const gzipped = await zopfli.gzipAsync(buffer, {});
    await fs.promises.writeFile(join(gzipFolder, p), gzipped);
  }));
}

@gfx/zopfli (transform stream)

import fs from 'fs';
import glob from 'glob';
import zopfli from '@gfx/zopfli';
import { join } from 'path';
import { promisify } from 'util';
import { Transform, pipeline } from 'stream';


main()
  .then(() => console.log('Files have been successfully gzipped.'))
  .catch(err => console.error(err));

async function main() {
  const distFolder = join(process.cwd(), 'dist');
  const gzipFolder = join(distFolder, 'gzip');

  try {
    await fs.promises.mkdir(gzipFolder, { recursive: true });
  } catch (err) {
    if (err.code !== 'EEXIST') throw err;
  }

  const pipe = promisify(pipeline);
  const files = await promisify(glob)('*!(.d.ts)', { nodir: true, cwd: distFolder });

  await Promise.all(files.map(p => pipe(
    fs.createReadStream(join(distFolder, p)),
    new Gzip(),
    fs.createWriteStream(join(gzipFolder, p))
  )));
}

class Gzip extends Transform {
  constructor() {
    super();
    this.in = new Buffer.alloc(0);
  }

  _transform(chunk, encoding, done) {
    this.in = Buffer.concat([this.in, chunk]);
    done();
  }

  _flush(done) {
    zopfli.gzip(this.in, {}, (err, out) => {
      if (err) {
        done(err);
      } else {
        this.push(out);
        done();
      }
    });
  }
}

Is .wasm the reason for the slowdown?
Is it within reason that @gfx/zopfli is sometimes ~4x slower than using node-zopfli?
Or maybe I've done something weird in the script?

@gfx/zopfli version: 1.0.13

Edit: Added version info

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions