Commit af2e2f32 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull squashfs updates from Phillip Lougher:
 "These patches optionally improve the multi-threading peformance of
  Squashfs by adding parallel decompression, and direct decompression
  into the page cache, eliminating an intermediate buffer (removing
  memcpy overhead and lock contention)"

* tag 'squashfs-updates' of git://git.kernel.org/pub/scm/linux/kernel/git/pkl/squashfs-next:
  Squashfs: Check stream is not NULL in decompressor_multi.c
  Squashfs: Directly decompress into the page cache for file data
  Squashfs: Restructure squashfs_readpage()
  Squashfs: Generalise paging handling in the decompressors
  Squashfs: add multi-threaded decompression using percpu variable
  squashfs: Enhance parallel I/O
  Squashfs: Refactor decompressor interface and code
parents 8b2e9b71 ed4f381e
Loading
Loading
Loading
Loading
+72 −0
Original line number Diff line number Diff line
@@ -25,6 +25,78 @@ config SQUASHFS

	  If unsure, say N.

choice
	prompt "File decompression options"
	depends on SQUASHFS
	help
	  Squashfs now supports two options for decompressing file
	  data.  Traditionally Squashfs has decompressed into an
	  intermediate buffer and then memcopied it into the page cache.
	  Squashfs now supports the ability to decompress directly into
	  the page cache.

	  If unsure, select "Decompress file data into an intermediate buffer"

config SQUASHFS_FILE_CACHE
	bool "Decompress file data into an intermediate buffer"
	help
	  Decompress file data into an intermediate buffer and then
	  memcopy it into the page cache.

config SQUASHFS_FILE_DIRECT
	bool "Decompress files directly into the page cache"
	help
	  Directly decompress file data into the page cache.
	  Doing so can significantly improve performance because
	  it eliminates a memcpy and it also removes the lock contention
	  on the single buffer.

endchoice

choice
	prompt "Decompressor parallelisation options"
	depends on SQUASHFS
	help
	  Squashfs now supports three parallelisation options for
	  decompression.  Each one exhibits various trade-offs between
	  decompression performance and CPU and memory usage.

	  If in doubt, select "Single threaded compression"

config SQUASHFS_DECOMP_SINGLE
	bool "Single threaded compression"
	help
	  Traditionally Squashfs has used single-threaded decompression.
	  Only one block (data or metadata) can be decompressed at any
	  one time.  This limits CPU and memory usage to a minimum.

config SQUASHFS_DECOMP_MULTI
	bool "Use multiple decompressors for parallel I/O"
	help
	  By default Squashfs uses a single decompressor but it gives
	  poor performance on parallel I/O workloads when using multiple CPU
	  machines due to waiting on decompressor availability.

	  If you have a parallel I/O workload and your system has enough memory,
	  using this option may improve overall I/O performance.

	  This decompressor implementation uses up to two parallel
	  decompressors per core.  It dynamically allocates decompressors
	  on a demand basis.

config SQUASHFS_DECOMP_MULTI_PERCPU
	bool "Use percpu multiple decompressors for parallel I/O"
	help
	  By default Squashfs uses a single decompressor but it gives
	  poor performance on parallel I/O workloads when using multiple CPU
	  machines due to waiting on decompressor availability.

	  This decompressor implementation uses a maximum of one
	  decompressor per core.  It uses percpu variables to ensure
	  decompression is load-balanced across the cores.

endchoice

config SQUASHFS_XATTR
	bool "Squashfs XATTR support"
	depends on SQUASHFS
+5 −0
Original line number Diff line number Diff line
@@ -5,6 +5,11 @@
obj-$(CONFIG_SQUASHFS) += squashfs.o
squashfs-y += block.o cache.o dir.o export.o file.o fragment.o id.o inode.o
squashfs-y += namei.o super.o symlink.o decompressor.o
squashfs-$(CONFIG_SQUASHFS_FILE_CACHE) += file_cache.o
squashfs-$(CONFIG_SQUASHFS_FILE_DIRECT) += file_direct.o page_actor.o
squashfs-$(CONFIG_SQUASHFS_DECOMP_SINGLE) += decompressor_single.o
squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI) += decompressor_multi.o
squashfs-$(CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU) += decompressor_multi_percpu.o
squashfs-$(CONFIG_SQUASHFS_XATTR) += xattr.o xattr_id.o
squashfs-$(CONFIG_SQUASHFS_LZO) += lzo_wrapper.o
squashfs-$(CONFIG_SQUASHFS_XZ) += xz_wrapper.o
+21 −15
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@
#include "squashfs_fs_sb.h"
#include "squashfs.h"
#include "decompressor.h"
#include "page_actor.h"

/*
 * Read the metadata block length, this is stored in the first two
@@ -86,16 +87,16 @@ static struct buffer_head *get_block_length(struct super_block *sb,
 * generated a larger block - this does occasionally happen with compression
 * algorithms).
 */
int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
			int length, u64 *next_index, int srclength, int pages)
int squashfs_read_data(struct super_block *sb, u64 index, int length,
		u64 *next_index, struct squashfs_page_actor *output)
{
	struct squashfs_sb_info *msblk = sb->s_fs_info;
	struct buffer_head **bh;
	int offset = index & ((1 << msblk->devblksize_log2) - 1);
	u64 cur_index = index >> msblk->devblksize_log2;
	int bytes, compressed, b = 0, k = 0, page = 0, avail;
	int bytes, compressed, b = 0, k = 0, avail, i;

	bh = kcalloc(((srclength + msblk->devblksize - 1)
	bh = kcalloc(((output->length + msblk->devblksize - 1)
		>> msblk->devblksize_log2) + 1, sizeof(*bh), GFP_KERNEL);
	if (bh == NULL)
		return -ENOMEM;
@@ -111,9 +112,9 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
			*next_index = index + length;

		TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n",
			index, compressed ? "" : "un", length, srclength);
			index, compressed ? "" : "un", length, output->length);

		if (length < 0 || length > srclength ||
		if (length < 0 || length > output->length ||
				(index + length) > msblk->bytes_used)
			goto read_failure;

@@ -145,7 +146,7 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
		TRACE("Block @ 0x%llx, %scompressed size %d\n", index,
				compressed ? "" : "un", length);

		if (length < 0 || length > srclength ||
		if (length < 0 || length > output->length ||
					(index + length) > msblk->bytes_used)
			goto block_release;

@@ -158,9 +159,15 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
		ll_rw_block(READ, b - 1, bh + 1);
	}

	for (i = 0; i < b; i++) {
		wait_on_buffer(bh[i]);
		if (!buffer_uptodate(bh[i]))
			goto block_release;
	}

	if (compressed) {
		length = squashfs_decompress(msblk, buffer, bh, b, offset,
			 length, srclength, pages);
		length = squashfs_decompress(msblk, bh, b, offset, length,
			output);
		if (length < 0)
			goto read_failure;
	} else {
@@ -168,22 +175,20 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
		 * Block is uncompressed.
		 */
		int in, pg_offset = 0;
		void *data = squashfs_first_page(output);

		for (bytes = length; k < b; k++) {
			in = min(bytes, msblk->devblksize - offset);
			bytes -= in;
			wait_on_buffer(bh[k]);
			if (!buffer_uptodate(bh[k]))
				goto block_release;
			while (in) {
				if (pg_offset == PAGE_CACHE_SIZE) {
					page++;
					data = squashfs_next_page(output);
					pg_offset = 0;
				}
				avail = min_t(int, in, PAGE_CACHE_SIZE -
						pg_offset);
				memcpy(buffer[page] + pg_offset,
						bh[k]->b_data + offset, avail);
				memcpy(data + pg_offset, bh[k]->b_data + offset,
						avail);
				in -= avail;
				pg_offset += avail;
				offset += avail;
@@ -191,6 +196,7 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
			offset = 0;
			put_bh(bh[k]);
		}
		squashfs_finish_page(output);
	}

	kfree(bh);
+23 −5
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs.h"
#include "page_actor.h"

/*
 * Look-up block in cache, and increment usage count.  If not in cache, read
@@ -119,9 +120,8 @@ struct squashfs_cache_entry *squashfs_cache_get(struct super_block *sb,
			entry->error = 0;
			spin_unlock(&cache->lock);

			entry->length = squashfs_read_data(sb, entry->data,
				block, length, &entry->next_index,
				cache->block_size, cache->pages);
			entry->length = squashfs_read_data(sb, block, length,
				&entry->next_index, entry->actor);

			spin_lock(&cache->lock);

@@ -220,6 +220,7 @@ void squashfs_cache_delete(struct squashfs_cache *cache)
				kfree(cache->entry[i].data[j]);
			kfree(cache->entry[i].data);
		}
		kfree(cache->entry[i].actor);
	}

	kfree(cache->entry);
@@ -280,6 +281,13 @@ struct squashfs_cache *squashfs_cache_init(char *name, int entries,
				goto cleanup;
			}
		}

		entry->actor = squashfs_page_actor_init(entry->data,
						cache->pages, 0);
		if (entry->actor == NULL) {
			ERROR("Failed to allocate %s cache entry\n", name);
			goto cleanup;
		}
	}

	return cache;
@@ -410,6 +418,7 @@ void *squashfs_read_table(struct super_block *sb, u64 block, int length)
	int pages = (length + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
	int i, res;
	void *table, *buffer, **data;
	struct squashfs_page_actor *actor;

	table = buffer = kmalloc(length, GFP_KERNEL);
	if (table == NULL)
@@ -421,19 +430,28 @@ void *squashfs_read_table(struct super_block *sb, u64 block, int length)
		goto failed;
	}

	actor = squashfs_page_actor_init(data, pages, length);
	if (actor == NULL) {
		res = -ENOMEM;
		goto failed2;
	}

	for (i = 0; i < pages; i++, buffer += PAGE_CACHE_SIZE)
		data[i] = buffer;

	res = squashfs_read_data(sb, data, block, length |
		SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, length, pages);
	res = squashfs_read_data(sb, block, length |
		SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, actor);

	kfree(data);
	kfree(actor);

	if (res < 0)
		goto failed;

	return table;

failed2:
	kfree(data);
failed:
	kfree(table);
	return ERR_PTR(res);
+42 −17
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
#include "squashfs_fs_sb.h"
#include "decompressor.h"
#include "squashfs.h"
#include "page_actor.h"

/*
 * This file (and decompressor.h) implements a decompressor framework for
@@ -37,29 +38,29 @@
 */

static const struct squashfs_decompressor squashfs_lzma_unsupported_comp_ops = {
	NULL, NULL, NULL, LZMA_COMPRESSION, "lzma", 0
	NULL, NULL, NULL, NULL, LZMA_COMPRESSION, "lzma", 0
};

#ifndef CONFIG_SQUASHFS_LZO
static const struct squashfs_decompressor squashfs_lzo_comp_ops = {
	NULL, NULL, NULL, LZO_COMPRESSION, "lzo", 0
	NULL, NULL, NULL, NULL, LZO_COMPRESSION, "lzo", 0
};
#endif

#ifndef CONFIG_SQUASHFS_XZ
static const struct squashfs_decompressor squashfs_xz_comp_ops = {
	NULL, NULL, NULL, XZ_COMPRESSION, "xz", 0
	NULL, NULL, NULL, NULL, XZ_COMPRESSION, "xz", 0
};
#endif

#ifndef CONFIG_SQUASHFS_ZLIB
static const struct squashfs_decompressor squashfs_zlib_comp_ops = {
	NULL, NULL, NULL, ZLIB_COMPRESSION, "zlib", 0
	NULL, NULL, NULL, NULL, ZLIB_COMPRESSION, "zlib", 0
};
#endif

static const struct squashfs_decompressor squashfs_unknown_comp_ops = {
	NULL, NULL, NULL, 0, "unknown", 0
	NULL, NULL, NULL, NULL, 0, "unknown", 0
};

static const struct squashfs_decompressor *decompressor[] = {
@@ -83,10 +84,11 @@ const struct squashfs_decompressor *squashfs_lookup_decompressor(int id)
}


void *squashfs_decompressor_init(struct super_block *sb, unsigned short flags)
static void *get_comp_opts(struct super_block *sb, unsigned short flags)
{
	struct squashfs_sb_info *msblk = sb->s_fs_info;
	void *strm, *buffer = NULL;
	void *buffer = NULL, *comp_opts;
	struct squashfs_page_actor *actor = NULL;
	int length = 0;

	/*
@@ -94,23 +96,46 @@ void *squashfs_decompressor_init(struct super_block *sb, unsigned short flags)
	 */
	if (SQUASHFS_COMP_OPTS(flags)) {
		buffer = kmalloc(PAGE_CACHE_SIZE, GFP_KERNEL);
		if (buffer == NULL)
			return ERR_PTR(-ENOMEM);
		if (buffer == NULL) {
			comp_opts = ERR_PTR(-ENOMEM);
			goto out;
		}

		actor = squashfs_page_actor_init(&buffer, 1, 0);
		if (actor == NULL) {
			comp_opts = ERR_PTR(-ENOMEM);
			goto out;
		}

		length = squashfs_read_data(sb, &buffer,
			sizeof(struct squashfs_super_block), 0, NULL,
			PAGE_CACHE_SIZE, 1);
		length = squashfs_read_data(sb,
			sizeof(struct squashfs_super_block), 0, NULL, actor);

		if (length < 0) {
			strm = ERR_PTR(length);
			goto finished;
			comp_opts = ERR_PTR(length);
			goto out;
		}
	}

	strm = msblk->decompressor->init(msblk, buffer, length);
	comp_opts = squashfs_comp_opts(msblk, buffer, length);

finished:
out:
	kfree(actor);
	kfree(buffer);
	return comp_opts;
}


void *squashfs_decompressor_setup(struct super_block *sb, unsigned short flags)
{
	struct squashfs_sb_info *msblk = sb->s_fs_info;
	void *stream, *comp_opts = get_comp_opts(sb, flags);

	if (IS_ERR(comp_opts))
		return comp_opts;

	stream = squashfs_decompressor_create(msblk, comp_opts);
	if (IS_ERR(stream))
		kfree(comp_opts);

	return strm;
	return stream;
}
Loading