linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/8] zswap: compressed swap caching
@ 2012-12-11 21:55 Seth Jennings
  2012-12-11 21:55 ` [PATCH 1/8] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
                   ` (11 more replies)
  0 siblings, 12 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:55 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

Zswap Overview:

Zswap is a lightweight compressed cache for swap pages. It takes
pages that are in the process of being swapped out and attempts to
compress them into a dynamically allocated RAM-based memory pool.
If this process is successful, the writeback to the swap device is
deferred and, in many cases, avoided completely.  This results in
a significant I/O reduction and performance gains for systems that
are swapping. The results of a kernel building benchmark indicate a
runtime reduction of 53% and an I/O reduction 76% with zswap vs normal
swapping with a kernel build under heavy memory pressure (see
Performance section for more).

Patchset Structure:
1-4: improvements/changes to zsmalloc
5:   add atomic_t get/set to debugfs
6:   promote zsmalloc to /lib
7-8: add zswap and documentation

Targeting this for linux-next.

Rationale:

Zswap provides compressed swap caching that basically trades CPU cycles
for reduced swap I/O.  This trade-off can result in a significant
performance improvement as reads to/writes from to the compressed
cache almost always faster that reading from a swap device
which incurs the latency of an asynchronous block I/O read.

Some potential benefits:
* Desktop/laptop users with limited RAM capacities can mitigate the
    performance impact of swapping.
* Overcommitted guests that share a common I/O resource can
    dramatically reduce their swap I/O pressure, avoiding heavy
    handed I/O throttling by the hypervisor.  This allows more work
    to get done with less impact to the guest workload and guests
    sharing the I/O subsystem
* Users with SSDs as swap devices can extend the life of the device by
    drastically reducing life-shortening writes.

Zswap evicts pages from compressed cache on an LRU basis to the backing
swap device when the compress pool reaches it size limit or the pool is
unable to obtain additional pages from the buddy allocator.  This
requirement had been identified in prior community discussions.

Compressed swap is also provided in zcache, along with page cache
compression and RAM clustering through RAMSter. Zswap seeks to deliver
the benefit of swap  compression to users in a discrete function.
This design decision is akin to Unix design philosophy of doing one
thing well, it leaves file cache compression and other features
for separate code.

Design:

Zswap receives pages for compression through the Frontswap API and
is able to evict pages from its own compressed pool on an LRU basis
and write them back to the backing swap device in the case that the
compressed pool is full or unable to secure additional pages from
the buddy allocator.

Zswap makes use of zsmalloc for the managing the compressed memory
pool.  This is because zsmalloc is specifically designed to minimize
fragmentation on large (> PAGE_SIZE/2) allocation sizes.  Each
allocation in zsmalloc is not directly accessible by address.
Rather, a handle is return by the allocation routine and that handle
must be mapped before being accessed.  The compressed memory pool grows
on demand and shrinks as compressed pages are freed.  The pool is
not preallocated.

When a swap page is passed from frontswap to zswap, zswap maintains
a mapping of the swap entry, a combination of the swap type and swap
offset, to the zsmalloc handle that references that compressed swap
page.  This mapping is achieved with a red-black tree per swap type.
The swap offset is the search key for the tree nodes.

Zswap seeks to be simple in its policies.  Sysfs attributes allow for
two user controlled policies:
* max_compression_ratio - Maximum compression ratio, as as percentage,
    for an acceptable compressed page. Any page that does not compress
    by at least this ratio will be rejected.
* max_pool_percent - The maximum percentage of memory that the compressed
    pool can occupy.

To enabled zswap, the "enabled" attribute must be set to 1 at boot time.

Zswap allows the compressor to be selected at kernel boot time by
setting the “compressor” attribute.  The default compressor is lzo.

A debugfs interface is provided for various statistic about pool size,
number of pages stored, and various counters for the reasons pages
are rejected.

Performance, Kernel Building:

Setup
========
Gentoo w/ kernel v3.7-rc7
Quad-core i5-2500 @ 3.3GHz
512MB DDR3 1600MHz (limited with mem=512m on boot)
Filesystem and swap on 80GB HDD (about 58MB/s with hdparm -t)
majflt are major page faults reported by the time command
pswpin/out is the delta of pswpin/out from /proc/vmstat before and after
the make -jN

Summary
========
* Zswap reduces I/O and improves performance at all swap pressure levels.

* Under heavy swaping at 24 threads, zswap reduced I/O by 76%, saving
  over 1.5GB of I/O, and cut runtime in half.

Details
========
I/O (in pages)
	base				zswap				change	change
N	pswpin	pswpout	majflt	I/O sum	pswpin	pswpout	majflt	I/O sum	%I/O	MB
8	1	335	291	627	0	0	249	249	-60%	1
12	3688	14315	5290	23293	123	860	5954	6937	-70%	64
16	12711	46179	16803	75693	2936	7390	46092	56418	-25%	75
20	42178	133781	49898	225857	9460	28382	92951	130793	-42%	371
24	96079	357280	105242	558601	7719	18484	109309	135512	-76%	1653

Runtime (in seconds)
N	base	zswap	%change
8	107	107	0%
12	128	110	-14%
16	191	179	-6%
20	371	240	-35%
24	570	267	-53%

%CPU utilization (out of 400% on 4 cpus)
N	base	zswap	%change
8	317	319	1%
12	267	311	16%
16	179	191	7%
20	94	143	52%
24	60	128	113%

Patchset is based on next-20121210

Seth Jennings (8):
  staging: zsmalloc: add gfp flags to zs_create_pool
  staging: zsmalloc: remove unsed pool name
  staging: zsmalloc: add page alloc/free callbacks
  staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
  debugfs: add get/set for atomic types
  zsmalloc: promote to lib/
  zswap: add to mm/
  zswap: add documentation

 Documentation/vm/zswap.txt               |   74 ++
 drivers/staging/Kconfig                  |    2 -
 drivers/staging/Makefile                 |    1 -
 drivers/staging/zcache/zcache-main.c     |    7 +-
 drivers/staging/zram/zram_drv.c          |    4 +-
 drivers/staging/zram/zram_drv.h          |    3 +-
 drivers/staging/zsmalloc/Kconfig         |   10 -
 drivers/staging/zsmalloc/Makefile        |    3 -
 drivers/staging/zsmalloc/zsmalloc-main.c | 1064 -----------------------------
 drivers/staging/zsmalloc/zsmalloc.h      |   43 --
 fs/debugfs/file.c                        |   42 ++
 include/linux/debugfs.h                  |    2 +
 include/linux/swap.h                     |    4 +
 include/linux/zsmalloc.h                 |   49 ++
 lib/Kconfig                              |   18 +
 lib/Makefile                             |    1 +
 lib/zsmalloc.c                           | 1076 +++++++++++++++++++++++++++++
 mm/Kconfig                               |   15 +
 mm/Makefile                              |    1 +
 mm/page_io.c                             |   22 +-
 mm/swap_state.c                          |    2 +-
 mm/zswap.c                               | 1077 ++++++++++++++++++++++++++++++
 22 files changed, 2383 insertions(+), 1137 deletions(-)
 create mode 100644 Documentation/vm/zswap.txt
 delete mode 100644 drivers/staging/zsmalloc/Kconfig
 delete mode 100644 drivers/staging/zsmalloc/Makefile
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc.h
 create mode 100644 include/linux/zsmalloc.h
 create mode 100644 lib/zsmalloc.c
 create mode 100644 mm/zswap.c

-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH 1/8] staging: zsmalloc: add gfp flags to zs_create_pool
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
@ 2012-12-11 21:55 ` Seth Jennings
  2012-12-11 21:56 ` [PATCH 2/8] staging: zsmalloc: remove unsed pool name Seth Jennings
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:55 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

zs_create_pool() currently takes a gfp flags argument
that is used when growing the memory pool.  However
it is not used in allocating the metadata for the pool
itself.  That is currently hardcoded to GFP_KERNEL.

zswap calls zs_create_pool() at swapon time which is done
in atomic context, resulting in a "might sleep" warning.

This patch changes the meaning of the flags argument in
zs_create_pool() to mean the flags for the metadata allocation,
and adds a flags argument to zs_malloc that will be used for
memory pool growth if required.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zcache/zcache-main.c     |    4 ++--
 drivers/staging/zram/zram_drv.c          |    4 ++--
 drivers/staging/zsmalloc/zsmalloc-main.c |    9 +++------
 drivers/staging/zsmalloc/zsmalloc.h      |    2 +-
 4 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index 52b43b7..674c754 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -711,7 +711,7 @@ static unsigned long zv_create(struct zs_pool *pool, uint32_t pool_id,
 
 	BUG_ON(!irqs_disabled());
 	BUG_ON(chunks >= NCHUNKS);
-	handle = zs_malloc(pool, size);
+	handle = zs_malloc(pool, size, ZCACHE_GFP_MASK);
 	if (!handle)
 		goto out;
 	atomic_inc(&zv_curr_dist_counts[chunks]);
@@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
 		goto out;
 	cli->allocated = 1;
 #ifdef CONFIG_FRONTSWAP
-	cli->zspool = zs_create_pool("zcache", ZCACHE_GFP_MASK);
+	cli->zspool = zs_create_pool("zcache", GFP_KERNEL);
 	if (cli->zspool == NULL)
 		goto out;
 	idr_init(&cli->tmem_pools);
diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
index fb4a7c9..13e9b4b 100644
--- a/drivers/staging/zram/zram_drv.c
+++ b/drivers/staging/zram/zram_drv.c
@@ -336,7 +336,7 @@ static int zram_bvec_write(struct zram *zram, struct bio_vec *bvec, u32 index,
 		clen = PAGE_SIZE;
 	}
 
-	handle = zs_malloc(zram->mem_pool, clen);
+	handle = zs_malloc(zram->mem_pool, clen, GFP_NOIO | __GFP_HIGHMEM);
 	if (!handle) {
 		pr_info("Error allocating memory for compressed "
 			"page: %u, size=%zu\n", index, clen);
@@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
 	/* zram devices sort of resembles non-rotational disks */
 	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
 
-	zram->mem_pool = zs_create_pool("zram", GFP_NOIO | __GFP_HIGHMEM);
+	zram->mem_pool = zs_create_pool("zram", GFP_KERNEL);
 	if (!zram->mem_pool) {
 		pr_err("Error creating memory pool\n");
 		ret = -ENOMEM;
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 09a9d35..6ff380e 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -205,8 +205,6 @@ struct link_free {
 
 struct zs_pool {
 	struct size_class size_class[ZS_SIZE_CLASSES];
-
-	gfp_t flags;	/* allocation flags used when growing pool */
 	const char *name;
 };
 
@@ -807,7 +805,7 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
 		return NULL;
 
 	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
-	pool = kzalloc(ovhd_size, GFP_KERNEL);
+	pool = kzalloc(ovhd_size, flags);
 	if (!pool)
 		return NULL;
 
@@ -827,7 +825,6 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
 
 	}
 
-	pool->flags = flags;
 	pool->name = name;
 
 	return pool;
@@ -863,7 +860,7 @@ EXPORT_SYMBOL_GPL(zs_destroy_pool);
  * otherwise 0.
  * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
  */
-unsigned long zs_malloc(struct zs_pool *pool, size_t size)
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
 {
 	unsigned long obj;
 	struct link_free *link;
@@ -885,7 +882,7 @@ unsigned long zs_malloc(struct zs_pool *pool, size_t size)
 
 	if (!first_page) {
 		spin_unlock(&class->lock);
-		first_page = alloc_zspage(class, pool->flags);
+		first_page = alloc_zspage(class, flags);
 		if (unlikely(!first_page))
 			return 0;
 
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
index de2e8bf..907ff03 100644
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ b/drivers/staging/zsmalloc/zsmalloc.h
@@ -31,7 +31,7 @@ struct zs_pool;
 struct zs_pool *zs_create_pool(const char *name, gfp_t flags);
 void zs_destroy_pool(struct zs_pool *pool);
 
-unsigned long zs_malloc(struct zs_pool *pool, size_t size);
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
 void zs_free(struct zs_pool *pool, unsigned long obj);
 
 void *zs_map_object(struct zs_pool *pool, unsigned long handle,
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 2/8] staging: zsmalloc: remove unsed pool name
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
  2012-12-11 21:55 ` [PATCH 1/8] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
@ 2012-12-11 21:56 ` Seth Jennings
  2012-12-11 21:56 ` [PATCH 3/8] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

zs_create_pool() currently takes a name argument which is
never used in any useful way.

This patch removes it.

Signed-off-by: Seth Jennnings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zcache/zcache-main.c     |    2 +-
 drivers/staging/zram/zram_drv.c          |    2 +-
 drivers/staging/zsmalloc/zsmalloc-main.c |    7 +------
 drivers/staging/zsmalloc/zsmalloc.h      |    2 +-
 4 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index 674c754..6fa9f9a 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
 		goto out;
 	cli->allocated = 1;
 #ifdef CONFIG_FRONTSWAP
-	cli->zspool = zs_create_pool("zcache", GFP_KERNEL);
+	cli->zspool = zs_create_pool(GFP_KERNEL);
 	if (cli->zspool == NULL)
 		goto out;
 	idr_init(&cli->tmem_pools);
diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
index 13e9b4b..13d9f6d 100644
--- a/drivers/staging/zram/zram_drv.c
+++ b/drivers/staging/zram/zram_drv.c
@@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
 	/* zram devices sort of resembles non-rotational disks */
 	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
 
-	zram->mem_pool = zs_create_pool("zram", GFP_KERNEL);
+	zram->mem_pool = zs_create_pool(GFP_KERNEL);
 	if (!zram->mem_pool) {
 		pr_err("Error creating memory pool\n");
 		ret = -ENOMEM;
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 6ff380e..5e212c0 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -796,14 +796,11 @@ fail:
 	return notifier_to_errno(ret);
 }
 
-struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
+struct zs_pool *zs_create_pool(gfp_t flags)
 {
 	int i, ovhd_size;
 	struct zs_pool *pool;
 
-	if (!name)
-		return NULL;
-
 	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
 	pool = kzalloc(ovhd_size, flags);
 	if (!pool)
@@ -825,8 +822,6 @@ struct zs_pool *zs_create_pool(const char *name, gfp_t flags)
 
 	}
 
-	pool->name = name;
-
 	return pool;
 }
 EXPORT_SYMBOL_GPL(zs_create_pool);
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
index 907ff03..25a4b4d 100644
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ b/drivers/staging/zsmalloc/zsmalloc.h
@@ -28,7 +28,7 @@ enum zs_mapmode {
 
 struct zs_pool;
 
-struct zs_pool *zs_create_pool(const char *name, gfp_t flags);
+struct zs_pool *zs_create_pool(gfp_t flags);
 void zs_destroy_pool(struct zs_pool *pool);
 
 unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 3/8] staging: zsmalloc: add page alloc/free callbacks
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
  2012-12-11 21:55 ` [PATCH 1/8] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
  2012-12-11 21:56 ` [PATCH 2/8] staging: zsmalloc: remove unsed pool name Seth Jennings
@ 2012-12-11 21:56 ` Seth Jennings
  2012-12-11 21:56 ` [PATCH 4/8] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE Seth Jennings
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

This patch allows users of zsmalloc to register the
allocation and free routines used by zsmalloc to obtain
more pages for the memory pool.  This allows the user
more control over zsmalloc pool policy and behavior.

If the user does not wish to control this, alloc_page() and
__free_page() are used by default.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zcache/zcache-main.c     |    2 +-
 drivers/staging/zram/zram_drv.c          |    2 +-
 drivers/staging/zsmalloc/zsmalloc-main.c |   43 ++++++++++++++++++++++--------
 drivers/staging/zsmalloc/zsmalloc.h      |    8 +++++-
 4 files changed, 41 insertions(+), 14 deletions(-)

diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index 6fa9f9a..e3e533b 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -982,7 +982,7 @@ int zcache_new_client(uint16_t cli_id)
 		goto out;
 	cli->allocated = 1;
 #ifdef CONFIG_FRONTSWAP
-	cli->zspool = zs_create_pool(GFP_KERNEL);
+	cli->zspool = zs_create_pool(GFP_KERNEL, NULL);
 	if (cli->zspool == NULL)
 		goto out;
 	idr_init(&cli->tmem_pools);
diff --git a/drivers/staging/zram/zram_drv.c b/drivers/staging/zram/zram_drv.c
index 13d9f6d..0357903 100644
--- a/drivers/staging/zram/zram_drv.c
+++ b/drivers/staging/zram/zram_drv.c
@@ -576,7 +576,7 @@ int zram_init_device(struct zram *zram)
 	/* zram devices sort of resembles non-rotational disks */
 	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, zram->disk->queue);
 
-	zram->mem_pool = zs_create_pool(GFP_KERNEL);
+	zram->mem_pool = zs_create_pool(GFP_KERNEL, NULL);
 	if (!zram->mem_pool) {
 		pr_err("Error creating memory pool\n");
 		ret = -ENOMEM;
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 5e212c0..825e124 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -205,7 +205,7 @@ struct link_free {
 
 struct zs_pool {
 	struct size_class size_class[ZS_SIZE_CLASSES];
-	const char *name;
+	struct zs_ops *ops;
 };
 
 /*
@@ -240,6 +240,21 @@ struct mapping_area {
 	enum zs_mapmode vm_mm; /* mapping mode */
 };
 
+/* default page alloc/free ops */
+struct page *zs_alloc_page(gfp_t flags)
+{
+	return alloc_page(flags);
+}
+
+void zs_free_page(struct page *page)
+{
+	__free_page(page);
+}
+
+struct zs_ops zs_default_ops = {
+	.alloc = zs_alloc_page,
+	.free = zs_free_page
+};
 
 /* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
 static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
@@ -476,7 +491,7 @@ static void reset_page(struct page *page)
 	reset_page_mapcount(page);
 }
 
-static void free_zspage(struct page *first_page)
+static void free_zspage(struct zs_ops *ops, struct page *first_page)
 {
 	struct page *nextp, *tmp, *head_extra;
 
@@ -486,7 +501,7 @@ static void free_zspage(struct page *first_page)
 	head_extra = (struct page *)page_private(first_page);
 
 	reset_page(first_page);
-	__free_page(first_page);
+	ops->free(first_page);
 
 	/* zspage with only 1 system page */
 	if (!head_extra)
@@ -495,10 +510,10 @@ static void free_zspage(struct page *first_page)
 	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
 		list_del(&nextp->lru);
 		reset_page(nextp);
-		__free_page(nextp);
+		ops->free(nextp);
 	}
 	reset_page(head_extra);
-	__free_page(head_extra);
+	ops->free(head_extra);
 }
 
 /* Initialize a newly allocated zspage */
@@ -550,7 +565,8 @@ static void init_zspage(struct page *first_page, struct size_class *class)
 /*
  * Allocate a zspage for the given size class
  */
-static struct page *alloc_zspage(struct size_class *class, gfp_t flags)
+static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
+				gfp_t flags)
 {
 	int i, error;
 	struct page *first_page = NULL, *uninitialized_var(prev_page);
@@ -570,7 +586,7 @@ static struct page *alloc_zspage(struct size_class *class, gfp_t flags)
 	for (i = 0; i < class->pages_per_zspage; i++) {
 		struct page *page;
 
-		page = alloc_page(flags);
+		page = ops->alloc(flags);
 		if (!page)
 			goto cleanup;
 
@@ -602,7 +618,7 @@ static struct page *alloc_zspage(struct size_class *class, gfp_t flags)
 
 cleanup:
 	if (unlikely(error) && first_page) {
-		free_zspage(first_page);
+		free_zspage(ops, first_page);
 		first_page = NULL;
 	}
 
@@ -796,7 +812,7 @@ fail:
 	return notifier_to_errno(ret);
 }
 
-struct zs_pool *zs_create_pool(gfp_t flags)
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
 {
 	int i, ovhd_size;
 	struct zs_pool *pool;
@@ -822,6 +838,11 @@ struct zs_pool *zs_create_pool(gfp_t flags)
 
 	}
 
+	if (ops)
+		pool->ops = ops;
+	else
+		pool->ops = &zs_default_ops;
+
 	return pool;
 }
 EXPORT_SYMBOL_GPL(zs_create_pool);
@@ -877,7 +898,7 @@ unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
 
 	if (!first_page) {
 		spin_unlock(&class->lock);
-		first_page = alloc_zspage(class, flags);
+		first_page = alloc_zspage(pool->ops, class, flags);
 		if (unlikely(!first_page))
 			return 0;
 
@@ -943,7 +964,7 @@ void zs_free(struct zs_pool *pool, unsigned long obj)
 	spin_unlock(&class->lock);
 
 	if (fullness == ZS_EMPTY)
-		free_zspage(first_page);
+		free_zspage(pool->ops, first_page);
 }
 EXPORT_SYMBOL_GPL(zs_free);
 
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
index 25a4b4d..eb6efb6 100644
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ b/drivers/staging/zsmalloc/zsmalloc.h
@@ -14,6 +14,7 @@
 #define _ZS_MALLOC_H_
 
 #include <linux/types.h>
+#include <linux/mm_types.h>
 
 /*
  * zsmalloc mapping modes
@@ -26,9 +27,14 @@ enum zs_mapmode {
 	ZS_MM_WO /* write-only (no copy-in at map time) */
 };
 
+struct zs_ops {
+	struct page * (*alloc)(gfp_t);
+	void (*free)(struct page *);
+};
+
 struct zs_pool;
 
-struct zs_pool *zs_create_pool(gfp_t flags);
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
 void zs_destroy_pool(struct zs_pool *pool);
 
 unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 4/8] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (2 preceding siblings ...)
  2012-12-11 21:56 ` [PATCH 3/8] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
@ 2012-12-11 21:56 ` Seth Jennings
  2012-12-11 21:56 ` [PATCH 5/8] debugfs: add get/set for atomic types Seth Jennings
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

Right now ZS_SIZE_CLASS_DELTA is hardcoded to be 16.  This
creates 254 classes for systems with 4k pages. However, on
PPC64 with 64k pages, it creates 4095 classes which is far
too many.

This patch makes ZS_SIZE_CLASS_DELTA relative to PAGE_SIZE
so that regardless of the page size, there will be the same
number of classes.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 drivers/staging/zsmalloc/zsmalloc-main.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
index 825e124..3543047 100644
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ b/drivers/staging/zsmalloc/zsmalloc-main.c
@@ -141,7 +141,7 @@
  *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
  *  (reason above)
  */
-#define ZS_SIZE_CLASS_DELTA	16
+#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
 #define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
 					ZS_SIZE_CLASS_DELTA + 1)
 
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 5/8] debugfs: add get/set for atomic types
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (3 preceding siblings ...)
  2012-12-11 21:56 ` [PATCH 4/8] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE Seth Jennings
@ 2012-12-11 21:56 ` Seth Jennings
  2012-12-11 21:56 ` [PATCH 6/8] zsmalloc: promote to lib/ Seth Jennings
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

debugfs currently lack the ability to create attributes
that set/get atomic_t values.

This patch adds support for this through a new
debugfs_create_atomic_t() function.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 fs/debugfs/file.c       |   42 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/debugfs.h |    2 ++
 2 files changed, 44 insertions(+)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index c5ca6ae..fa26d5b 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -21,6 +21,7 @@
 #include <linux/debugfs.h>
 #include <linux/io.h>
 #include <linux/slab.h>
+#include <linux/atomic.h>
 
 static ssize_t default_read_file(struct file *file, char __user *buf,
 				 size_t count, loff_t *ppos)
@@ -403,6 +404,47 @@ struct dentry *debugfs_create_size_t(const char *name, umode_t mode,
 }
 EXPORT_SYMBOL_GPL(debugfs_create_size_t);
 
+static int debugfs_atomic_t_set(void *data, u64 val)
+{
+	atomic_set((atomic_t *)data, val);
+	return 0;
+}
+static int debugfs_atomic_t_get(void *data, u64 *val)
+{
+	*val = atomic_read((atomic_t *)data);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t, debugfs_atomic_t_get,
+			debugfs_atomic_t_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_ro, debugfs_atomic_t_get, NULL, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_wo, NULL, debugfs_atomic_t_set, "%llu\n");
+
+/**
+ * debugfs_create_atomic_t - create a debugfs file that is used to read and
+ * write an atomic_t value
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file.  This should be a
+ *          directory dentry if set.  If this parameter is %NULL, then the
+ *          file will be created in the root of the debugfs filesystem.
+ * @value: a pointer to the variable that the file should read to and write
+ *         from.
+ */
+struct dentry *debugfs_create_atomic_t(const char *name, umode_t mode,
+				 struct dentry *parent, atomic_t *value)
+{
+	/* if there are no write bits set, make read only */
+	if (!(mode & S_IWUGO))
+		return debugfs_create_file(name, mode, parent, value,
+					&fops_atomic_t_ro);
+	/* if there are no read bits set, make write only */
+	if (!(mode & S_IRUGO))
+		return debugfs_create_file(name, mode, parent, value,
+					&fops_atomic_t_wo);
+
+	return debugfs_create_file(name, mode, parent, value, &fops_atomic_t);
+}
+EXPORT_SYMBOL_GPL(debugfs_create_atomic_t);
 
 static ssize_t read_file_bool(struct file *file, char __user *user_buf,
 			      size_t count, loff_t *ppos)
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
index 66c434f..51fea70 100644
--- a/include/linux/debugfs.h
+++ b/include/linux/debugfs.h
@@ -79,6 +79,8 @@ struct dentry *debugfs_create_x64(const char *name, umode_t mode,
 				  struct dentry *parent, u64 *value);
 struct dentry *debugfs_create_size_t(const char *name, umode_t mode,
 				     struct dentry *parent, size_t *value);
+struct dentry *debugfs_create_atomic_t(const char *name, umode_t mode,
+				     struct dentry *parent, atomic_t *value);
 struct dentry *debugfs_create_bool(const char *name, umode_t mode,
 				  struct dentry *parent, u32 *value);
 
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 6/8] zsmalloc: promote to lib/
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (4 preceding siblings ...)
  2012-12-11 21:56 ` [PATCH 5/8] debugfs: add get/set for atomic types Seth Jennings
@ 2012-12-11 21:56 ` Seth Jennings
  2012-12-11 21:56 ` [PATCH 7/8] zswap: add to mm/ Seth Jennings
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

This patch promotes the slab-based zsmalloc memory allocator
from the staging tree to lib/

zswap depends on this allocator for storing compressed RAM pages
in an efficient way under system wide memory pressure where
high-order (greater than 0) page allocation are very likely to
fail.

For more information on zsmalloc and its internals, read the
documentation at the top of the zsmalloc.c file.

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
--
This patch is similar to a patch Minchan has on out on the list
to promote for use in zram.
---
 drivers/staging/Kconfig                  |    2 -
 drivers/staging/Makefile                 |    1 -
 drivers/staging/zcache/zcache-main.c     |    3 +-
 drivers/staging/zram/zram_drv.h          |    3 +-
 drivers/staging/zsmalloc/Kconfig         |   10 -
 drivers/staging/zsmalloc/Makefile        |    3 -
 drivers/staging/zsmalloc/zsmalloc-main.c | 1077 ------------------------------
 drivers/staging/zsmalloc/zsmalloc.h      |   49 --
 include/linux/zsmalloc.h                 |   49 ++
 lib/Kconfig                              |   18 +
 lib/Makefile                             |    1 +
 lib/zsmalloc.c                           | 1076 +++++++++++++++++++++++++++++
 12 files changed, 1146 insertions(+), 1146 deletions(-)
 delete mode 100644 drivers/staging/zsmalloc/Kconfig
 delete mode 100644 drivers/staging/zsmalloc/Makefile
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c
 delete mode 100644 drivers/staging/zsmalloc/zsmalloc.h
 create mode 100644 include/linux/zsmalloc.h
 create mode 100644 lib/zsmalloc.c

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 329bdb4..c0a7918 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -76,8 +76,6 @@ source "drivers/staging/zram/Kconfig"
 
 source "drivers/staging/zcache/Kconfig"
 
-source "drivers/staging/zsmalloc/Kconfig"
-
 source "drivers/staging/wlags49_h2/Kconfig"
 
 source "drivers/staging/wlags49_h25/Kconfig"
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index c7ec486..1572fe5 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -32,7 +32,6 @@ obj-$(CONFIG_DX_SEP)            += sep/
 obj-$(CONFIG_IIO)		+= iio/
 obj-$(CONFIG_ZRAM)		+= zram/
 obj-$(CONFIG_ZCACHE)		+= zcache/
-obj-$(CONFIG_ZSMALLOC)		+= zsmalloc/
 obj-$(CONFIG_WLAGS49_H2)	+= wlags49_h2/
 obj-$(CONFIG_WLAGS49_H25)	+= wlags49_h25/
 obj-$(CONFIG_FB_SM7XX)		+= sm7xxfb/
diff --git a/drivers/staging/zcache/zcache-main.c b/drivers/staging/zcache/zcache-main.c
index e3e533b..08e412a 100644
--- a/drivers/staging/zcache/zcache-main.c
+++ b/drivers/staging/zcache/zcache-main.c
@@ -32,10 +32,9 @@
 #include <linux/crypto.h>
 #include <linux/string.h>
 #include <linux/idr.h>
+#include <linux/zsmalloc.h>
 #include "tmem.h"
 
-#include "../zsmalloc/zsmalloc.h"
-
 #ifdef CONFIG_CLEANCACHE
 #include <linux/cleancache.h>
 #endif
diff --git a/drivers/staging/zram/zram_drv.h b/drivers/staging/zram/zram_drv.h
index df2eec4..1e72965 100644
--- a/drivers/staging/zram/zram_drv.h
+++ b/drivers/staging/zram/zram_drv.h
@@ -17,8 +17,7 @@
 
 #include <linux/spinlock.h>
 #include <linux/mutex.h>
-
-#include "../zsmalloc/zsmalloc.h"
+#include <linux/zsmalloc.h>
 
 /*
  * Some arbitrary value. This is just to catch
diff --git a/drivers/staging/zsmalloc/Kconfig b/drivers/staging/zsmalloc/Kconfig
deleted file mode 100644
index 9084565..0000000
--- a/drivers/staging/zsmalloc/Kconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-config ZSMALLOC
-	tristate "Memory allocator for compressed pages"
-	default n
-	help
-	  zsmalloc is a slab-based memory allocator designed to store
-	  compressed RAM pages.  zsmalloc uses virtual memory mapping
-	  in order to reduce fragmentation.  However, this results in a
-	  non-standard allocator interface where a handle, not a pointer, is
-	  returned by an alloc().  This handle must be mapped in order to
-	  access the allocated space.
diff --git a/drivers/staging/zsmalloc/Makefile b/drivers/staging/zsmalloc/Makefile
deleted file mode 100644
index b134848..0000000
--- a/drivers/staging/zsmalloc/Makefile
+++ /dev/null
@@ -1,3 +0,0 @@
-zsmalloc-y 		:= zsmalloc-main.o
-
-obj-$(CONFIG_ZSMALLOC)	+= zsmalloc.o
diff --git a/drivers/staging/zsmalloc/zsmalloc-main.c b/drivers/staging/zsmalloc/zsmalloc-main.c
deleted file mode 100644
index 3543047..0000000
--- a/drivers/staging/zsmalloc/zsmalloc-main.c
+++ /dev/null
@@ -1,1077 +0,0 @@
-/*
- * zsmalloc memory allocator
- *
- * Copyright (C) 2011  Nitin Gupta
- *
- * This code is released using a dual license strategy: BSD/GPL
- * You can choose the license that better fits your requirements.
- *
- * Released under the terms of 3-clause BSD License
- * Released under the terms of GNU General Public License Version 2.0
- */
-
-
-/*
- * This allocator is designed for use with zcache and zram. Thus, the
- * allocator is supposed to work well under low memory conditions. In
- * particular, it never attempts higher order page allocation which is
- * very likely to fail under memory pressure. On the other hand, if we
- * just use single (0-order) pages, it would suffer from very high
- * fragmentation -- any object of size PAGE_SIZE/2 or larger would occupy
- * an entire page. This was one of the major issues with its predecessor
- * (xvmalloc).
- *
- * To overcome these issues, zsmalloc allocates a bunch of 0-order pages
- * and links them together using various 'struct page' fields. These linked
- * pages act as a single higher-order page i.e. an object can span 0-order
- * page boundaries. The code refers to these linked pages as a single entity
- * called zspage.
- *
- * Following is how we use various fields and flags of underlying
- * struct page(s) to form a zspage.
- *
- * Usage of struct page fields:
- *	page->first_page: points to the first component (0-order) page
- *	page->index (union with page->freelist): offset of the first object
- *		starting in this page. For the first page, this is
- *		always 0, so we use this field (aka freelist) to point
- *		to the first free object in zspage.
- *	page->lru: links together all component pages (except the first page)
- *		of a zspage
- *
- *	For _first_ page only:
- *
- *	page->private (union with page->first_page): refers to the
- *		component page after the first page
- *	page->freelist: points to the first free object in zspage.
- *		Free objects are linked together using in-place
- *		metadata.
- *	page->objects: maximum number of objects we can store in this
- *		zspage (class->zspage_order * PAGE_SIZE / class->size)
- *	page->lru: links together first pages of various zspages.
- *		Basically forming list of zspages in a fullness group.
- *	page->mapping: class index and fullness group of the zspage
- *
- * Usage of struct page flags:
- *	PG_private: identifies the first component page
- *	PG_private2: identifies the last component page
- *
- */
-
-#ifdef CONFIG_ZSMALLOC_DEBUG
-#define DEBUG
-#endif
-
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/bitops.h>
-#include <linux/errno.h>
-#include <linux/highmem.h>
-#include <linux/init.h>
-#include <linux/string.h>
-#include <linux/slab.h>
-#include <asm/tlbflush.h>
-#include <asm/pgtable.h>
-#include <linux/cpumask.h>
-#include <linux/cpu.h>
-#include <linux/vmalloc.h>
-#include <linux/hardirq.h>
-#include <linux/spinlock.h>
-#include <linux/types.h>
-
-#include "zsmalloc.h"
-
-/*
- * This must be power of 2 and greater than of equal to sizeof(link_free).
- * These two conditions ensure that any 'struct link_free' itself doesn't
- * span more than 1 page which avoids complex case of mapping 2 pages simply
- * to restore link_free pointer values.
- */
-#define ZS_ALIGN		8
-
-/*
- * A single 'zspage' is composed of up to 2^N discontiguous 0-order (single)
- * pages. ZS_MAX_ZSPAGE_ORDER defines upper limit on N.
- */
-#define ZS_MAX_ZSPAGE_ORDER 2
-#define ZS_MAX_PAGES_PER_ZSPAGE (_AC(1, UL) << ZS_MAX_ZSPAGE_ORDER)
-
-/*
- * Object location (<PFN>, <obj_idx>) is encoded as
- * as single (void *) handle value.
- *
- * Note that object index <obj_idx> is relative to system
- * page <PFN> it is stored in, so for each sub-page belonging
- * to a zspage, obj_idx starts with 0.
- *
- * This is made more complicated by various memory models and PAE.
- */
-
-#ifndef MAX_PHYSMEM_BITS
-#ifdef CONFIG_HIGHMEM64G
-#define MAX_PHYSMEM_BITS 36
-#else /* !CONFIG_HIGHMEM64G */
-/*
- * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just
- * be PAGE_SHIFT
- */
-#define MAX_PHYSMEM_BITS BITS_PER_LONG
-#endif
-#endif
-#define _PFN_BITS		(MAX_PHYSMEM_BITS - PAGE_SHIFT)
-#define OBJ_INDEX_BITS	(BITS_PER_LONG - _PFN_BITS)
-#define OBJ_INDEX_MASK	((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
-
-#define MAX(a, b) ((a) >= (b) ? (a) : (b))
-/* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */
-#define ZS_MIN_ALLOC_SIZE \
-	MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS))
-#define ZS_MAX_ALLOC_SIZE	PAGE_SIZE
-
-/*
- * On systems with 4K page size, this gives 254 size classes! There is a
- * trader-off here:
- *  - Large number of size classes is potentially wasteful as free page are
- *    spread across these classes
- *  - Small number of size classes causes large internal fragmentation
- *  - Probably its better to use specific size classes (empirically
- *    determined). NOTE: all those class sizes must be set as multiple of
- *    ZS_ALIGN to make sure link_free itself never has to span 2 pages.
- *
- *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
- *  (reason above)
- */
-#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
-#define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
-					ZS_SIZE_CLASS_DELTA + 1)
-
-/*
- * We do not maintain any list for completely empty or full pages
- */
-enum fullness_group {
-	ZS_ALMOST_FULL,
-	ZS_ALMOST_EMPTY,
-	_ZS_NR_FULLNESS_GROUPS,
-
-	ZS_EMPTY,
-	ZS_FULL
-};
-
-/*
- * We assign a page to ZS_ALMOST_EMPTY fullness group when:
- *	n <= N / f, where
- * n = number of allocated objects
- * N = total number of objects zspage can store
- * f = 1/fullness_threshold_frac
- *
- * Similarly, we assign zspage to:
- *	ZS_ALMOST_FULL	when n > N / f
- *	ZS_EMPTY	when n == 0
- *	ZS_FULL		when n == N
- *
- * (see: fix_fullness_group())
- */
-static const int fullness_threshold_frac = 4;
-
-struct size_class {
-	/*
-	 * Size of objects stored in this class. Must be multiple
-	 * of ZS_ALIGN.
-	 */
-	int size;
-	unsigned int index;
-
-	/* Number of PAGE_SIZE sized pages to combine to form a 'zspage' */
-	int pages_per_zspage;
-
-	spinlock_t lock;
-
-	/* stats */
-	u64 pages_allocated;
-
-	struct page *fullness_list[_ZS_NR_FULLNESS_GROUPS];
-};
-
-/*
- * Placed within free objects to form a singly linked list.
- * For every zspage, first_page->freelist gives head of this list.
- *
- * This must be power of 2 and less than or equal to ZS_ALIGN
- */
-struct link_free {
-	/* Handle of next free chunk (encodes <PFN, obj_idx>) */
-	void *next;
-};
-
-struct zs_pool {
-	struct size_class size_class[ZS_SIZE_CLASSES];
-	struct zs_ops *ops;
-};
-
-/*
- * A zspage's class index and fullness group
- * are encoded in its (first)page->mapping
- */
-#define CLASS_IDX_BITS	28
-#define FULLNESS_BITS	4
-#define CLASS_IDX_MASK	((1 << CLASS_IDX_BITS) - 1)
-#define FULLNESS_MASK	((1 << FULLNESS_BITS) - 1)
-
-/*
- * By default, zsmalloc uses a copy-based object mapping method to access
- * allocations that span two pages. However, if a particular architecture
- * 1) Implements local_flush_tlb_kernel_range() and 2) Performs VM mapping
- * faster than copying, then it should be added here so that
- * USE_PGTABLE_MAPPING is defined. This causes zsmalloc to use page table
- * mapping rather than copying
- * for object mapping.
-*/
-#if defined(CONFIG_ARM)
-#define USE_PGTABLE_MAPPING
-#endif
-
-struct mapping_area {
-#ifdef USE_PGTABLE_MAPPING
-	struct vm_struct *vm; /* vm area for mapping object that span pages */
-#else
-	char *vm_buf; /* copy buffer for objects that span pages */
-#endif
-	char *vm_addr; /* address of kmap_atomic()'ed pages */
-	enum zs_mapmode vm_mm; /* mapping mode */
-};
-
-/* default page alloc/free ops */
-struct page *zs_alloc_page(gfp_t flags)
-{
-	return alloc_page(flags);
-}
-
-void zs_free_page(struct page *page)
-{
-	__free_page(page);
-}
-
-struct zs_ops zs_default_ops = {
-	.alloc = zs_alloc_page,
-	.free = zs_free_page
-};
-
-/* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
-static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
-
-static int is_first_page(struct page *page)
-{
-	return PagePrivate(page);
-}
-
-static int is_last_page(struct page *page)
-{
-	return PagePrivate2(page);
-}
-
-static void get_zspage_mapping(struct page *page, unsigned int *class_idx,
-				enum fullness_group *fullness)
-{
-	unsigned long m;
-	BUG_ON(!is_first_page(page));
-
-	m = (unsigned long)page->mapping;
-	*fullness = m & FULLNESS_MASK;
-	*class_idx = (m >> FULLNESS_BITS) & CLASS_IDX_MASK;
-}
-
-static void set_zspage_mapping(struct page *page, unsigned int class_idx,
-				enum fullness_group fullness)
-{
-	unsigned long m;
-	BUG_ON(!is_first_page(page));
-
-	m = ((class_idx & CLASS_IDX_MASK) << FULLNESS_BITS) |
-			(fullness & FULLNESS_MASK);
-	page->mapping = (struct address_space *)m;
-}
-
-static int get_size_class_index(int size)
-{
-	int idx = 0;
-
-	if (likely(size > ZS_MIN_ALLOC_SIZE))
-		idx = DIV_ROUND_UP(size - ZS_MIN_ALLOC_SIZE,
-				ZS_SIZE_CLASS_DELTA);
-
-	return idx;
-}
-
-static enum fullness_group get_fullness_group(struct page *page)
-{
-	int inuse, max_objects;
-	enum fullness_group fg;
-	BUG_ON(!is_first_page(page));
-
-	inuse = page->inuse;
-	max_objects = page->objects;
-
-	if (inuse == 0)
-		fg = ZS_EMPTY;
-	else if (inuse == max_objects)
-		fg = ZS_FULL;
-	else if (inuse <= max_objects / fullness_threshold_frac)
-		fg = ZS_ALMOST_EMPTY;
-	else
-		fg = ZS_ALMOST_FULL;
-
-	return fg;
-}
-
-static void insert_zspage(struct page *page, struct size_class *class,
-				enum fullness_group fullness)
-{
-	struct page **head;
-
-	BUG_ON(!is_first_page(page));
-
-	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
-		return;
-
-	head = &class->fullness_list[fullness];
-	if (*head)
-		list_add_tail(&page->lru, &(*head)->lru);
-
-	*head = page;
-}
-
-static void remove_zspage(struct page *page, struct size_class *class,
-				enum fullness_group fullness)
-{
-	struct page **head;
-
-	BUG_ON(!is_first_page(page));
-
-	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
-		return;
-
-	head = &class->fullness_list[fullness];
-	BUG_ON(!*head);
-	if (list_empty(&(*head)->lru))
-		*head = NULL;
-	else if (*head == page)
-		*head = (struct page *)list_entry((*head)->lru.next,
-					struct page, lru);
-
-	list_del_init(&page->lru);
-}
-
-static enum fullness_group fix_fullness_group(struct zs_pool *pool,
-						struct page *page)
-{
-	int class_idx;
-	struct size_class *class;
-	enum fullness_group currfg, newfg;
-
-	BUG_ON(!is_first_page(page));
-
-	get_zspage_mapping(page, &class_idx, &currfg);
-	newfg = get_fullness_group(page);
-	if (newfg == currfg)
-		goto out;
-
-	class = &pool->size_class[class_idx];
-	remove_zspage(page, class, currfg);
-	insert_zspage(page, class, newfg);
-	set_zspage_mapping(page, class_idx, newfg);
-
-out:
-	return newfg;
-}
-
-/*
- * We have to decide on how many pages to link together
- * to form a zspage for each size class. This is important
- * to reduce wastage due to unusable space left at end of
- * each zspage which is given as:
- *	wastage = Zp - Zp % size_class
- * where Zp = zspage size = k * PAGE_SIZE where k = 1, 2, ...
- *
- * For example, for size class of 3/8 * PAGE_SIZE, we should
- * link together 3 PAGE_SIZE sized pages to form a zspage
- * since then we can perfectly fit in 8 such objects.
- */
-static int get_pages_per_zspage(int class_size)
-{
-	int i, max_usedpc = 0;
-	/* zspage order which gives maximum used size per KB */
-	int max_usedpc_order = 1;
-
-	for (i = 1; i <= ZS_MAX_PAGES_PER_ZSPAGE; i++) {
-		int zspage_size;
-		int waste, usedpc;
-
-		zspage_size = i * PAGE_SIZE;
-		waste = zspage_size % class_size;
-		usedpc = (zspage_size - waste) * 100 / zspage_size;
-
-		if (usedpc > max_usedpc) {
-			max_usedpc = usedpc;
-			max_usedpc_order = i;
-		}
-	}
-
-	return max_usedpc_order;
-}
-
-/*
- * A single 'zspage' is composed of many system pages which are
- * linked together using fields in struct page. This function finds
- * the first/head page, given any component page of a zspage.
- */
-static struct page *get_first_page(struct page *page)
-{
-	if (is_first_page(page))
-		return page;
-	else
-		return page->first_page;
-}
-
-static struct page *get_next_page(struct page *page)
-{
-	struct page *next;
-
-	if (is_last_page(page))
-		next = NULL;
-	else if (is_first_page(page))
-		next = (struct page *)page->private;
-	else
-		next = list_entry(page->lru.next, struct page, lru);
-
-	return next;
-}
-
-/* Encode <page, obj_idx> as a single handle value */
-static void *obj_location_to_handle(struct page *page, unsigned long obj_idx)
-{
-	unsigned long handle;
-
-	if (!page) {
-		BUG_ON(obj_idx);
-		return NULL;
-	}
-
-	handle = page_to_pfn(page) << OBJ_INDEX_BITS;
-	handle |= (obj_idx & OBJ_INDEX_MASK);
-
-	return (void *)handle;
-}
-
-/* Decode <page, obj_idx> pair from the given object handle */
-static void obj_handle_to_location(unsigned long handle, struct page **page,
-				unsigned long *obj_idx)
-{
-	*page = pfn_to_page(handle >> OBJ_INDEX_BITS);
-	*obj_idx = handle & OBJ_INDEX_MASK;
-}
-
-static unsigned long obj_idx_to_offset(struct page *page,
-				unsigned long obj_idx, int class_size)
-{
-	unsigned long off = 0;
-
-	if (!is_first_page(page))
-		off = page->index;
-
-	return off + obj_idx * class_size;
-}
-
-static void reset_page(struct page *page)
-{
-	clear_bit(PG_private, &page->flags);
-	clear_bit(PG_private_2, &page->flags);
-	set_page_private(page, 0);
-	page->mapping = NULL;
-	page->freelist = NULL;
-	reset_page_mapcount(page);
-}
-
-static void free_zspage(struct zs_ops *ops, struct page *first_page)
-{
-	struct page *nextp, *tmp, *head_extra;
-
-	BUG_ON(!is_first_page(first_page));
-	BUG_ON(first_page->inuse);
-
-	head_extra = (struct page *)page_private(first_page);
-
-	reset_page(first_page);
-	ops->free(first_page);
-
-	/* zspage with only 1 system page */
-	if (!head_extra)
-		return;
-
-	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
-		list_del(&nextp->lru);
-		reset_page(nextp);
-		ops->free(nextp);
-	}
-	reset_page(head_extra);
-	ops->free(head_extra);
-}
-
-/* Initialize a newly allocated zspage */
-static void init_zspage(struct page *first_page, struct size_class *class)
-{
-	unsigned long off = 0;
-	struct page *page = first_page;
-
-	BUG_ON(!is_first_page(first_page));
-	while (page) {
-		struct page *next_page;
-		struct link_free *link;
-		unsigned int i, objs_on_page;
-
-		/*
-		 * page->index stores offset of first object starting
-		 * in the page. For the first page, this is always 0,
-		 * so we use first_page->index (aka ->freelist) to store
-		 * head of corresponding zspage's freelist.
-		 */
-		if (page != first_page)
-			page->index = off;
-
-		link = (struct link_free *)kmap_atomic(page) +
-						off / sizeof(*link);
-		objs_on_page = (PAGE_SIZE - off) / class->size;
-
-		for (i = 1; i <= objs_on_page; i++) {
-			off += class->size;
-			if (off < PAGE_SIZE) {
-				link->next = obj_location_to_handle(page, i);
-				link += class->size / sizeof(*link);
-			}
-		}
-
-		/*
-		 * We now come to the last (full or partial) object on this
-		 * page, which must point to the first object on the next
-		 * page (if present)
-		 */
-		next_page = get_next_page(page);
-		link->next = obj_location_to_handle(next_page, 0);
-		kunmap_atomic(link);
-		page = next_page;
-		off = (off + class->size) % PAGE_SIZE;
-	}
-}
-
-/*
- * Allocate a zspage for the given size class
- */
-static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
-				gfp_t flags)
-{
-	int i, error;
-	struct page *first_page = NULL, *uninitialized_var(prev_page);
-
-	/*
-	 * Allocate individual pages and link them together as:
-	 * 1. first page->private = first sub-page
-	 * 2. all sub-pages are linked together using page->lru
-	 * 3. each sub-page is linked to the first page using page->first_page
-	 *
-	 * For each size class, First/Head pages are linked together using
-	 * page->lru. Also, we set PG_private to identify the first page
-	 * (i.e. no other sub-page has this flag set) and PG_private_2 to
-	 * identify the last page.
-	 */
-	error = -ENOMEM;
-	for (i = 0; i < class->pages_per_zspage; i++) {
-		struct page *page;
-
-		page = ops->alloc(flags);
-		if (!page)
-			goto cleanup;
-
-		INIT_LIST_HEAD(&page->lru);
-		if (i == 0) {	/* first page */
-			SetPagePrivate(page);
-			set_page_private(page, 0);
-			first_page = page;
-			first_page->inuse = 0;
-		}
-		if (i == 1)
-			first_page->private = (unsigned long)page;
-		if (i >= 1)
-			page->first_page = first_page;
-		if (i >= 2)
-			list_add(&page->lru, &prev_page->lru);
-		if (i == class->pages_per_zspage - 1)	/* last page */
-			SetPagePrivate2(page);
-		prev_page = page;
-	}
-
-	init_zspage(first_page, class);
-
-	first_page->freelist = obj_location_to_handle(first_page, 0);
-	/* Maximum number of objects we can store in this zspage */
-	first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
-
-	error = 0; /* Success */
-
-cleanup:
-	if (unlikely(error) && first_page) {
-		free_zspage(ops, first_page);
-		first_page = NULL;
-	}
-
-	return first_page;
-}
-
-static struct page *find_get_zspage(struct size_class *class)
-{
-	int i;
-	struct page *page;
-
-	for (i = 0; i < _ZS_NR_FULLNESS_GROUPS; i++) {
-		page = class->fullness_list[i];
-		if (page)
-			break;
-	}
-
-	return page;
-}
-
-#ifdef USE_PGTABLE_MAPPING
-static inline int __zs_cpu_up(struct mapping_area *area)
-{
-	/*
-	 * Make sure we don't leak memory if a cpu UP notification
-	 * and zs_init() race and both call zs_cpu_up() on the same cpu
-	 */
-	if (area->vm)
-		return 0;
-	area->vm = alloc_vm_area(PAGE_SIZE * 2, NULL);
-	if (!area->vm)
-		return -ENOMEM;
-	return 0;
-}
-
-static inline void __zs_cpu_down(struct mapping_area *area)
-{
-	if (area->vm)
-		free_vm_area(area->vm);
-	area->vm = NULL;
-}
-
-static inline void *__zs_map_object(struct mapping_area *area,
-				struct page *pages[2], int off, int size)
-{
-	BUG_ON(map_vm_area(area->vm, PAGE_KERNEL, &pages));
-	area->vm_addr = area->vm->addr;
-	return area->vm_addr + off;
-}
-
-static inline void __zs_unmap_object(struct mapping_area *area,
-				struct page *pages[2], int off, int size)
-{
-	unsigned long addr = (unsigned long)area->vm_addr;
-	unsigned long end = addr + (PAGE_SIZE * 2);
-
-	flush_cache_vunmap(addr, end);
-	unmap_kernel_range_noflush(addr, PAGE_SIZE * 2);
-	local_flush_tlb_kernel_range(addr, end);
-}
-
-#else /* USE_PGTABLE_MAPPING */
-
-static inline int __zs_cpu_up(struct mapping_area *area)
-{
-	/*
-	 * Make sure we don't leak memory if a cpu UP notification
-	 * and zs_init() race and both call zs_cpu_up() on the same cpu
-	 */
-	if (area->vm_buf)
-		return 0;
-	area->vm_buf = (char *)__get_free_page(GFP_KERNEL);
-	if (!area->vm_buf)
-		return -ENOMEM;
-	return 0;
-}
-
-static inline void __zs_cpu_down(struct mapping_area *area)
-{
-	if (area->vm_buf)
-		free_page((unsigned long)area->vm_buf);
-	area->vm_buf = NULL;
-}
-
-static void *__zs_map_object(struct mapping_area *area,
-			struct page *pages[2], int off, int size)
-{
-	int sizes[2];
-	void *addr;
-	char *buf = area->vm_buf;
-
-	/* disable page faults to match kmap_atomic() return conditions */
-	pagefault_disable();
-
-	/* no read fastpath */
-	if (area->vm_mm == ZS_MM_WO)
-		goto out;
-
-	sizes[0] = PAGE_SIZE - off;
-	sizes[1] = size - sizes[0];
-
-	/* copy object to per-cpu buffer */
-	addr = kmap_atomic(pages[0]);
-	memcpy(buf, addr + off, sizes[0]);
-	kunmap_atomic(addr);
-	addr = kmap_atomic(pages[1]);
-	memcpy(buf + sizes[0], addr, sizes[1]);
-	kunmap_atomic(addr);
-out:
-	return area->vm_buf;
-}
-
-static void __zs_unmap_object(struct mapping_area *area,
-			struct page *pages[2], int off, int size)
-{
-	int sizes[2];
-	void *addr;
-	char *buf = area->vm_buf;
-
-	/* no write fastpath */
-	if (area->vm_mm == ZS_MM_RO)
-		goto out;
-
-	sizes[0] = PAGE_SIZE - off;
-	sizes[1] = size - sizes[0];
-
-	/* copy per-cpu buffer to object */
-	addr = kmap_atomic(pages[0]);
-	memcpy(addr + off, buf, sizes[0]);
-	kunmap_atomic(addr);
-	addr = kmap_atomic(pages[1]);
-	memcpy(addr, buf + sizes[0], sizes[1]);
-	kunmap_atomic(addr);
-
-out:
-	/* enable page faults to match kunmap_atomic() return conditions */
-	pagefault_enable();
-}
-
-#endif /* USE_PGTABLE_MAPPING */
-
-static int zs_cpu_notifier(struct notifier_block *nb, unsigned long action,
-				void *pcpu)
-{
-	int ret, cpu = (long)pcpu;
-	struct mapping_area *area;
-
-	switch (action) {
-	case CPU_UP_PREPARE:
-		area = &per_cpu(zs_map_area, cpu);
-		ret = __zs_cpu_up(area);
-		if (ret)
-			return notifier_from_errno(ret);
-		break;
-	case CPU_DEAD:
-	case CPU_UP_CANCELED:
-		area = &per_cpu(zs_map_area, cpu);
-		__zs_cpu_down(area);
-		break;
-	}
-
-	return NOTIFY_OK;
-}
-
-static struct notifier_block zs_cpu_nb = {
-	.notifier_call = zs_cpu_notifier
-};
-
-static void zs_exit(void)
-{
-	int cpu;
-
-	for_each_online_cpu(cpu)
-		zs_cpu_notifier(NULL, CPU_DEAD, (void *)(long)cpu);
-	unregister_cpu_notifier(&zs_cpu_nb);
-}
-
-static int zs_init(void)
-{
-	int cpu, ret;
-
-	register_cpu_notifier(&zs_cpu_nb);
-	for_each_online_cpu(cpu) {
-		ret = zs_cpu_notifier(NULL, CPU_UP_PREPARE, (void *)(long)cpu);
-		if (notifier_to_errno(ret))
-			goto fail;
-	}
-	return 0;
-fail:
-	zs_exit();
-	return notifier_to_errno(ret);
-}
-
-struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
-{
-	int i, ovhd_size;
-	struct zs_pool *pool;
-
-	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
-	pool = kzalloc(ovhd_size, flags);
-	if (!pool)
-		return NULL;
-
-	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
-		int size;
-		struct size_class *class;
-
-		size = ZS_MIN_ALLOC_SIZE + i * ZS_SIZE_CLASS_DELTA;
-		if (size > ZS_MAX_ALLOC_SIZE)
-			size = ZS_MAX_ALLOC_SIZE;
-
-		class = &pool->size_class[i];
-		class->size = size;
-		class->index = i;
-		spin_lock_init(&class->lock);
-		class->pages_per_zspage = get_pages_per_zspage(size);
-
-	}
-
-	if (ops)
-		pool->ops = ops;
-	else
-		pool->ops = &zs_default_ops;
-
-	return pool;
-}
-EXPORT_SYMBOL_GPL(zs_create_pool);
-
-void zs_destroy_pool(struct zs_pool *pool)
-{
-	int i;
-
-	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
-		int fg;
-		struct size_class *class = &pool->size_class[i];
-
-		for (fg = 0; fg < _ZS_NR_FULLNESS_GROUPS; fg++) {
-			if (class->fullness_list[fg]) {
-				pr_info("Freeing non-empty class with size "
-					"%db, fullness group %d\n",
-					class->size, fg);
-			}
-		}
-	}
-	kfree(pool);
-}
-EXPORT_SYMBOL_GPL(zs_destroy_pool);
-
-/**
- * zs_malloc - Allocate block of given size from pool.
- * @pool: pool to allocate from
- * @size: size of block to allocate
- *
- * On success, handle to the allocated object is returned,
- * otherwise 0.
- * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
- */
-unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
-{
-	unsigned long obj;
-	struct link_free *link;
-	int class_idx;
-	struct size_class *class;
-
-	struct page *first_page, *m_page;
-	unsigned long m_objidx, m_offset;
-
-	if (unlikely(!size || size > ZS_MAX_ALLOC_SIZE))
-		return 0;
-
-	class_idx = get_size_class_index(size);
-	class = &pool->size_class[class_idx];
-	BUG_ON(class_idx != class->index);
-
-	spin_lock(&class->lock);
-	first_page = find_get_zspage(class);
-
-	if (!first_page) {
-		spin_unlock(&class->lock);
-		first_page = alloc_zspage(pool->ops, class, flags);
-		if (unlikely(!first_page))
-			return 0;
-
-		set_zspage_mapping(first_page, class->index, ZS_EMPTY);
-		spin_lock(&class->lock);
-		class->pages_allocated += class->pages_per_zspage;
-	}
-
-	obj = (unsigned long)first_page->freelist;
-	obj_handle_to_location(obj, &m_page, &m_objidx);
-	m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
-
-	link = (struct link_free *)kmap_atomic(m_page) +
-					m_offset / sizeof(*link);
-	first_page->freelist = link->next;
-	memset(link, POISON_INUSE, sizeof(*link));
-	kunmap_atomic(link);
-
-	first_page->inuse++;
-	/* Now move the zspage to another fullness group, if required */
-	fix_fullness_group(pool, first_page);
-	spin_unlock(&class->lock);
-
-	return obj;
-}
-EXPORT_SYMBOL_GPL(zs_malloc);
-
-void zs_free(struct zs_pool *pool, unsigned long obj)
-{
-	struct link_free *link;
-	struct page *first_page, *f_page;
-	unsigned long f_objidx, f_offset;
-
-	int class_idx;
-	struct size_class *class;
-	enum fullness_group fullness;
-
-	if (unlikely(!obj))
-		return;
-
-	obj_handle_to_location(obj, &f_page, &f_objidx);
-	first_page = get_first_page(f_page);
-
-	get_zspage_mapping(first_page, &class_idx, &fullness);
-	class = &pool->size_class[class_idx];
-	f_offset = obj_idx_to_offset(f_page, f_objidx, class->size);
-
-	spin_lock(&class->lock);
-
-	/* Insert this object in containing zspage's freelist */
-	link = (struct link_free *)((unsigned char *)kmap_atomic(f_page)
-							+ f_offset);
-	link->next = first_page->freelist;
-	kunmap_atomic(link);
-	first_page->freelist = (void *)obj;
-
-	first_page->inuse--;
-	fullness = fix_fullness_group(pool, first_page);
-
-	if (fullness == ZS_EMPTY)
-		class->pages_allocated -= class->pages_per_zspage;
-
-	spin_unlock(&class->lock);
-
-	if (fullness == ZS_EMPTY)
-		free_zspage(pool->ops, first_page);
-}
-EXPORT_SYMBOL_GPL(zs_free);
-
-/**
- * zs_map_object - get address of allocated object from handle.
- * @pool: pool from which the object was allocated
- * @handle: handle returned from zs_malloc
- *
- * Before using an object allocated from zs_malloc, it must be mapped using
- * this function. When done with the object, it must be unmapped using
- * zs_unmap_object.
- *
- * Only one object can be mapped per cpu at a time. There is no protection
- * against nested mappings.
- *
- * This function returns with preemption and page faults disabled.
-*/
-void *zs_map_object(struct zs_pool *pool, unsigned long handle,
-			enum zs_mapmode mm)
-{
-	struct page *page;
-	unsigned long obj_idx, off;
-
-	unsigned int class_idx;
-	enum fullness_group fg;
-	struct size_class *class;
-	struct mapping_area *area;
-	struct page *pages[2];
-
-	BUG_ON(!handle);
-
-	/*
-	 * Because we use per-cpu mapping areas shared among the
-	 * pools/users, we can't allow mapping in interrupt context
-	 * because it can corrupt another users mappings.
-	 */
-	BUG_ON(in_interrupt());
-
-	obj_handle_to_location(handle, &page, &obj_idx);
-	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
-	class = &pool->size_class[class_idx];
-	off = obj_idx_to_offset(page, obj_idx, class->size);
-
-	area = &get_cpu_var(zs_map_area);
-	area->vm_mm = mm;
-	if (off + class->size <= PAGE_SIZE) {
-		/* this object is contained entirely within a page */
-		area->vm_addr = kmap_atomic(page);
-		return area->vm_addr + off;
-	}
-
-	/* this object spans two pages */
-	pages[0] = page;
-	pages[1] = get_next_page(page);
-	BUG_ON(!pages[1]);
-
-	return __zs_map_object(area, pages, off, class->size);
-}
-EXPORT_SYMBOL_GPL(zs_map_object);
-
-void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
-{
-	struct page *page;
-	unsigned long obj_idx, off;
-
-	unsigned int class_idx;
-	enum fullness_group fg;
-	struct size_class *class;
-	struct mapping_area *area;
-
-	BUG_ON(!handle);
-
-	obj_handle_to_location(handle, &page, &obj_idx);
-	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
-	class = &pool->size_class[class_idx];
-	off = obj_idx_to_offset(page, obj_idx, class->size);
-
-	area = &__get_cpu_var(zs_map_area);
-	if (off + class->size <= PAGE_SIZE)
-		kunmap_atomic(area->vm_addr);
-	else {
-		struct page *pages[2];
-
-		pages[0] = page;
-		pages[1] = get_next_page(page);
-		BUG_ON(!pages[1]);
-
-		__zs_unmap_object(area, pages, off, class->size);
-	}
-	put_cpu_var(zs_map_area);
-}
-EXPORT_SYMBOL_GPL(zs_unmap_object);
-
-u64 zs_get_total_size_bytes(struct zs_pool *pool)
-{
-	int i;
-	u64 npages = 0;
-
-	for (i = 0; i < ZS_SIZE_CLASSES; i++)
-		npages += pool->size_class[i].pages_allocated;
-
-	return npages << PAGE_SHIFT;
-}
-EXPORT_SYMBOL_GPL(zs_get_total_size_bytes);
-
-module_init(zs_init);
-module_exit(zs_exit);
-
-MODULE_LICENSE("Dual BSD/GPL");
-MODULE_AUTHOR("Nitin Gupta <ngupta@vflare.org>");
diff --git a/drivers/staging/zsmalloc/zsmalloc.h b/drivers/staging/zsmalloc/zsmalloc.h
deleted file mode 100644
index eb6efb6..0000000
--- a/drivers/staging/zsmalloc/zsmalloc.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * zsmalloc memory allocator
- *
- * Copyright (C) 2011  Nitin Gupta
- *
- * This code is released using a dual license strategy: BSD/GPL
- * You can choose the license that better fits your requirements.
- *
- * Released under the terms of 3-clause BSD License
- * Released under the terms of GNU General Public License Version 2.0
- */
-
-#ifndef _ZS_MALLOC_H_
-#define _ZS_MALLOC_H_
-
-#include <linux/types.h>
-#include <linux/mm_types.h>
-
-/*
- * zsmalloc mapping modes
- *
- * NOTE: These only make a difference when a mapped object spans pages
-*/
-enum zs_mapmode {
-	ZS_MM_RW, /* normal read-write mapping */
-	ZS_MM_RO, /* read-only (no copy-out at unmap time) */
-	ZS_MM_WO /* write-only (no copy-in at map time) */
-};
-
-struct zs_ops {
-	struct page * (*alloc)(gfp_t);
-	void (*free)(struct page *);
-};
-
-struct zs_pool;
-
-struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
-void zs_destroy_pool(struct zs_pool *pool);
-
-unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
-void zs_free(struct zs_pool *pool, unsigned long obj);
-
-void *zs_map_object(struct zs_pool *pool, unsigned long handle,
-			enum zs_mapmode mm);
-void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
-
-u64 zs_get_total_size_bytes(struct zs_pool *pool);
-
-#endif
diff --git a/include/linux/zsmalloc.h b/include/linux/zsmalloc.h
new file mode 100644
index 0000000..eb6efb6
--- /dev/null
+++ b/include/linux/zsmalloc.h
@@ -0,0 +1,49 @@
+/*
+ * zsmalloc memory allocator
+ *
+ * Copyright (C) 2011  Nitin Gupta
+ *
+ * This code is released using a dual license strategy: BSD/GPL
+ * You can choose the license that better fits your requirements.
+ *
+ * Released under the terms of 3-clause BSD License
+ * Released under the terms of GNU General Public License Version 2.0
+ */
+
+#ifndef _ZS_MALLOC_H_
+#define _ZS_MALLOC_H_
+
+#include <linux/types.h>
+#include <linux/mm_types.h>
+
+/*
+ * zsmalloc mapping modes
+ *
+ * NOTE: These only make a difference when a mapped object spans pages
+*/
+enum zs_mapmode {
+	ZS_MM_RW, /* normal read-write mapping */
+	ZS_MM_RO, /* read-only (no copy-out at unmap time) */
+	ZS_MM_WO /* write-only (no copy-in at map time) */
+};
+
+struct zs_ops {
+	struct page * (*alloc)(gfp_t);
+	void (*free)(struct page *);
+};
+
+struct zs_pool;
+
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops);
+void zs_destroy_pool(struct zs_pool *pool);
+
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags);
+void zs_free(struct zs_pool *pool, unsigned long obj);
+
+void *zs_map_object(struct zs_pool *pool, unsigned long handle,
+			enum zs_mapmode mm);
+void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
+
+u64 zs_get_total_size_bytes(struct zs_pool *pool);
+
+#endif
diff --git a/lib/Kconfig b/lib/Kconfig
index 75cdb77..fdab273 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -219,6 +219,24 @@ config DECOMPRESS_LZO
 config GENERIC_ALLOCATOR
 	boolean
 
+config ZSMALLOC
+	tristate "Memory allocator for compressed pages"
+	default n
+	help
+	  zsmalloc is a slab-based memory allocator designed to store
+	  compressed RAM pages.  zsmalloc uses a memory pool that combines
+	  single pages into higher order pages by linking them together
+	  using the fields of the struct page. Allocations are then
+	  mapped through copy buffers or VM mapping, in order to reduce
+	  memory pool fragmentation and increase allocation success rate under
+	  memory pressure.
+
+	  This results in a non-standard allocator interface where
+	  a handle, not a pointer, is returned by the allocation function.
+	  This handle must be mapped in order to access the allocated space.
+
+	  If unsure, say N.
+
 #
 # reed solomon support is select'ed if needed
 #
diff --git a/lib/Makefile b/lib/Makefile
index 0f4c6ef..60ce44b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_CRC7)	+= crc7.o
 obj-$(CONFIG_LIBCRC32C)	+= libcrc32c.o
 obj-$(CONFIG_CRC8)	+= crc8.o
 obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o
+obj-$(CONFIG_ZSMALLOC) += zsmalloc.o
 
 obj-$(CONFIG_ZLIB_INFLATE) += zlib_inflate/
 obj-$(CONFIG_ZLIB_DEFLATE) += zlib_deflate/
diff --git a/lib/zsmalloc.c b/lib/zsmalloc.c
new file mode 100644
index 0000000..3aaf086
--- /dev/null
+++ b/lib/zsmalloc.c
@@ -0,0 +1,1076 @@
+/*
+ * zsmalloc memory allocator
+ *
+ * Copyright (C) 2011  Nitin Gupta
+ *
+ * This code is released using a dual license strategy: BSD/GPL
+ * You can choose the license that better fits your requirements.
+ *
+ * Released under the terms of 3-clause BSD License
+ * Released under the terms of GNU General Public License Version 2.0
+ */
+
+
+/*
+ * This allocator is designed for use with zcache and zram. Thus, the
+ * allocator is supposed to work well under low memory conditions. In
+ * particular, it never attempts higher order page allocation which is
+ * very likely to fail under memory pressure. On the other hand, if we
+ * just use single (0-order) pages, it would suffer from very high
+ * fragmentation -- any object of size PAGE_SIZE/2 or larger would occupy
+ * an entire page. This was one of the major issues with its predecessor
+ * (xvmalloc).
+ *
+ * To overcome these issues, zsmalloc allocates a bunch of 0-order pages
+ * and links them together using various 'struct page' fields. These linked
+ * pages act as a single higher-order page i.e. an object can span 0-order
+ * page boundaries. The code refers to these linked pages as a single entity
+ * called zspage.
+ *
+ * Following is how we use various fields and flags of underlying
+ * struct page(s) to form a zspage.
+ *
+ * Usage of struct page fields:
+ *	page->first_page: points to the first component (0-order) page
+ *	page->index (union with page->freelist): offset of the first object
+ *		starting in this page. For the first page, this is
+ *		always 0, so we use this field (aka freelist) to point
+ *		to the first free object in zspage.
+ *	page->lru: links together all component pages (except the first page)
+ *		of a zspage
+ *
+ *	For _first_ page only:
+ *
+ *	page->private (union with page->first_page): refers to the
+ *		component page after the first page
+ *	page->freelist: points to the first free object in zspage.
+ *		Free objects are linked together using in-place
+ *		metadata.
+ *	page->objects: maximum number of objects we can store in this
+ *		zspage (class->zspage_order * PAGE_SIZE / class->size)
+ *	page->lru: links together first pages of various zspages.
+ *		Basically forming list of zspages in a fullness group.
+ *	page->mapping: class index and fullness group of the zspage
+ *
+ * Usage of struct page flags:
+ *	PG_private: identifies the first component page
+ *	PG_private2: identifies the last component page
+ *
+ */
+
+#ifdef CONFIG_ZSMALLOC_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/highmem.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <asm/tlbflush.h>
+#include <asm/pgtable.h>
+#include <linux/cpumask.h>
+#include <linux/cpu.h>
+#include <linux/vmalloc.h>
+#include <linux/hardirq.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/zsmalloc.h>
+
+/*
+ * This must be power of 2 and greater than of equal to sizeof(link_free).
+ * These two conditions ensure that any 'struct link_free' itself doesn't
+ * span more than 1 page which avoids complex case of mapping 2 pages simply
+ * to restore link_free pointer values.
+ */
+#define ZS_ALIGN		8
+
+/*
+ * A single 'zspage' is composed of up to 2^N discontiguous 0-order (single)
+ * pages. ZS_MAX_ZSPAGE_ORDER defines upper limit on N.
+ */
+#define ZS_MAX_ZSPAGE_ORDER 2
+#define ZS_MAX_PAGES_PER_ZSPAGE (_AC(1, UL) << ZS_MAX_ZSPAGE_ORDER)
+
+/*
+ * Object location (<PFN>, <obj_idx>) is encoded as
+ * as single (void *) handle value.
+ *
+ * Note that object index <obj_idx> is relative to system
+ * page <PFN> it is stored in, so for each sub-page belonging
+ * to a zspage, obj_idx starts with 0.
+ *
+ * This is made more complicated by various memory models and PAE.
+ */
+
+#ifndef MAX_PHYSMEM_BITS
+#ifdef CONFIG_HIGHMEM64G
+#define MAX_PHYSMEM_BITS 36
+#else /* !CONFIG_HIGHMEM64G */
+/*
+ * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just
+ * be PAGE_SHIFT
+ */
+#define MAX_PHYSMEM_BITS BITS_PER_LONG
+#endif
+#endif
+#define _PFN_BITS		(MAX_PHYSMEM_BITS - PAGE_SHIFT)
+#define OBJ_INDEX_BITS	(BITS_PER_LONG - _PFN_BITS)
+#define OBJ_INDEX_MASK	((_AC(1, UL) << OBJ_INDEX_BITS) - 1)
+
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
+/* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */
+#define ZS_MIN_ALLOC_SIZE \
+	MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS))
+#define ZS_MAX_ALLOC_SIZE	PAGE_SIZE
+
+/*
+ * On systems with 4K page size, this gives 254 size classes! There is a
+ * trader-off here:
+ *  - Large number of size classes is potentially wasteful as free page are
+ *    spread across these classes
+ *  - Small number of size classes causes large internal fragmentation
+ *  - Probably its better to use specific size classes (empirically
+ *    determined). NOTE: all those class sizes must be set as multiple of
+ *    ZS_ALIGN to make sure link_free itself never has to span 2 pages.
+ *
+ *  ZS_MIN_ALLOC_SIZE and ZS_SIZE_CLASS_DELTA must be multiple of ZS_ALIGN
+ *  (reason above)
+ */
+#define ZS_SIZE_CLASS_DELTA	(PAGE_SIZE >> 8)
+#define ZS_SIZE_CLASSES		((ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE) / \
+					ZS_SIZE_CLASS_DELTA + 1)
+
+/*
+ * We do not maintain any list for completely empty or full pages
+ */
+enum fullness_group {
+	ZS_ALMOST_FULL,
+	ZS_ALMOST_EMPTY,
+	_ZS_NR_FULLNESS_GROUPS,
+
+	ZS_EMPTY,
+	ZS_FULL
+};
+
+/*
+ * We assign a page to ZS_ALMOST_EMPTY fullness group when:
+ *	n <= N / f, where
+ * n = number of allocated objects
+ * N = total number of objects zspage can store
+ * f = 1/fullness_threshold_frac
+ *
+ * Similarly, we assign zspage to:
+ *	ZS_ALMOST_FULL	when n > N / f
+ *	ZS_EMPTY	when n == 0
+ *	ZS_FULL		when n == N
+ *
+ * (see: fix_fullness_group())
+ */
+static const int fullness_threshold_frac = 4;
+
+struct size_class {
+	/*
+	 * Size of objects stored in this class. Must be multiple
+	 * of ZS_ALIGN.
+	 */
+	int size;
+	unsigned int index;
+
+	/* Number of PAGE_SIZE sized pages to combine to form a 'zspage' */
+	int pages_per_zspage;
+
+	spinlock_t lock;
+
+	/* stats */
+	u64 pages_allocated;
+
+	struct page *fullness_list[_ZS_NR_FULLNESS_GROUPS];
+};
+
+/*
+ * Placed within free objects to form a singly linked list.
+ * For every zspage, first_page->freelist gives head of this list.
+ *
+ * This must be power of 2 and less than or equal to ZS_ALIGN
+ */
+struct link_free {
+	/* Handle of next free chunk (encodes <PFN, obj_idx>) */
+	void *next;
+};
+
+struct zs_pool {
+	struct size_class size_class[ZS_SIZE_CLASSES];
+	struct zs_ops *ops;
+};
+
+/*
+ * A zspage's class index and fullness group
+ * are encoded in its (first)page->mapping
+ */
+#define CLASS_IDX_BITS	28
+#define FULLNESS_BITS	4
+#define CLASS_IDX_MASK	((1 << CLASS_IDX_BITS) - 1)
+#define FULLNESS_MASK	((1 << FULLNESS_BITS) - 1)
+
+/*
+ * By default, zsmalloc uses a copy-based object mapping method to access
+ * allocations that span two pages. However, if a particular architecture
+ * 1) Implements local_flush_tlb_kernel_range() and 2) Performs VM mapping
+ * faster than copying, then it should be added here so that
+ * USE_PGTABLE_MAPPING is defined. This causes zsmalloc to use page table
+ * mapping rather than copying
+ * for object mapping.
+*/
+#if defined(CONFIG_ARM)
+#define USE_PGTABLE_MAPPING
+#endif
+
+struct mapping_area {
+#ifdef USE_PGTABLE_MAPPING
+	struct vm_struct *vm; /* vm area for mapping object that span pages */
+#else
+	char *vm_buf; /* copy buffer for objects that span pages */
+#endif
+	char *vm_addr; /* address of kmap_atomic()'ed pages */
+	enum zs_mapmode vm_mm; /* mapping mode */
+};
+
+/* default page alloc/free ops */
+struct page *zs_alloc_page(gfp_t flags)
+{
+	return alloc_page(flags);
+}
+
+void zs_free_page(struct page *page)
+{
+	__free_page(page);
+}
+
+struct zs_ops zs_default_ops = {
+	.alloc = zs_alloc_page,
+	.free = zs_free_page
+};
+
+/* per-cpu VM mapping areas for zspage accesses that cross page boundaries */
+static DEFINE_PER_CPU(struct mapping_area, zs_map_area);
+
+static int is_first_page(struct page *page)
+{
+	return PagePrivate(page);
+}
+
+static int is_last_page(struct page *page)
+{
+	return PagePrivate2(page);
+}
+
+static void get_zspage_mapping(struct page *page, unsigned int *class_idx,
+				enum fullness_group *fullness)
+{
+	unsigned long m;
+	BUG_ON(!is_first_page(page));
+
+	m = (unsigned long)page->mapping;
+	*fullness = m & FULLNESS_MASK;
+	*class_idx = (m >> FULLNESS_BITS) & CLASS_IDX_MASK;
+}
+
+static void set_zspage_mapping(struct page *page, unsigned int class_idx,
+				enum fullness_group fullness)
+{
+	unsigned long m;
+	BUG_ON(!is_first_page(page));
+
+	m = ((class_idx & CLASS_IDX_MASK) << FULLNESS_BITS) |
+			(fullness & FULLNESS_MASK);
+	page->mapping = (struct address_space *)m;
+}
+
+static int get_size_class_index(int size)
+{
+	int idx = 0;
+
+	if (likely(size > ZS_MIN_ALLOC_SIZE))
+		idx = DIV_ROUND_UP(size - ZS_MIN_ALLOC_SIZE,
+				ZS_SIZE_CLASS_DELTA);
+
+	return idx;
+}
+
+static enum fullness_group get_fullness_group(struct page *page)
+{
+	int inuse, max_objects;
+	enum fullness_group fg;
+	BUG_ON(!is_first_page(page));
+
+	inuse = page->inuse;
+	max_objects = page->objects;
+
+	if (inuse == 0)
+		fg = ZS_EMPTY;
+	else if (inuse == max_objects)
+		fg = ZS_FULL;
+	else if (inuse <= max_objects / fullness_threshold_frac)
+		fg = ZS_ALMOST_EMPTY;
+	else
+		fg = ZS_ALMOST_FULL;
+
+	return fg;
+}
+
+static void insert_zspage(struct page *page, struct size_class *class,
+				enum fullness_group fullness)
+{
+	struct page **head;
+
+	BUG_ON(!is_first_page(page));
+
+	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
+		return;
+
+	head = &class->fullness_list[fullness];
+	if (*head)
+		list_add_tail(&page->lru, &(*head)->lru);
+
+	*head = page;
+}
+
+static void remove_zspage(struct page *page, struct size_class *class,
+				enum fullness_group fullness)
+{
+	struct page **head;
+
+	BUG_ON(!is_first_page(page));
+
+	if (fullness >= _ZS_NR_FULLNESS_GROUPS)
+		return;
+
+	head = &class->fullness_list[fullness];
+	BUG_ON(!*head);
+	if (list_empty(&(*head)->lru))
+		*head = NULL;
+	else if (*head == page)
+		*head = (struct page *)list_entry((*head)->lru.next,
+					struct page, lru);
+
+	list_del_init(&page->lru);
+}
+
+static enum fullness_group fix_fullness_group(struct zs_pool *pool,
+						struct page *page)
+{
+	int class_idx;
+	struct size_class *class;
+	enum fullness_group currfg, newfg;
+
+	BUG_ON(!is_first_page(page));
+
+	get_zspage_mapping(page, &class_idx, &currfg);
+	newfg = get_fullness_group(page);
+	if (newfg == currfg)
+		goto out;
+
+	class = &pool->size_class[class_idx];
+	remove_zspage(page, class, currfg);
+	insert_zspage(page, class, newfg);
+	set_zspage_mapping(page, class_idx, newfg);
+
+out:
+	return newfg;
+}
+
+/*
+ * We have to decide on how many pages to link together
+ * to form a zspage for each size class. This is important
+ * to reduce wastage due to unusable space left at end of
+ * each zspage which is given as:
+ *	wastage = Zp - Zp % size_class
+ * where Zp = zspage size = k * PAGE_SIZE where k = 1, 2, ...
+ *
+ * For example, for size class of 3/8 * PAGE_SIZE, we should
+ * link together 3 PAGE_SIZE sized pages to form a zspage
+ * since then we can perfectly fit in 8 such objects.
+ */
+static int get_pages_per_zspage(int class_size)
+{
+	int i, max_usedpc = 0;
+	/* zspage order which gives maximum used size per KB */
+	int max_usedpc_order = 1;
+
+	for (i = 1; i <= ZS_MAX_PAGES_PER_ZSPAGE; i++) {
+		int zspage_size;
+		int waste, usedpc;
+
+		zspage_size = i * PAGE_SIZE;
+		waste = zspage_size % class_size;
+		usedpc = (zspage_size - waste) * 100 / zspage_size;
+
+		if (usedpc > max_usedpc) {
+			max_usedpc = usedpc;
+			max_usedpc_order = i;
+		}
+	}
+
+	return max_usedpc_order;
+}
+
+/*
+ * A single 'zspage' is composed of many system pages which are
+ * linked together using fields in struct page. This function finds
+ * the first/head page, given any component page of a zspage.
+ */
+static struct page *get_first_page(struct page *page)
+{
+	if (is_first_page(page))
+		return page;
+	else
+		return page->first_page;
+}
+
+static struct page *get_next_page(struct page *page)
+{
+	struct page *next;
+
+	if (is_last_page(page))
+		next = NULL;
+	else if (is_first_page(page))
+		next = (struct page *)page->private;
+	else
+		next = list_entry(page->lru.next, struct page, lru);
+
+	return next;
+}
+
+/* Encode <page, obj_idx> as a single handle value */
+static void *obj_location_to_handle(struct page *page, unsigned long obj_idx)
+{
+	unsigned long handle;
+
+	if (!page) {
+		BUG_ON(obj_idx);
+		return NULL;
+	}
+
+	handle = page_to_pfn(page) << OBJ_INDEX_BITS;
+	handle |= (obj_idx & OBJ_INDEX_MASK);
+
+	return (void *)handle;
+}
+
+/* Decode <page, obj_idx> pair from the given object handle */
+static void obj_handle_to_location(unsigned long handle, struct page **page,
+				unsigned long *obj_idx)
+{
+	*page = pfn_to_page(handle >> OBJ_INDEX_BITS);
+	*obj_idx = handle & OBJ_INDEX_MASK;
+}
+
+static unsigned long obj_idx_to_offset(struct page *page,
+				unsigned long obj_idx, int class_size)
+{
+	unsigned long off = 0;
+
+	if (!is_first_page(page))
+		off = page->index;
+
+	return off + obj_idx * class_size;
+}
+
+static void reset_page(struct page *page)
+{
+	clear_bit(PG_private, &page->flags);
+	clear_bit(PG_private_2, &page->flags);
+	set_page_private(page, 0);
+	page->mapping = NULL;
+	page->freelist = NULL;
+	reset_page_mapcount(page);
+}
+
+static void free_zspage(struct zs_ops *ops, struct page *first_page)
+{
+	struct page *nextp, *tmp, *head_extra;
+
+	BUG_ON(!is_first_page(first_page));
+	BUG_ON(first_page->inuse);
+
+	head_extra = (struct page *)page_private(first_page);
+
+	reset_page(first_page);
+	ops->free(first_page);
+
+	/* zspage with only 1 system page */
+	if (!head_extra)
+		return;
+
+	list_for_each_entry_safe(nextp, tmp, &head_extra->lru, lru) {
+		list_del(&nextp->lru);
+		reset_page(nextp);
+		ops->free(nextp);
+	}
+	reset_page(head_extra);
+	ops->free(head_extra);
+}
+
+/* Initialize a newly allocated zspage */
+static void init_zspage(struct page *first_page, struct size_class *class)
+{
+	unsigned long off = 0;
+	struct page *page = first_page;
+
+	BUG_ON(!is_first_page(first_page));
+	while (page) {
+		struct page *next_page;
+		struct link_free *link;
+		unsigned int i, objs_on_page;
+
+		/*
+		 * page->index stores offset of first object starting
+		 * in the page. For the first page, this is always 0,
+		 * so we use first_page->index (aka ->freelist) to store
+		 * head of corresponding zspage's freelist.
+		 */
+		if (page != first_page)
+			page->index = off;
+
+		link = (struct link_free *)kmap_atomic(page) +
+						off / sizeof(*link);
+		objs_on_page = (PAGE_SIZE - off) / class->size;
+
+		for (i = 1; i <= objs_on_page; i++) {
+			off += class->size;
+			if (off < PAGE_SIZE) {
+				link->next = obj_location_to_handle(page, i);
+				link += class->size / sizeof(*link);
+			}
+		}
+
+		/*
+		 * We now come to the last (full or partial) object on this
+		 * page, which must point to the first object on the next
+		 * page (if present)
+		 */
+		next_page = get_next_page(page);
+		link->next = obj_location_to_handle(next_page, 0);
+		kunmap_atomic(link);
+		page = next_page;
+		off = (off + class->size) % PAGE_SIZE;
+	}
+}
+
+/*
+ * Allocate a zspage for the given size class
+ */
+static struct page *alloc_zspage(struct zs_ops *ops, struct size_class *class,
+				gfp_t flags)
+{
+	int i, error;
+	struct page *first_page = NULL, *uninitialized_var(prev_page);
+
+	/*
+	 * Allocate individual pages and link them together as:
+	 * 1. first page->private = first sub-page
+	 * 2. all sub-pages are linked together using page->lru
+	 * 3. each sub-page is linked to the first page using page->first_page
+	 *
+	 * For each size class, First/Head pages are linked together using
+	 * page->lru. Also, we set PG_private to identify the first page
+	 * (i.e. no other sub-page has this flag set) and PG_private_2 to
+	 * identify the last page.
+	 */
+	error = -ENOMEM;
+	for (i = 0; i < class->pages_per_zspage; i++) {
+		struct page *page;
+
+		page = ops->alloc(flags);
+		if (!page)
+			goto cleanup;
+
+		INIT_LIST_HEAD(&page->lru);
+		if (i == 0) {	/* first page */
+			SetPagePrivate(page);
+			set_page_private(page, 0);
+			first_page = page;
+			first_page->inuse = 0;
+		}
+		if (i == 1)
+			first_page->private = (unsigned long)page;
+		if (i >= 1)
+			page->first_page = first_page;
+		if (i >= 2)
+			list_add(&page->lru, &prev_page->lru);
+		if (i == class->pages_per_zspage - 1)	/* last page */
+			SetPagePrivate2(page);
+		prev_page = page;
+	}
+
+	init_zspage(first_page, class);
+
+	first_page->freelist = obj_location_to_handle(first_page, 0);
+	/* Maximum number of objects we can store in this zspage */
+	first_page->objects = class->pages_per_zspage * PAGE_SIZE / class->size;
+
+	error = 0; /* Success */
+
+cleanup:
+	if (unlikely(error) && first_page) {
+		free_zspage(ops, first_page);
+		first_page = NULL;
+	}
+
+	return first_page;
+}
+
+static struct page *find_get_zspage(struct size_class *class)
+{
+	int i;
+	struct page *page;
+
+	for (i = 0; i < _ZS_NR_FULLNESS_GROUPS; i++) {
+		page = class->fullness_list[i];
+		if (page)
+			break;
+	}
+
+	return page;
+}
+
+#ifdef USE_PGTABLE_MAPPING
+static inline int __zs_cpu_up(struct mapping_area *area)
+{
+	/*
+	 * Make sure we don't leak memory if a cpu UP notification
+	 * and zs_init() race and both call zs_cpu_up() on the same cpu
+	 */
+	if (area->vm)
+		return 0;
+	area->vm = alloc_vm_area(PAGE_SIZE * 2, NULL);
+	if (!area->vm)
+		return -ENOMEM;
+	return 0;
+}
+
+static inline void __zs_cpu_down(struct mapping_area *area)
+{
+	if (area->vm)
+		free_vm_area(area->vm);
+	area->vm = NULL;
+}
+
+static inline void *__zs_map_object(struct mapping_area *area,
+				struct page *pages[2], int off, int size)
+{
+	BUG_ON(map_vm_area(area->vm, PAGE_KERNEL, &pages));
+	area->vm_addr = area->vm->addr;
+	return area->vm_addr + off;
+}
+
+static inline void __zs_unmap_object(struct mapping_area *area,
+				struct page *pages[2], int off, int size)
+{
+	unsigned long addr = (unsigned long)area->vm_addr;
+	unsigned long end = addr + (PAGE_SIZE * 2);
+
+	flush_cache_vunmap(addr, end);
+	unmap_kernel_range_noflush(addr, PAGE_SIZE * 2);
+	local_flush_tlb_kernel_range(addr, end);
+}
+
+#else /* USE_PGTABLE_MAPPING */
+
+static inline int __zs_cpu_up(struct mapping_area *area)
+{
+	/*
+	 * Make sure we don't leak memory if a cpu UP notification
+	 * and zs_init() race and both call zs_cpu_up() on the same cpu
+	 */
+	if (area->vm_buf)
+		return 0;
+	area->vm_buf = (char *)__get_free_page(GFP_KERNEL);
+	if (!area->vm_buf)
+		return -ENOMEM;
+	return 0;
+}
+
+static inline void __zs_cpu_down(struct mapping_area *area)
+{
+	if (area->vm_buf)
+		free_page((unsigned long)area->vm_buf);
+	area->vm_buf = NULL;
+}
+
+static void *__zs_map_object(struct mapping_area *area,
+			struct page *pages[2], int off, int size)
+{
+	int sizes[2];
+	void *addr;
+	char *buf = area->vm_buf;
+
+	/* disable page faults to match kmap_atomic() return conditions */
+	pagefault_disable();
+
+	/* no read fastpath */
+	if (area->vm_mm == ZS_MM_WO)
+		goto out;
+
+	sizes[0] = PAGE_SIZE - off;
+	sizes[1] = size - sizes[0];
+
+	/* copy object to per-cpu buffer */
+	addr = kmap_atomic(pages[0]);
+	memcpy(buf, addr + off, sizes[0]);
+	kunmap_atomic(addr);
+	addr = kmap_atomic(pages[1]);
+	memcpy(buf + sizes[0], addr, sizes[1]);
+	kunmap_atomic(addr);
+out:
+	return area->vm_buf;
+}
+
+static void __zs_unmap_object(struct mapping_area *area,
+			struct page *pages[2], int off, int size)
+{
+	int sizes[2];
+	void *addr;
+	char *buf = area->vm_buf;
+
+	/* no write fastpath */
+	if (area->vm_mm == ZS_MM_RO)
+		goto out;
+
+	sizes[0] = PAGE_SIZE - off;
+	sizes[1] = size - sizes[0];
+
+	/* copy per-cpu buffer to object */
+	addr = kmap_atomic(pages[0]);
+	memcpy(addr + off, buf, sizes[0]);
+	kunmap_atomic(addr);
+	addr = kmap_atomic(pages[1]);
+	memcpy(addr, buf + sizes[0], sizes[1]);
+	kunmap_atomic(addr);
+
+out:
+	/* enable page faults to match kunmap_atomic() return conditions */
+	pagefault_enable();
+}
+
+#endif /* USE_PGTABLE_MAPPING */
+
+static int zs_cpu_notifier(struct notifier_block *nb, unsigned long action,
+				void *pcpu)
+{
+	int ret, cpu = (long)pcpu;
+	struct mapping_area *area;
+
+	switch (action) {
+	case CPU_UP_PREPARE:
+		area = &per_cpu(zs_map_area, cpu);
+		ret = __zs_cpu_up(area);
+		if (ret)
+			return notifier_from_errno(ret);
+		break;
+	case CPU_DEAD:
+	case CPU_UP_CANCELED:
+		area = &per_cpu(zs_map_area, cpu);
+		__zs_cpu_down(area);
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block zs_cpu_nb = {
+	.notifier_call = zs_cpu_notifier
+};
+
+static void zs_exit(void)
+{
+	int cpu;
+
+	for_each_online_cpu(cpu)
+		zs_cpu_notifier(NULL, CPU_DEAD, (void *)(long)cpu);
+	unregister_cpu_notifier(&zs_cpu_nb);
+}
+
+static int zs_init(void)
+{
+	int cpu, ret;
+
+	register_cpu_notifier(&zs_cpu_nb);
+	for_each_online_cpu(cpu) {
+		ret = zs_cpu_notifier(NULL, CPU_UP_PREPARE, (void *)(long)cpu);
+		if (notifier_to_errno(ret))
+			goto fail;
+	}
+	return 0;
+fail:
+	zs_exit();
+	return notifier_to_errno(ret);
+}
+
+struct zs_pool *zs_create_pool(gfp_t flags, struct zs_ops *ops)
+{
+	int i, ovhd_size;
+	struct zs_pool *pool;
+
+	ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
+	pool = kzalloc(ovhd_size, flags);
+	if (!pool)
+		return NULL;
+
+	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
+		int size;
+		struct size_class *class;
+
+		size = ZS_MIN_ALLOC_SIZE + i * ZS_SIZE_CLASS_DELTA;
+		if (size > ZS_MAX_ALLOC_SIZE)
+			size = ZS_MAX_ALLOC_SIZE;
+
+		class = &pool->size_class[i];
+		class->size = size;
+		class->index = i;
+		spin_lock_init(&class->lock);
+		class->pages_per_zspage = get_pages_per_zspage(size);
+
+	}
+
+	if (ops)
+		pool->ops = ops;
+	else
+		pool->ops = &zs_default_ops;
+
+	return pool;
+}
+EXPORT_SYMBOL_GPL(zs_create_pool);
+
+void zs_destroy_pool(struct zs_pool *pool)
+{
+	int i;
+
+	for (i = 0; i < ZS_SIZE_CLASSES; i++) {
+		int fg;
+		struct size_class *class = &pool->size_class[i];
+
+		for (fg = 0; fg < _ZS_NR_FULLNESS_GROUPS; fg++) {
+			if (class->fullness_list[fg]) {
+				pr_info("Freeing non-empty class with size "
+					"%db, fullness group %d\n",
+					class->size, fg);
+			}
+		}
+	}
+	kfree(pool);
+}
+EXPORT_SYMBOL_GPL(zs_destroy_pool);
+
+/**
+ * zs_malloc - Allocate block of given size from pool.
+ * @pool: pool to allocate from
+ * @size: size of block to allocate
+ *
+ * On success, handle to the allocated object is returned,
+ * otherwise 0.
+ * Allocation requests with size > ZS_MAX_ALLOC_SIZE will fail.
+ */
+unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags)
+{
+	unsigned long obj;
+	struct link_free *link;
+	int class_idx;
+	struct size_class *class;
+
+	struct page *first_page, *m_page;
+	unsigned long m_objidx, m_offset;
+
+	if (unlikely(!size || size > ZS_MAX_ALLOC_SIZE))
+		return 0;
+
+	class_idx = get_size_class_index(size);
+	class = &pool->size_class[class_idx];
+	BUG_ON(class_idx != class->index);
+
+	spin_lock(&class->lock);
+	first_page = find_get_zspage(class);
+
+	if (!first_page) {
+		spin_unlock(&class->lock);
+		first_page = alloc_zspage(pool->ops, class, flags);
+		if (unlikely(!first_page))
+			return 0;
+
+		set_zspage_mapping(first_page, class->index, ZS_EMPTY);
+		spin_lock(&class->lock);
+		class->pages_allocated += class->pages_per_zspage;
+	}
+
+	obj = (unsigned long)first_page->freelist;
+	obj_handle_to_location(obj, &m_page, &m_objidx);
+	m_offset = obj_idx_to_offset(m_page, m_objidx, class->size);
+
+	link = (struct link_free *)kmap_atomic(m_page) +
+					m_offset / sizeof(*link);
+	first_page->freelist = link->next;
+	memset(link, POISON_INUSE, sizeof(*link));
+	kunmap_atomic(link);
+
+	first_page->inuse++;
+	/* Now move the zspage to another fullness group, if required */
+	fix_fullness_group(pool, first_page);
+	spin_unlock(&class->lock);
+
+	return obj;
+}
+EXPORT_SYMBOL_GPL(zs_malloc);
+
+void zs_free(struct zs_pool *pool, unsigned long obj)
+{
+	struct link_free *link;
+	struct page *first_page, *f_page;
+	unsigned long f_objidx, f_offset;
+
+	int class_idx;
+	struct size_class *class;
+	enum fullness_group fullness;
+
+	if (unlikely(!obj))
+		return;
+
+	obj_handle_to_location(obj, &f_page, &f_objidx);
+	first_page = get_first_page(f_page);
+
+	get_zspage_mapping(first_page, &class_idx, &fullness);
+	class = &pool->size_class[class_idx];
+	f_offset = obj_idx_to_offset(f_page, f_objidx, class->size);
+
+	spin_lock(&class->lock);
+
+	/* Insert this object in containing zspage's freelist */
+	link = (struct link_free *)((unsigned char *)kmap_atomic(f_page)
+							+ f_offset);
+	link->next = first_page->freelist;
+	kunmap_atomic(link);
+	first_page->freelist = (void *)obj;
+
+	first_page->inuse--;
+	fullness = fix_fullness_group(pool, first_page);
+
+	if (fullness == ZS_EMPTY)
+		class->pages_allocated -= class->pages_per_zspage;
+
+	spin_unlock(&class->lock);
+
+	if (fullness == ZS_EMPTY)
+		free_zspage(pool->ops, first_page);
+}
+EXPORT_SYMBOL_GPL(zs_free);
+
+/**
+ * zs_map_object - get address of allocated object from handle.
+ * @pool: pool from which the object was allocated
+ * @handle: handle returned from zs_malloc
+ *
+ * Before using an object allocated from zs_malloc, it must be mapped using
+ * this function. When done with the object, it must be unmapped using
+ * zs_unmap_object.
+ *
+ * Only one object can be mapped per cpu at a time. There is no protection
+ * against nested mappings.
+ *
+ * This function returns with preemption and page faults disabled.
+*/
+void *zs_map_object(struct zs_pool *pool, unsigned long handle,
+			enum zs_mapmode mm)
+{
+	struct page *page;
+	unsigned long obj_idx, off;
+
+	unsigned int class_idx;
+	enum fullness_group fg;
+	struct size_class *class;
+	struct mapping_area *area;
+	struct page *pages[2];
+
+	BUG_ON(!handle);
+
+	/*
+	 * Because we use per-cpu mapping areas shared among the
+	 * pools/users, we can't allow mapping in interrupt context
+	 * because it can corrupt another users mappings.
+	 */
+	BUG_ON(in_interrupt());
+
+	obj_handle_to_location(handle, &page, &obj_idx);
+	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
+	class = &pool->size_class[class_idx];
+	off = obj_idx_to_offset(page, obj_idx, class->size);
+
+	area = &get_cpu_var(zs_map_area);
+	area->vm_mm = mm;
+	if (off + class->size <= PAGE_SIZE) {
+		/* this object is contained entirely within a page */
+		area->vm_addr = kmap_atomic(page);
+		return area->vm_addr + off;
+	}
+
+	/* this object spans two pages */
+	pages[0] = page;
+	pages[1] = get_next_page(page);
+	BUG_ON(!pages[1]);
+
+	return __zs_map_object(area, pages, off, class->size);
+}
+EXPORT_SYMBOL_GPL(zs_map_object);
+
+void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
+{
+	struct page *page;
+	unsigned long obj_idx, off;
+
+	unsigned int class_idx;
+	enum fullness_group fg;
+	struct size_class *class;
+	struct mapping_area *area;
+
+	BUG_ON(!handle);
+
+	obj_handle_to_location(handle, &page, &obj_idx);
+	get_zspage_mapping(get_first_page(page), &class_idx, &fg);
+	class = &pool->size_class[class_idx];
+	off = obj_idx_to_offset(page, obj_idx, class->size);
+
+	area = &__get_cpu_var(zs_map_area);
+	if (off + class->size <= PAGE_SIZE)
+		kunmap_atomic(area->vm_addr);
+	else {
+		struct page *pages[2];
+
+		pages[0] = page;
+		pages[1] = get_next_page(page);
+		BUG_ON(!pages[1]);
+
+		__zs_unmap_object(area, pages, off, class->size);
+	}
+	put_cpu_var(zs_map_area);
+}
+EXPORT_SYMBOL_GPL(zs_unmap_object);
+
+u64 zs_get_total_size_bytes(struct zs_pool *pool)
+{
+	int i;
+	u64 npages = 0;
+
+	for (i = 0; i < ZS_SIZE_CLASSES; i++)
+		npages += pool->size_class[i].pages_allocated;
+
+	return npages << PAGE_SHIFT;
+}
+EXPORT_SYMBOL_GPL(zs_get_total_size_bytes);
+
+module_init(zs_init);
+module_exit(zs_exit);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Nitin Gupta <ngupta@vflare.org>");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 7/8] zswap: add to mm/
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (5 preceding siblings ...)
  2012-12-11 21:56 ` [PATCH 6/8] zsmalloc: promote to lib/ Seth Jennings
@ 2012-12-11 21:56 ` Seth Jennings
  2013-01-03 16:07   ` Seth Jennings
  2012-12-11 21:56 ` [PATCH 8/8] zswap: add documentation Seth Jennings
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

zswap is a thin compression backend for frontswap. It receives
pages from frontswap and attempts to store them in a compressed
memory pool, resulting in an effective partial memory reclaim and
dramatically reduced swap device I/O.

Additional, in most cases, pages can be retrieved from this
compressed store much more quickly than reading from tradition
swap devices resulting in faster performance for many workloads.

This patch adds the zswap driver to mm/

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 include/linux/swap.h |    4 +
 mm/Kconfig           |   15 +
 mm/Makefile          |    1 +
 mm/page_io.c         |   22 +-
 mm/swap_state.c      |    2 +-
 mm/zswap.c           | 1077 ++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1115 insertions(+), 6 deletions(-)
 create mode 100644 mm/zswap.c

diff --git a/include/linux/swap.h b/include/linux/swap.h
index 68df9c1..98981f0 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -321,6 +321,9 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t ent)
 /* linux/mm/page_io.c */
 extern int swap_readpage(struct page *);
 extern int swap_writepage(struct page *page, struct writeback_control *wbc);
+extern void end_swap_bio_write(struct bio *bio, int err);
+extern int __swap_writepage(struct page *page, struct writeback_control *wbc,
+	void (*end_write_func)(struct bio *, int));
 extern int swap_set_page_dirty(struct page *page);
 extern void end_swap_bio_read(struct bio *bio, int err);
 
@@ -335,6 +338,7 @@ extern struct address_space swapper_space;
 extern void show_swap_cache_info(void);
 extern int add_to_swap(struct page *);
 extern int add_to_swap_cache(struct page *, swp_entry_t, gfp_t);
+extern int __add_to_swap_cache(struct page *page, swp_entry_t entry);
 extern void __delete_from_swap_cache(struct page *);
 extern void delete_from_swap_cache(struct page *);
 extern void free_page_and_swap_cache(struct page *);
diff --git a/mm/Kconfig b/mm/Kconfig
index 1680a012..68cd1b6 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -435,3 +435,18 @@ config FRONTSWAP
 	  and swap data is stored as normal on the matching swap device.
 
 	  If unsure, say Y to enable frontswap.
+
+config ZSWAP
+	bool "In-kernel swap page compression"
+	depends on FRONTSWAP && CRYPTO
+	select CRYPTO_LZO
+	select ZSMALLOC
+	default n
+	help
+	  Zswap is a backend for the frontswap mechanism in the VMM.
+	  It receives pages from frontswap and attempts to store them
+	  in a compressed memory pool, resulting in an effective
+	  partial memory reclaim.  In addition, pages and be retrieved
+	  from this compressed store much faster than most tradition
+	  swap devices resulting in reduced I/O and faster performance
+	  for many workloads.
diff --git a/mm/Makefile b/mm/Makefile
index 3a46287..1b1ed5c 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_HAVE_MEMBLOCK) += memblock.o
 obj-$(CONFIG_BOUNCE)	+= bounce.o
 obj-$(CONFIG_SWAP)	+= page_io.o swap_state.o swapfile.o
 obj-$(CONFIG_FRONTSWAP)	+= frontswap.o
+obj-$(CONFIG_ZSWAP)	+= zswap.o
 obj-$(CONFIG_HAS_DMA)	+= dmapool.o
 obj-$(CONFIG_HUGETLBFS)	+= hugetlb.o
 obj-$(CONFIG_NUMA) 	+= mempolicy.o
diff --git a/mm/page_io.c b/mm/page_io.c
index 78eee32..56276fe 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -42,7 +42,7 @@ static struct bio *get_swap_bio(gfp_t gfp_flags,
 	return bio;
 }
 
-static void end_swap_bio_write(struct bio *bio, int err)
+void end_swap_bio_write(struct bio *bio, int err)
 {
 	const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
 	struct page *page = bio->bi_io_vec[0].bv_page;
@@ -179,15 +179,16 @@ bad_bmap:
 	goto out;
 }
 
+int __swap_writepage(struct page *page, struct writeback_control *wbc,
+	void (*end_write_func)(struct bio *, int));
+
 /*
  * We may have stale swap cache pages in memory: notice
  * them here and get rid of the unnecessary final write.
  */
 int swap_writepage(struct page *page, struct writeback_control *wbc)
 {
-	struct bio *bio;
-	int ret = 0, rw = WRITE;
-	struct swap_info_struct *sis = page_swap_info(page);
+	int ret = 0;
 
 	if (try_to_free_swap(page)) {
 		unlock_page(page);
@@ -199,6 +200,17 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
 		end_page_writeback(page);
 		goto out;
 	}
+	ret = __swap_writepage(page, wbc, end_swap_bio_write);
+out:
+	return ret;
+}
+
+int __swap_writepage(struct page *page, struct writeback_control *wbc,
+	void (*end_write_func)(struct bio *, int))
+{
+	struct bio *bio;
+	int ret = 0, rw = WRITE;
+	struct swap_info_struct *sis = page_swap_info(page);
 
 	if (sis->flags & SWP_FILE) {
 		struct kiocb kiocb;
@@ -226,7 +238,7 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
 		return ret;
 	}
 
-	bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
+	bio = get_swap_bio(GFP_NOIO, page, end_write_func);
 	if (bio == NULL) {
 		set_page_dirty(page);
 		unlock_page(page);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index d1f6c2d..95a8597 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -68,7 +68,7 @@ void show_swap_cache_info(void)
  * __add_to_swap_cache resembles add_to_page_cache_locked on swapper_space,
  * but sets SwapCache flag and private instead of mapping and index.
  */
-static int __add_to_swap_cache(struct page *page, swp_entry_t entry)
+int __add_to_swap_cache(struct page *page, swp_entry_t entry)
 {
 	int error;
 
diff --git a/mm/zswap.c b/mm/zswap.c
new file mode 100644
index 0000000..f05b26b
--- /dev/null
+++ b/mm/zswap.c
@@ -0,0 +1,1077 @@
+/*
+ * zswap-drv.c - zswap driver file
+ *
+ * zswap is a backend for frontswap that takes pages that are in the
+ * process of being swapped out and attempts to compress them and store
+ * them in a RAM-based memory pool.  This results in a significant I/O
+ * reduction on the real swap device and, in the case of a slow swap
+ * device, can also improve workload performance.
+ *
+ * Copyright (C) 2012  Seth Jennings <sjenning@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+*/
+
+#include <linux/module.h>
+#include <linux/cpu.h>
+#include <linux/highmem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/frontswap.h>
+#include <linux/rbtree.h>
+#include <linux/swap.h>
+#include <linux/crypto.h>
+#include <linux/mempool.h>
+#include <linux/zsmalloc.h>
+
+#include <linux/mm_types.h>
+#include <linux/page-flags.h>
+#include <linux/swapops.h>
+#include <linux/writeback.h>
+#include <linux/pagemap.h>
+
+/*********************************
+* statistics
+**********************************/
+/* Number of memory pages used by the compressed pool */
+static atomic_t zswap_pool_pages = ATOMIC_INIT(0);
+/* The number of compressed pages currently stored in zswap */
+static atomic_t zswap_stored_pages = ATOMIC_INIT(0);
+/* The number of outstanding pages awaiting writeback */
+static atomic_t zswap_outstanding_flushes = ATOMIC_INIT(0);
+
+/*
+ * The statistics below are not protected from concurrent access for
+ * performance reasons so they may not be a 100% accurate.  However,
+ * the do provide useful information on roughly how many times a
+ * certain event is occurring.
+*/
+static u64 zswap_flushed_pages;
+static u64 zswap_reject_compress_poor;
+static u64 zswap_flush_attempted;
+static u64 zswap_reject_tmppage_fail;
+static u64 zswap_reject_flush_fail;
+static u64 zswap_reject_zsmalloc_fail;
+static u64 zswap_reject_kmemcache_fail;
+static u64 zswap_saved_by_flush;
+static u64 zswap_duplicate_entry;
+
+/*********************************
+* tunables
+**********************************/
+/* Enable/disable zswap (enabled by default, fixed at boot for now) */
+static bool zswap_enabled;
+module_param_named(enabled, zswap_enabled, bool, 0);
+
+/* Compressor to be used by zswap (fixed at boot for now) */
+#define ZSWAP_COMPRESSOR_DEFAULT "lzo"
+static char *zswap_compressor = ZSWAP_COMPRESSOR_DEFAULT;
+module_param_named(compressor, zswap_compressor, charp, 0);
+
+/* The maximum percentage of memory that the compressed pool can occupy */
+static unsigned int zswap_max_pool_percent = 20;
+module_param_named(max_pool_percent,
+			zswap_max_pool_percent, uint, 0644);
+
+/*
+ * Maximum compression ratio, as as percentage, for an acceptable
+ * compressed page. Any pages that do not compress by at least
+ * this ratio will be rejected.
+*/
+static unsigned int zswap_max_compression_ratio = 80;
+module_param_named(max_compression_ratio,
+			zswap_max_compression_ratio, uint, 0644);
+
+/*********************************
+* compression functions
+**********************************/
+/* per-cpu compression transforms */
+static struct crypto_comp * __percpu *zswap_comp_pcpu_tfms;
+
+enum comp_op {
+	ZSWAP_COMPOP_COMPRESS,
+	ZSWAP_COMPOP_DECOMPRESS
+};
+
+static int zswap_comp_op(enum comp_op op, const u8 *src, unsigned int slen,
+				u8 *dst, unsigned int *dlen)
+{
+	struct crypto_comp *tfm;
+	int ret;
+
+	tfm = *per_cpu_ptr(zswap_comp_pcpu_tfms, get_cpu());
+	switch (op) {
+	case ZSWAP_COMPOP_COMPRESS:
+		ret = crypto_comp_compress(tfm, src, slen, dst, dlen);
+		break;
+	case ZSWAP_COMPOP_DECOMPRESS:
+		ret = crypto_comp_decompress(tfm, src, slen, dst, dlen);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	put_cpu();
+	return ret;
+}
+
+static int __init zswap_comp_init(void)
+{
+	if (!crypto_has_comp(zswap_compressor, 0, 0)) {
+		pr_info("zswap: %s compressor not available\n",
+			zswap_compressor);
+		/* fall back to default compressor */
+		zswap_compressor = ZSWAP_COMPRESSOR_DEFAULT;
+		if (!crypto_has_comp(zswap_compressor, 0, 0))
+			/* can't even load the default compressor */
+			return -ENODEV;
+	}
+	pr_info("zswap: using %s compressor\n", zswap_compressor);
+
+	/* alloc percpu transforms */
+	zswap_comp_pcpu_tfms = alloc_percpu(struct crypto_comp *);
+	if (!zswap_comp_pcpu_tfms)
+		return -ENOMEM;
+	return 0;
+}
+
+static void zswap_comp_exit(void)
+{
+	/* free percpu transforms */
+	if (zswap_comp_pcpu_tfms)
+		free_percpu(zswap_comp_pcpu_tfms);
+}
+
+/*********************************
+* data structures
+**********************************/
+struct zswap_entry {
+	struct rb_node rbnode;
+	struct list_head lru;
+	int refcount;
+	unsigned type;
+	pgoff_t offset;
+	unsigned long handle;
+	unsigned int length;
+};
+
+struct zswap_tree {
+	struct rb_root rbroot;
+	struct list_head lru;
+	spinlock_t lock;
+	struct zs_pool *pool;
+};
+
+static struct zswap_tree *zswap_trees[MAX_SWAPFILES];
+
+/*********************************
+* zswap entry functions
+**********************************/
+#define ZSWAP_KMEM_CACHE_NAME "zswap_entry_cache"
+static struct kmem_cache *zswap_entry_cache;
+
+static inline int zswap_entry_cache_create(void)
+{
+	zswap_entry_cache =
+		kmem_cache_create(ZSWAP_KMEM_CACHE_NAME,
+			sizeof(struct zswap_entry), 0, 0, NULL);
+	return (zswap_entry_cache == NULL);
+}
+
+static inline void zswap_entry_cache_destory(void)
+{
+	kmem_cache_destroy(zswap_entry_cache);
+}
+
+static inline struct zswap_entry *zswap_entry_cache_alloc(gfp_t gfp)
+{
+	struct zswap_entry *entry;
+	entry = kmem_cache_alloc(zswap_entry_cache, gfp);
+	if (!entry)
+		return NULL;
+	INIT_LIST_HEAD(&entry->lru);
+	entry->refcount = 1;
+	return entry;
+}
+
+static inline void zswap_entry_cache_free(struct zswap_entry *entry)
+{
+	kmem_cache_free(zswap_entry_cache, entry);
+}
+
+static inline void zswap_entry_get(struct zswap_entry *entry)
+{
+	entry->refcount++;
+}
+
+static inline int zswap_entry_put(struct zswap_entry *entry)
+{
+	entry->refcount--;
+	return entry->refcount;
+}
+
+/*********************************
+* rbtree functions
+**********************************/
+static struct zswap_entry *zswap_rb_search(struct rb_root *root, pgoff_t offset)
+{
+	struct rb_node *node = root->rb_node;
+	struct zswap_entry *entry;
+
+	while (node) {
+		entry = rb_entry(node, struct zswap_entry, rbnode);
+		if (entry->offset > offset)
+			node = node->rb_left;
+		else if (entry->offset < offset)
+			node = node->rb_right;
+		else
+			return entry;
+	}
+	return NULL;
+}
+
+/*
+ * In the case that a entry with the same offset is found, it a pointer to
+ * the existing entry is stored in dupentry and the function returns -EEXIST
+*/
+static int zswap_rb_insert(struct rb_root *root, struct zswap_entry *entry,
+			struct zswap_entry **dupentry)
+{
+	struct rb_node **link = &root->rb_node, *parent = NULL;
+	struct zswap_entry *myentry;
+
+	while (*link) {
+		parent = *link;
+		myentry = rb_entry(parent, struct zswap_entry, rbnode);
+		if (myentry->offset > entry->offset)
+			link = &(*link)->rb_left;
+		else if (myentry->offset < entry->offset)
+			link = &(*link)->rb_right;
+		else {
+			*dupentry = myentry;
+			return -EEXIST;
+		}
+	}
+	rb_link_node(&entry->rbnode, parent, link);
+	rb_insert_color(&entry->rbnode, root);
+	return 0;
+}
+
+/*********************************
+* per-cpu code
+**********************************/
+static DEFINE_PER_CPU(u8 *, zswap_dstmem);
+
+static int __zswap_cpu_notifier(unsigned long action, unsigned long cpu)
+{
+	struct crypto_comp *tfm;
+	u8 *dst;
+
+	switch (action) {
+	case CPU_UP_PREPARE:
+		tfm = crypto_alloc_comp(zswap_compressor, 0, 0);
+		if (IS_ERR(tfm)) {
+			pr_err("zswap: can't allocate compressor transform\n");
+			return NOTIFY_BAD;
+		}
+		*per_cpu_ptr(zswap_comp_pcpu_tfms, cpu) = tfm;
+		dst = (u8 *)__get_free_pages(GFP_KERNEL, 1);
+		if (!dst) {
+			pr_err("zswap: can't allocate compressor buffer\n");
+			crypto_free_comp(tfm);
+			*per_cpu_ptr(zswap_comp_pcpu_tfms, cpu) = NULL;
+			return NOTIFY_BAD;
+		}
+		per_cpu(zswap_dstmem, cpu) = dst;
+		break;
+	case CPU_DEAD:
+	case CPU_UP_CANCELED:
+		tfm = *per_cpu_ptr(zswap_comp_pcpu_tfms, cpu);
+		if (tfm) {
+			crypto_free_comp(tfm);
+			*per_cpu_ptr(zswap_comp_pcpu_tfms, cpu) = NULL;
+		}
+		dst = per_cpu(zswap_dstmem, cpu);
+		if (dst) {
+			free_pages((unsigned long)dst, 1);
+			per_cpu(zswap_dstmem, cpu) = NULL;
+		}
+		break;
+	default:
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+static int zswap_cpu_notifier(struct notifier_block *nb,
+				unsigned long action, void *pcpu)
+{
+	unsigned long cpu = (unsigned long)pcpu;
+	return __zswap_cpu_notifier(action, cpu);
+}
+
+static struct notifier_block zswap_cpu_notifier_block = {
+	.notifier_call = zswap_cpu_notifier
+};
+
+static int zswap_cpu_init(void)
+{
+	unsigned long cpu;
+
+	get_online_cpus();
+	for_each_online_cpu(cpu)
+		if (__zswap_cpu_notifier(CPU_UP_PREPARE, cpu) != NOTIFY_OK)
+			goto cleanup;
+	register_cpu_notifier(&zswap_cpu_notifier_block);
+	put_online_cpus();
+	return 0;
+
+cleanup:
+	for_each_online_cpu(cpu)
+		__zswap_cpu_notifier(CPU_UP_CANCELED, cpu);
+	put_online_cpus();
+	return -ENOMEM;
+}
+
+#if 0
+/*
+ * #define out for now to avoid unused function warning
+ * but keep around in case future development needs it
+*/
+static void zswap_cpu_exit(void)
+{
+	unsigned long cpu;
+
+	get_online_cpus();
+	unregister_cpu_notifier(&zswap_cpu_notifier_block);
+	for_each_online_cpu(cpu)
+		(void)__zswap_cpu_notifier(CPU_UP_CANCELED, cpu);
+	put_online_cpus();
+}
+#endif
+
+/*********************************
+* zsmalloc callbacks
+**********************************/
+static mempool_t *zswap_page_pool;
+
+static u64 zswap_pool_limit_hit;
+
+static inline unsigned int zswap_max_pool_pages(void)
+{
+	return zswap_max_pool_percent * totalram_pages / 100;
+}
+
+static inline int zswap_page_pool_create(void)
+{
+	zswap_page_pool = mempool_create_page_pool(256, 0);
+	if (!zswap_page_pool)
+		return -ENOMEM;
+	return 0;
+}
+
+static inline void zswap_page_pool_destroy(void)
+{
+	mempool_destroy(zswap_page_pool);
+}
+
+static struct page *zswap_alloc_page(gfp_t flags)
+{
+	struct page *page;
+
+	if (atomic_read(&zswap_pool_pages) >= zswap_max_pool_pages()) {
+		zswap_pool_limit_hit++;
+		return NULL;
+	}
+	page = mempool_alloc(zswap_page_pool, flags);
+	if (page)
+		atomic_inc(&zswap_pool_pages);
+	return page;
+}
+
+static void zswap_free_page(struct page *page)
+{
+	mempool_free(page, zswap_page_pool);
+	atomic_dec(&zswap_pool_pages);
+}
+
+static struct zs_ops zswap_zs_ops = {
+	.alloc = zswap_alloc_page,
+	.free = zswap_free_page
+};
+
+/*********************************
+* flush code
+**********************************/
+static void zswap_end_swap_write(struct bio *bio, int err)
+{
+	end_swap_bio_write(bio, err);
+	atomic_dec(&zswap_outstanding_flushes);
+	zswap_flushed_pages++;
+}
+
+/*
+ * zswap_get_swap_cache_page
+ *
+ * This is an adaption of read_swap_cache_async()
+ *
+ * If success, page is returned in retpage
+ * Returns 0 if page was already in the swap cache, page is not locked
+ * Returns 1 if the new page needs to be populated, page is locked
+ */
+static int zswap_get_swap_cache_page(swp_entry_t entry,
+				struct page **retpage)
+{
+	struct page *found_page, *new_page = NULL;
+	int err;
+
+	*retpage = NULL;
+	do {
+		/*
+		 * First check the swap cache.  Since this is normally
+		 * called after lookup_swap_cache() failed, re-calling
+		 * that would confuse statistics.
+		 */
+		found_page = find_get_page(&swapper_space, entry.val);
+		if (found_page)
+			break;
+
+		/*
+		 * Get a new page to read into from swap.
+		 */
+		if (!new_page) {
+			new_page = alloc_page(GFP_KERNEL);
+			if (!new_page)
+				break; /* Out of memory */
+		}
+
+		/*
+		 * call radix_tree_preload() while we can wait.
+		 */
+		err = radix_tree_preload(GFP_KERNEL);
+		if (err)
+			break;
+
+		/*
+		 * Swap entry may have been freed since our caller observed it.
+		 */
+		err = swapcache_prepare(entry);
+		if (err == -EEXIST) { /* seems racy */
+			radix_tree_preload_end();
+			continue;
+		}
+		if (err) { /* swp entry is obsolete ? */
+			radix_tree_preload_end();
+			break;
+		}
+
+		/* May fail (-ENOMEM) if radix-tree node allocation failed. */
+		__set_page_locked(new_page);
+		SetPageSwapBacked(new_page);
+		err = __add_to_swap_cache(new_page, entry);
+		if (likely(!err)) {
+			radix_tree_preload_end();
+			lru_cache_add_anon(new_page);
+			*retpage = new_page;
+			return 1;
+		}
+		radix_tree_preload_end();
+		ClearPageSwapBacked(new_page);
+		__clear_page_locked(new_page);
+		/*
+		 * add_to_swap_cache() doesn't return -EEXIST, so we can safely
+		 * clear SWAP_HAS_CACHE flag.
+		 */
+		swapcache_free(entry, NULL);
+	} while (err != -ENOMEM);
+
+	if (new_page)
+		page_cache_release(new_page);
+	if (!found_page)
+		return -ENOMEM;
+	*retpage = found_page;
+	return 0;
+}
+
+static int zswap_flush_entry(struct zswap_entry *entry)
+{
+	unsigned long type = entry->type;
+	struct zswap_tree *tree = zswap_trees[type];
+	struct page *page;
+	swp_entry_t swpentry;
+	u8 *src, *dst;
+	unsigned int dlen;
+	int ret, refcount;
+	struct writeback_control wbc = {
+		.sync_mode = WB_SYNC_NONE,
+	};
+
+	/* get/allocate page in the swap cache */
+	swpentry = swp_entry(type, entry->offset);
+	ret = zswap_get_swap_cache_page(swpentry, &page);
+	if (ret < 0)
+		return ret;
+	else if (ret) {
+		/* decompress */
+		dlen = PAGE_SIZE;
+		src = zs_map_object(tree->pool, entry->handle, ZS_MM_RO);
+		dst = kmap_atomic(page);
+		ret = zswap_comp_op(ZSWAP_COMPOP_DECOMPRESS, src, entry->length,
+				dst, &dlen);
+		kunmap_atomic(dst);
+		zs_unmap_object(tree->pool, entry->handle);
+		BUG_ON(ret);
+		BUG_ON(dlen != PAGE_SIZE);
+		SetPageUptodate(page);
+	} else {
+		/* page is already in the swap cache, ignore for now */
+		spin_lock(&tree->lock);
+		refcount = zswap_entry_put(entry);
+		spin_unlock(&tree->lock);
+
+		if (likely(refcount))
+			return 0;
+
+		/* if the refcount is zero, invalidate must have come in */
+		/* free */
+		zs_free(tree->pool, entry->handle);
+		zswap_entry_cache_free(entry);
+		atomic_dec(&zswap_stored_pages);
+
+		return 0;
+	}
+
+	/* start writeback */
+	SetPageReclaim(page);
+	/*
+	 * Return value is ignored here because it doesn't change anything
+	 * for us.  Page is returned unlocked.
+	 */
+	(void)__swap_writepage(page, &wbc, zswap_end_swap_write);
+	page_cache_release(page);
+	atomic_inc(&zswap_outstanding_flushes);
+
+	/* remove */
+	spin_lock(&tree->lock);
+	refcount = zswap_entry_put(entry);
+	if (refcount > 1) {
+		/* load in progress, load will free */
+		spin_unlock(&tree->lock);
+		return 0;
+	}
+	if (refcount == 1)
+		/* no invalidate yet, remove from rbtree */
+		rb_erase(&entry->rbnode, &tree->rbroot);
+	spin_unlock(&tree->lock);
+
+	/* free */
+	zs_free(tree->pool, entry->handle);
+	zswap_entry_cache_free(entry);
+	atomic_dec(&zswap_stored_pages);
+
+	return 0;
+}
+
+static void zswap_flush_entries(unsigned type, int nr)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry;
+	int i, ret;
+
+/*
+ * This limits is arbitrary for now until a better
+ * policy can be implemented. This is so we don't
+ * eat all of RAM decompressing pages for writeback.
+ */
+#define ZSWAP_MAX_OUTSTANDING_FLUSHES 64
+	if (atomic_read(&zswap_outstanding_flushes) >
+		ZSWAP_MAX_OUTSTANDING_FLUSHES)
+		return;
+
+	for (i = 0; i < nr; i++) {
+		/* dequeue from lru */
+		spin_lock(&tree->lock);
+		if (list_empty(&tree->lru)) {
+			spin_unlock(&tree->lock);
+			break;
+		}
+		entry = list_first_entry(&tree->lru,
+				struct zswap_entry, lru);
+		list_del(&entry->lru);
+		zswap_entry_get(entry);
+		spin_unlock(&tree->lock);
+		ret = zswap_flush_entry(entry);
+		if (ret) {
+			/* put back on the lru */
+			spin_lock(&tree->lock);
+			list_add(&entry->lru, &tree->lru);
+			spin_unlock(&tree->lock);
+		} else {
+			if (atomic_read(&zswap_outstanding_flushes) >
+				ZSWAP_MAX_OUTSTANDING_FLUSHES)
+				break;
+		}
+	}
+}
+
+/*******************************************
+* page pool for temporary compression result
+********************************************/
+#define ZSWAP_TMPPAGE_POOL_PAGES 16
+static LIST_HEAD(zswap_tmppage_list);
+static DEFINE_SPINLOCK(zswap_tmppage_lock);
+
+static void zswap_tmppage_pool_destroy(void)
+{
+	struct page *page, *tmppage;
+
+	spin_lock(&zswap_tmppage_lock);
+	list_for_each_entry_safe(page, tmppage, &zswap_tmppage_list, lru) {
+		list_del(&page->lru);
+		__free_pages(page, 1);
+	}
+	spin_unlock(&zswap_tmppage_lock);
+}
+
+static int zswap_tmppage_pool_create(void)
+{
+	int i;
+	struct page *page;
+
+	for (i = 0; i < ZSWAP_TMPPAGE_POOL_PAGES; i++) {
+		page = alloc_pages(GFP_KERNEL, 1);
+		if (!page) {
+			zswap_tmppage_pool_destroy();
+			return -ENOMEM;
+		}
+		spin_lock(&zswap_tmppage_lock);
+		list_add(&page->lru, &zswap_tmppage_list);
+		spin_unlock(&zswap_tmppage_lock);
+	}
+	return 0;
+}
+
+static inline struct page *zswap_tmppage_alloc(void)
+{
+	struct page *page;
+
+	spin_lock(&zswap_tmppage_lock);
+	if (list_empty(&zswap_tmppage_list)) {
+		spin_unlock(&zswap_tmppage_lock);
+		return NULL;
+	}
+	page = list_first_entry(&zswap_tmppage_list, struct page, lru);
+	list_del(&page->lru);
+	spin_unlock(&zswap_tmppage_lock);
+	return page;
+}
+
+static inline void zswap_tmppage_free(struct page *page)
+{
+	spin_lock(&zswap_tmppage_lock);
+	list_add(&page->lru, &zswap_tmppage_list);
+	spin_unlock(&zswap_tmppage_lock);
+}
+
+/*********************************
+* frontswap hooks
+**********************************/
+/* attempts to compress and store an single page */
+static int zswap_fs_store(unsigned type, pgoff_t offset, struct page *page)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry, *dupentry;
+	int ret;
+	unsigned int dlen = PAGE_SIZE;
+	unsigned long handle;
+	char *buf;
+	u8 *src, *dst, *tmpdst;
+	struct page *tmppage;
+	bool flush_attempted = 0;
+
+	if (!tree) {
+		ret = -ENODEV;
+		goto reject;
+	}
+
+	/* compress */
+	dst = get_cpu_var(zswap_dstmem);
+	src = kmap_atomic(page);
+	ret = zswap_comp_op(ZSWAP_COMPOP_COMPRESS, src, PAGE_SIZE, dst, &dlen);
+	kunmap_atomic(src);
+	if (ret) {
+		ret = -EINVAL;
+		goto freepage;
+	}
+	if ((dlen * 100 / PAGE_SIZE) > zswap_max_compression_ratio) {
+		zswap_reject_compress_poor++;
+		ret = -E2BIG;
+		goto freepage;
+	}
+
+	/* store */
+	handle = zs_malloc(tree->pool, dlen,
+		__GFP_NORETRY | __GFP_HIGHMEM | __GFP_NOMEMALLOC |
+			__GFP_NOWARN);
+	if (!handle) {
+		zswap_flush_attempted++;
+		/*
+		 * Copy compressed buffer out of per-cpu storage so
+		 * we can re-enable preemption.
+		*/
+		tmppage = zswap_tmppage_alloc();
+		if (!tmppage) {
+			zswap_reject_tmppage_fail++;
+			ret = -ENOMEM;
+			goto freepage;
+		}
+		flush_attempted = 1;
+		tmpdst = page_address(tmppage);
+		memcpy(tmpdst, dst, dlen);
+		dst = tmpdst;
+		put_cpu_var(zswap_dstmem);
+
+		/* try to free up some space */
+		/* TODO: replace with more targeted policy */
+		zswap_flush_entries(type, 16);
+		/* try again, allowing wait */
+		handle = zs_malloc(tree->pool, dlen,
+			__GFP_NORETRY | __GFP_HIGHMEM | __GFP_NOMEMALLOC |
+				__GFP_NOWARN);
+		if (!handle) {
+			/* still no space, fail */
+			zswap_reject_zsmalloc_fail++;
+			ret = -ENOMEM;
+			goto freepage;
+		}
+		zswap_saved_by_flush++;
+	}
+
+	buf = zs_map_object(tree->pool, handle, ZS_MM_WO);
+	memcpy(buf, dst, dlen);
+	zs_unmap_object(tree->pool, handle);
+	if (flush_attempted)
+		zswap_tmppage_free(tmppage);
+	else
+		put_cpu_var(zswap_dstmem);
+
+	/* allocate entry */
+	entry = zswap_entry_cache_alloc(GFP_KERNEL);
+	if (!entry) {
+		zswap_reject_kmemcache_fail++;
+		ret = -ENOMEM;
+		goto reject;
+	}
+
+	/* populate entry */
+	entry->type = type;
+	entry->offset = offset;
+	entry->handle = handle;
+	entry->length = dlen;
+
+	/* map */
+	spin_lock(&tree->lock);
+	do {
+		ret = zswap_rb_insert(&tree->rbroot, entry, &dupentry);
+		if (ret == -EEXIST) {
+			zswap_duplicate_entry++;
+			/* remove from rbtree and lru */
+			rb_erase(&dupentry->rbnode, &tree->rbroot);
+			if (dupentry->lru.next != LIST_POISON1)
+				list_del(&dupentry->lru);
+			if (!zswap_entry_put(dupentry)) {
+				/* free */
+				zs_free(tree->pool, dupentry->handle);
+				zswap_entry_cache_free(dupentry);
+				atomic_dec(&zswap_stored_pages);
+			}
+		}
+	} while (ret == -EEXIST);
+	list_add_tail(&entry->lru, &tree->lru);
+	spin_unlock(&tree->lock);
+
+	/* update stats */
+	atomic_inc(&zswap_stored_pages);
+
+	return 0;
+
+freepage:
+	if (flush_attempted)
+		zswap_tmppage_free(tmppage);
+	else
+		put_cpu_var(zswap_dstmem);
+reject:
+	return ret;
+}
+
+/*
+ * returns 0 if the page was successfully decompressed
+ * return -1 on entry not found or error
+*/
+static int zswap_fs_load(unsigned type, pgoff_t offset, struct page *page)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry;
+	u8 *src, *dst;
+	unsigned int dlen;
+	int refcount;
+
+	/* find */
+	spin_lock(&tree->lock);
+	entry = zswap_rb_search(&tree->rbroot, offset);
+	if (!entry) {
+		/* entry was flushed */
+		spin_unlock(&tree->lock);
+		return -1;
+	}
+	zswap_entry_get(entry);
+
+	/* remove from lru */
+	if (entry->lru.next != LIST_POISON1)
+		list_del(&entry->lru);
+	spin_unlock(&tree->lock);
+
+	/* decompress */
+	dlen = PAGE_SIZE;
+	src = zs_map_object(tree->pool, entry->handle, ZS_MM_RO);
+	dst = kmap_atomic(page);
+	zswap_comp_op(ZSWAP_COMPOP_DECOMPRESS, src, entry->length,
+		dst, &dlen);
+	kunmap_atomic(dst);
+	zs_unmap_object(tree->pool, entry->handle);
+
+	spin_lock(&tree->lock);
+	refcount = zswap_entry_put(entry);
+	if (likely(refcount)) {
+		list_add_tail(&entry->lru, &tree->lru);
+		spin_unlock(&tree->lock);
+		return 0;
+	}
+	spin_unlock(&tree->lock);
+
+	/*
+	 * We don't have to unlink from the rbtree because zswap_flush_entry()
+	 * or zswap_fs_invalidate page() has already done this for us if we
+	 * are the last reference.
+	 */
+	/* free */
+	zs_free(tree->pool, entry->handle);
+	zswap_entry_cache_free(entry);
+	atomic_dec(&zswap_stored_pages);
+
+	return 0;
+}
+
+/* invalidates a single page */
+static void zswap_fs_invalidate_page(unsigned type, pgoff_t offset)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct zswap_entry *entry;
+	int refcount;
+
+	if (!tree)
+		return;
+
+	/* find */
+	spin_lock(&tree->lock);
+	entry = zswap_rb_search(&tree->rbroot, offset);
+	if (!entry) {
+		/* entry was flushed */
+		spin_unlock(&tree->lock);
+		return;
+	}
+
+	/* remove from rbtree and lru */
+	rb_erase(&entry->rbnode, &tree->rbroot);
+	if (entry->lru.next != LIST_POISON1)
+		list_del(&entry->lru);
+	refcount = zswap_entry_put(entry);
+	spin_unlock(&tree->lock);
+	if (refcount) {
+		/* must be flushing */
+		return;
+	}
+
+	/* free */
+	zs_free(tree->pool, entry->handle);
+	zswap_entry_cache_free(entry);
+	atomic_dec(&zswap_stored_pages);
+}
+
+/* invalidates all pages for the given swap type */
+static void zswap_fs_invalidate_area(unsigned type)
+{
+	struct zswap_tree *tree = zswap_trees[type];
+	struct rb_node *node, *next;
+	struct zswap_entry *entry;
+
+	if (!tree)
+		return;
+
+	/* walk the tree and free everything */
+	spin_lock(&tree->lock);
+	node = rb_first(&tree->rbroot);
+	while (node) {
+		entry = rb_entry(node, struct zswap_entry, rbnode);
+		zs_free(tree->pool, entry->handle);
+		next = rb_next(node);
+		zswap_entry_cache_free(entry);
+		node = next;
+	}
+	tree->rbroot = RB_ROOT;
+	INIT_LIST_HEAD(&tree->lru);
+	spin_unlock(&tree->lock);
+}
+
+/* NOTE: this is called in atomic context from swapon and must not sleep */
+static void zswap_fs_init(unsigned type)
+{
+	struct zswap_tree *tree;
+
+	tree = kzalloc(sizeof(struct zswap_tree), GFP_NOWAIT);
+	if (!tree)
+		goto err;
+	tree->pool = zs_create_pool(GFP_NOWAIT, &zswap_zs_ops);
+	if (!tree->pool)
+		goto freetree;
+	tree->rbroot = RB_ROOT;
+	INIT_LIST_HEAD(&tree->lru);
+	spin_lock_init(&tree->lock);
+	zswap_trees[type] = tree;
+	return;
+
+freetree:
+	kfree(tree);
+err:
+	pr_err("zswap: alloc failed, zswap disabled for swap type %d\n", type);
+}
+
+static struct frontswap_ops zswap_fs_ops = {
+	.store = zswap_fs_store,
+	.load = zswap_fs_load,
+	.invalidate_page = zswap_fs_invalidate_page,
+	.invalidate_area = zswap_fs_invalidate_area,
+	.init = zswap_fs_init
+};
+
+/*********************************
+* debugfs functions
+**********************************/
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+static struct dentry *zswap_debugfs_root;
+
+static int __init zswap_debugfs_init(void)
+{
+	if (!debugfs_initialized())
+		return -ENODEV;
+
+	zswap_debugfs_root = debugfs_create_dir("zswap", NULL);
+	if (!zswap_debugfs_root)
+		return -ENOMEM;
+
+	debugfs_create_u64("saved_by_flush", S_IRUGO,
+			zswap_debugfs_root, &zswap_saved_by_flush);
+	debugfs_create_u64("pool_limit_hit", S_IRUGO,
+			zswap_debugfs_root, &zswap_pool_limit_hit);
+	debugfs_create_u64("reject_flush_attempted", S_IRUGO,
+			zswap_debugfs_root, &zswap_flush_attempted);
+	debugfs_create_u64("reject_tmppage_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_tmppage_fail);
+	debugfs_create_u64("reject_flush_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_flush_fail);
+	debugfs_create_u64("reject_zsmalloc_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_zsmalloc_fail);
+	debugfs_create_u64("reject_kmemcache_fail", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_kmemcache_fail);
+	debugfs_create_u64("reject_compress_poor", S_IRUGO,
+			zswap_debugfs_root, &zswap_reject_compress_poor);
+	debugfs_create_u64("flushed_pages", S_IRUGO,
+			zswap_debugfs_root, &zswap_flushed_pages);
+	debugfs_create_u64("duplicate_entry", S_IRUGO,
+			zswap_debugfs_root, &zswap_duplicate_entry);
+	debugfs_create_atomic_t("pool_pages", S_IRUGO,
+			zswap_debugfs_root, &zswap_pool_pages);
+	debugfs_create_atomic_t("stored_pages", S_IRUGO,
+			zswap_debugfs_root, &zswap_stored_pages);
+	debugfs_create_atomic_t("outstanding_flushes", S_IRUGO,
+			zswap_debugfs_root, &zswap_outstanding_flushes);
+
+	return 0;
+}
+
+static void __exit zswap_debugfs_exit(void)
+{
+	if (zswap_debugfs_root)
+		debugfs_remove_recursive(zswap_debugfs_root);
+}
+#else
+static inline int __init zswap_debugfs_init(void)
+{
+	return 0;
+}
+
+static inline void __exit zswap_debugfs_exit(void) { }
+#endif
+
+/*********************************
+* module init and exit
+**********************************/
+static int __init init_zswap(void)
+{
+	if (!zswap_enabled)
+		return 0;
+
+	pr_info("loading zswap\n");
+	if (zswap_entry_cache_create()) {
+		pr_err("zswap: entry cache creation failed\n");
+		goto error;
+	}
+	if (zswap_page_pool_create()) {
+		pr_err("zswap: page pool initialization failed\n");
+		goto pagepoolfail;
+	}
+	if (zswap_tmppage_pool_create()) {
+		pr_err("zswap: workmem pool initialization failed\n");
+		goto tmppoolfail;
+	}
+	if (zswap_comp_init()) {
+		pr_err("zswap: compressor initialization failed\n");
+		goto compfail;
+	}
+	if (zswap_cpu_init()) {
+		pr_err("zswap: per-cpu initialization failed\n");
+		goto pcpufail;
+	}
+	frontswap_register_ops(&zswap_fs_ops);
+	if (zswap_debugfs_init())
+		pr_warn("zswap: debugfs initialization failed\n");
+	return 0;
+pcpufail:
+	zswap_comp_exit();
+compfail:
+	zswap_tmppage_pool_destroy();
+tmppoolfail:
+	zswap_page_pool_destroy();
+pagepoolfail:
+	zswap_entry_cache_destory();
+error:
+	return -ENOMEM;
+}
+/* must be late so crypto has time to come up */
+late_initcall(init_zswap);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Seth Jennings <sjenning@linux.vnet.ibm.com>");
+MODULE_DESCRIPTION("Compression backend for frontswap pages");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 8/8] zswap: add documentation
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (6 preceding siblings ...)
  2012-12-11 21:56 ` [PATCH 7/8] zswap: add to mm/ Seth Jennings
@ 2012-12-11 21:56 ` Seth Jennings
  2012-12-11 22:01 ` [PATCH 0/8] zswap: compressed swap caching Greg Kroah-Hartman
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-11 21:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Andrew Morton
  Cc: Seth Jennings, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

This patch adds the documentation file for the zswap functionality

Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
---
 Documentation/vm/zswap.txt |   74 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 Documentation/vm/zswap.txt

diff --git a/Documentation/vm/zswap.txt b/Documentation/vm/zswap.txt
new file mode 100644
index 0000000..f12d690
--- /dev/null
+++ b/Documentation/vm/zswap.txt
@@ -0,0 +1,74 @@
+Overview:
+
+Zswap is a lightweight compressed cache for swap pages. It takes
+pages that are in the process of being swapped out and attempts to
+compress them into a dynamically allocated RAM-based memory pool.
+If this process is successful, the writeback to the swap device is
+deferred and, in many cases, avoided completely.  This results in
+a significant I/O reduction and performance gains for systems that
+are swapping.
+
+Zswap provides compressed swap caching that basically trades CPU cycles
+for reduced swap I/O.  This trade-off can result in a significant
+performance improvement as reads to/writes from to the compressed
+cache almost always faster that reading from a swap device
+which incurs the latency of an asynchronous block I/O read.
+
+Some potential benefits:
+* Desktop/laptop users with limited RAM capacities can mitigate the
+    performance impact of swapping.
+* Overcommitted guests that share a common I/O resource can
+    dramatically reduce their swap I/O pressure, avoiding heavy
+    handed I/O throttling by the hypervisor.  This allows more work
+    to get done with less impact to the guest workload and guests
+    sharing the I/O subsystem
+* Users with SSDs as swap devices can extend the life of the device by
+    drastically reducing life-shortening writes.
+
+Zswap evicts pages from compressed cache on an LRU basis to the backing
+swap device when the compress pool reaches it size limit or the pool is
+unable to obtain additional pages from the buddy allocator.  This
+requirement had been identified in prior community discussions.
+
+To enabled zswap, the "enabled" attribute must be set to 1 at boot time.
+e.g. zswap.enabled=1
+
+Design:
+
+Zswap receives pages for compression through the Frontswap API and
+is able to evict pages from its own compressed pool on an LRU basis
+and write them back to the backing swap device in the case that the
+compressed pool is full or unable to secure additional pages from
+the buddy allocator.
+
+Zswap makes use of zsmalloc for the managing the compressed memory
+pool.  This is because zsmalloc is specifically designed to minimize
+fragmentation on large (> PAGE_SIZE/2) allocation sizes.  Each
+allocation in zsmalloc is not directly accessible by address.
+Rather, a handle is return by the allocation routine and that handle
+must be mapped before being accessed.  The compressed memory pool grows
+on demand and shrinks as compressed pages are freed.  The pool is
+not preallocated.
+
+When a swap page is passed from frontswap to zswap, zswap maintains
+a mapping of the swap entry, a combination of the swap type and swap
+offset, to the zsmalloc handle that references that compressed swap
+page.  This mapping is achieved with a red-black tree per swap type.
+The swap offset is the search key for the tree nodes.
+
+Zswap seeks to be simple in its policies.  Sysfs attributes allow for
+two user controlled policies:
+* max_compression_ratio - Maximum compression ratio, as as percentage,
+    for an acceptable compressed page. Any page that does not compress
+    by at least this ratio will be rejected.
+* max_pool_percent - The maximum percentage of memory that the compressed
+    pool can occupy.
+
+Zswap allows the compressor to be selected at kernel boot time by
+setting the “compressor” attribute.  The default compressor is lzo.
+e.g. zswap.compressor=deflate
+
+A debugfs interface is provided for various statistic about pool size,
+number of pages stored, and various counters for the reasons pages
+are rejected.
+
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH 0/8] zswap: compressed swap caching
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (7 preceding siblings ...)
  2012-12-11 21:56 ` [PATCH 8/8] zswap: add documentation Seth Jennings
@ 2012-12-11 22:01 ` Greg Kroah-Hartman
  2012-12-12 16:29   ` Seth Jennings
  2012-12-12 18:36 ` Seth Jennings
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 38+ messages in thread
From: Greg Kroah-Hartman @ 2012-12-11 22:01 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Andrew Morton, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

On Tue, Dec 11, 2012 at 03:55:58PM -0600, Seth Jennings wrote:
> Zswap Overview:

<snip>

Why are you sending this right at the start of the merge window, when
all of the people who need to review it are swamped with other work?

{sigh}

greg k-h

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 0/8] zswap: compressed swap caching
  2012-12-11 22:01 ` [PATCH 0/8] zswap: compressed swap caching Greg Kroah-Hartman
@ 2012-12-12 16:29   ` Seth Jennings
  2012-12-12 17:27     ` Dan Magenheimer
  0 siblings, 1 reply; 38+ messages in thread
From: Seth Jennings @ 2012-12-12 16:29 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Andrew Morton, Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk,
	Dan Magenheimer, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

On 12/11/2012 04:01 PM, Greg Kroah-Hartman wrote:
> On Tue, Dec 11, 2012 at 03:55:58PM -0600, Seth Jennings wrote:
>> Zswap Overview:
> 
> <snip>
> 
> Why are you sending this right at the start of the merge window, when
> all of the people who need to review it are swamped with other work?

Yes, sorry, poor timing :-/

I'm just looking for early feedback from those that are not swamped
doing merge window stuff.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 0/8] zswap: compressed swap caching
  2012-12-12 16:29   ` Seth Jennings
@ 2012-12-12 17:27     ` Dan Magenheimer
  2012-12-12 18:32       ` Seth Jennings
  0 siblings, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2012-12-12 17:27 UTC (permalink / raw)
  To: Seth Jennings, Greg Kroah-Hartman
  Cc: Andrew Morton, Nitin Gupta, Minchan Kim, Konrad Wilk,
	Robert Jennings, Jenifer Hopper, Mel Gorman, Johannes Weiner,
	Rik van Riel, Larry Woodman, linux-mm, linux-kernel, devel

> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> Subject: Re: [PATCH 0/8] zswap: compressed swap caching
> 
> On 12/11/2012 04:01 PM, Greg Kroah-Hartman wrote:
> > On Tue, Dec 11, 2012 at 03:55:58PM -0600, Seth Jennings wrote:
> >> Zswap Overview:
> >
> > <snip>
> >
> > Why are you sending this right at the start of the merge window, when
> > all of the people who need to review it are swamped with other work?
> 
> Yes, sorry, poor timing :-/
> 
> I'm just looking for early feedback from those that are not swamped
> doing merge window stuff.

Hi Seth --

Related, are you now comfortable with abandoning "zcache1" and
moving "zcache2" (now in drivers/staging/ramster in 3.7) to become
the one-and-only in-tree drivers/staging/zcache (with ramster
as a subdirectory and build option)?  It would be nice to get
rid of that artificial and confusing distinction as soon as possible,
especially if, due to zswap, you have no plans to continue to
maintain/enhance/promote zcache1 anymore.

If so, I'll work with Konrad to generate a drivers/staging
patch for Greg (post-window :-).

Thanks,
Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 0/8] zswap: compressed swap caching
  2012-12-12 17:27     ` Dan Magenheimer
@ 2012-12-12 18:32       ` Seth Jennings
  0 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-12 18:32 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

On 12/12/2012 11:27 AM, Dan Magenheimer wrote:
> Related, are you now comfortable with abandoning "zcache1" and
> moving "zcache2" (now in drivers/staging/ramster in 3.7) to become
> the one-and-only in-tree drivers/staging/zcache (with ramster
> as a subdirectory and build option)?  It would be nice to get
> rid of that artificial and confusing distinction as soon as possible,
> especially if, due to zswap, you have no plans to continue to
> maintain/enhance/promote zcache1 anymore.

Yes, that's fine by me.  I guess that didn't get said explicitly in
the last discussion so sorry for any confusion.

Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 0/8] zswap: compressed swap caching
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (8 preceding siblings ...)
  2012-12-11 22:01 ` [PATCH 0/8] zswap: compressed swap caching Greg Kroah-Hartman
@ 2012-12-12 18:36 ` Seth Jennings
  2012-12-12 22:49 ` Luigi Semenzato
  2013-01-03 16:01 ` Seth Jennings
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-12 18:36 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

Here are some addition performance metrics regarding the performance
improvements and I/O reductions that can be achieved using zswap as
measured by SPECjbb.

http://ibm.co/VCgHvM

Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 0/8] zswap: compressed swap caching
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (9 preceding siblings ...)
  2012-12-12 18:36 ` Seth Jennings
@ 2012-12-12 22:49 ` Luigi Semenzato
  2012-12-12 23:46   ` Dan Magenheimer
  2012-12-14 15:59   ` Seth Jennings
  2013-01-03 16:01 ` Seth Jennings
  11 siblings, 2 replies; 38+ messages in thread
From: Luigi Semenzato @ 2012-12-12 22:49 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

Just a couple of questions and comments as a user.  I apologize if
this is the wrong time to make them, feel free to ignore them.

1. It's becoming difficult to understand how zcache, zcache2, zram,
and now zswap, interact and/or overlap with each other.  For instance,
I am examining the possibility of using zcache2 and zram in parallel.
Should I also/instead consider zswap, using a plain RAM disk as the
swap device?

2. Zswap looks like a two-stage swap device, with one stage being
compressed memory.  I don't know if and where the abstraction breaks,
and what the implementation issues would be, but I would find it
easier to view and configure this as a two-stage swap, where one stage
is chosen as zcache (but could be something else).

3. As far as I can tell, none of these compressors has a good way of
balancing the amount of memory dedicated to compression against the
pressure on the rest of the memory.  On both zram and zswap, we set a
max size for the compressed data.  That size determines how much RAM
is left for the working set, which should remain uncompressed.  But
the size of the working set can vary significantly with the load.  If
we choose the size based on a worst-case working set, we'll be
underutilizing RAM on average.  If we choose it smaller than that, the
worst-case working set will cause excessive CPU use and thrashing.

Thanks!

P.S. For us, the situation in case 3 is improved from having
centralized control over all (relevant) processes.  If we can detect
thrashing, we can activate the tab discarder and decrease the load.


On Tue, Dec 11, 2012 at 1:55 PM, Seth Jennings
<sjenning@linux.vnet.ibm.com> wrote:
> Zswap Overview:
>
> Zswap is a lightweight compressed cache for swap pages. It takes
> pages that are in the process of being swapped out and attempts to
> compress them into a dynamically allocated RAM-based memory pool.
> If this process is successful, the writeback to the swap device is
> deferred and, in many cases, avoided completely.  This results in
> a significant I/O reduction and performance gains for systems that
> are swapping. The results of a kernel building benchmark indicate a
> runtime reduction of 53% and an I/O reduction 76% with zswap vs normal
> swapping with a kernel build under heavy memory pressure (see
> Performance section for more).
>
> Patchset Structure:
> 1-4: improvements/changes to zsmalloc
> 5:   add atomic_t get/set to debugfs
> 6:   promote zsmalloc to /lib
> 7-8: add zswap and documentation
>
> Targeting this for linux-next.
>
> Rationale:
>
> Zswap provides compressed swap caching that basically trades CPU cycles
> for reduced swap I/O.  This trade-off can result in a significant
> performance improvement as reads to/writes from to the compressed
> cache almost always faster that reading from a swap device
> which incurs the latency of an asynchronous block I/O read.
>
> Some potential benefits:
> * Desktop/laptop users with limited RAM capacities can mitigate the
>     performance impact of swapping.
> * Overcommitted guests that share a common I/O resource can
>     dramatically reduce their swap I/O pressure, avoiding heavy
>     handed I/O throttling by the hypervisor.  This allows more work
>     to get done with less impact to the guest workload and guests
>     sharing the I/O subsystem
> * Users with SSDs as swap devices can extend the life of the device by
>     drastically reducing life-shortening writes.
>
> Zswap evicts pages from compressed cache on an LRU basis to the backing
> swap device when the compress pool reaches it size limit or the pool is
> unable to obtain additional pages from the buddy allocator.  This
> requirement had been identified in prior community discussions.
>
> Compressed swap is also provided in zcache, along with page cache
> compression and RAM clustering through RAMSter. Zswap seeks to deliver
> the benefit of swap  compression to users in a discrete function.
> This design decision is akin to Unix design philosophy of doing one
> thing well, it leaves file cache compression and other features
> for separate code.
>
> Design:
>
> Zswap receives pages for compression through the Frontswap API and
> is able to evict pages from its own compressed pool on an LRU basis
> and write them back to the backing swap device in the case that the
> compressed pool is full or unable to secure additional pages from
> the buddy allocator.
>
> Zswap makes use of zsmalloc for the managing the compressed memory
> pool.  This is because zsmalloc is specifically designed to minimize
> fragmentation on large (> PAGE_SIZE/2) allocation sizes.  Each
> allocation in zsmalloc is not directly accessible by address.
> Rather, a handle is return by the allocation routine and that handle
> must be mapped before being accessed.  The compressed memory pool grows
> on demand and shrinks as compressed pages are freed.  The pool is
> not preallocated.
>
> When a swap page is passed from frontswap to zswap, zswap maintains
> a mapping of the swap entry, a combination of the swap type and swap
> offset, to the zsmalloc handle that references that compressed swap
> page.  This mapping is achieved with a red-black tree per swap type.
> The swap offset is the search key for the tree nodes.
>
> Zswap seeks to be simple in its policies.  Sysfs attributes allow for
> two user controlled policies:
> * max_compression_ratio - Maximum compression ratio, as as percentage,
>     for an acceptable compressed page. Any page that does not compress
>     by at least this ratio will be rejected.
> * max_pool_percent - The maximum percentage of memory that the compressed
>     pool can occupy.
>
> To enabled zswap, the "enabled" attribute must be set to 1 at boot time.
>
> Zswap allows the compressor to be selected at kernel boot time by
> setting the “compressor” attribute.  The default compressor is lzo.
>
> A debugfs interface is provided for various statistic about pool size,
> number of pages stored, and various counters for the reasons pages
> are rejected.
>
> Performance, Kernel Building:
>
> Setup
> ========
> Gentoo w/ kernel v3.7-rc7
> Quad-core i5-2500 @ 3.3GHz
> 512MB DDR3 1600MHz (limited with mem=512m on boot)
> Filesystem and swap on 80GB HDD (about 58MB/s with hdparm -t)
> majflt are major page faults reported by the time command
> pswpin/out is the delta of pswpin/out from /proc/vmstat before and after
> the make -jN
>
> Summary
> ========
> * Zswap reduces I/O and improves performance at all swap pressure levels.
>
> * Under heavy swaping at 24 threads, zswap reduced I/O by 76%, saving
>   over 1.5GB of I/O, and cut runtime in half.
>
> Details
> ========
> I/O (in pages)
>         base                            zswap                           change  change
> N       pswpin  pswpout majflt  I/O sum pswpin  pswpout majflt  I/O sum %I/O    MB
> 8       1       335     291     627     0       0       249     249     -60%    1
> 12      3688    14315   5290    23293   123     860     5954    6937    -70%    64
> 16      12711   46179   16803   75693   2936    7390    46092   56418   -25%    75
> 20      42178   133781  49898   225857  9460    28382   92951   130793  -42%    371
> 24      96079   357280  105242  558601  7719    18484   109309  135512  -76%    1653
>
> Runtime (in seconds)
> N       base    zswap   %change
> 8       107     107     0%
> 12      128     110     -14%
> 16      191     179     -6%
> 20      371     240     -35%
> 24      570     267     -53%
>
> %CPU utilization (out of 400% on 4 cpus)
> N       base    zswap   %change
> 8       317     319     1%
> 12      267     311     16%
> 16      179     191     7%
> 20      94      143     52%
> 24      60      128     113%
>
> Patchset is based on next-20121210
>
> Seth Jennings (8):
>   staging: zsmalloc: add gfp flags to zs_create_pool
>   staging: zsmalloc: remove unsed pool name
>   staging: zsmalloc: add page alloc/free callbacks
>   staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
>   debugfs: add get/set for atomic types
>   zsmalloc: promote to lib/
>   zswap: add to mm/
>   zswap: add documentation
>
>  Documentation/vm/zswap.txt               |   74 ++
>  drivers/staging/Kconfig                  |    2 -
>  drivers/staging/Makefile                 |    1 -
>  drivers/staging/zcache/zcache-main.c     |    7 +-
>  drivers/staging/zram/zram_drv.c          |    4 +-
>  drivers/staging/zram/zram_drv.h          |    3 +-
>  drivers/staging/zsmalloc/Kconfig         |   10 -
>  drivers/staging/zsmalloc/Makefile        |    3 -
>  drivers/staging/zsmalloc/zsmalloc-main.c | 1064 -----------------------------
>  drivers/staging/zsmalloc/zsmalloc.h      |   43 --
>  fs/debugfs/file.c                        |   42 ++
>  include/linux/debugfs.h                  |    2 +
>  include/linux/swap.h                     |    4 +
>  include/linux/zsmalloc.h                 |   49 ++
>  lib/Kconfig                              |   18 +
>  lib/Makefile                             |    1 +
>  lib/zsmalloc.c                           | 1076 +++++++++++++++++++++++++++++
>  mm/Kconfig                               |   15 +
>  mm/Makefile                              |    1 +
>  mm/page_io.c                             |   22 +-
>  mm/swap_state.c                          |    2 +-
>  mm/zswap.c                               | 1077 ++++++++++++++++++++++++++++++
>  22 files changed, 2383 insertions(+), 1137 deletions(-)
>  create mode 100644 Documentation/vm/zswap.txt
>  delete mode 100644 drivers/staging/zsmalloc/Kconfig
>  delete mode 100644 drivers/staging/zsmalloc/Makefile
>  delete mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c
>  delete mode 100644 drivers/staging/zsmalloc/zsmalloc.h
>  create mode 100644 include/linux/zsmalloc.h
>  create mode 100644 lib/zsmalloc.c
>  create mode 100644 mm/zswap.c
>
> --
> 1.7.9.5
>
> --
> To unsubscribe, send a message with 'unsubscribe linux-mm' in
> the body to majordomo@kvack.org.  For more info on Linux MM,
> see: http://www.linux-mm.org/ .
> Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>

^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 0/8] zswap: compressed swap caching
  2012-12-12 22:49 ` Luigi Semenzato
@ 2012-12-12 23:46   ` Dan Magenheimer
  2012-12-14 15:59   ` Seth Jennings
  1 sibling, 0 replies; 38+ messages in thread
From: Dan Magenheimer @ 2012-12-12 23:46 UTC (permalink / raw)
  To: Luigi Semenzato, Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

> From: Luigi Semenzato [mailto:semenzato@google.com]
> Subject: Re: [PATCH 0/8] zswap: compressed swap caching

Hi Luigi --

> Just a couple of questions and comments as a user.  I apologize if
> this is the wrong time to make them, feel free to ignore them.

IMHO now is a great time to address this as IMHO it doesn't
make much sense for MM developers to do detailed code-review
on any of these until the broader objectives of in-kernel
compression and the advantages/disadvantages of each are
thoroughly understood.  There may be room for more than one
solution, or may not be.

But... for me this is not so great a time... it's a rather
inconvenient time as some personal constraints over the
next few weeks will result in sporadic ability for me
to regularly participate in an email discussion.

Ultimately, this may be a great major topic for the next
MM Summit, though hopefully progress can be made before then.

And rather than hijack Seth's thread, it might be better
to start a new thread. :-)

> 1. It's becoming difficult to understand how zcache, zcache2, zram,
> and now zswap, interact and/or overlap with each other.  For instance,
> I am examining the possibility of using zcache2 and zram in parallel.
> Should I also/instead consider zswap, using a plain RAM disk as the
> swap device?

Yes, it seems to be a thousand flowers blooming!

I know Minchan earlier this year had talked about working
on a good comparison of the in-kernel compression options.
It might be good for Minchan or you or some other neutral
party to attempt that, with input from the developers of
the various options.

> 2. Zswap looks like a two-stage swap device, with one stage being
> compressed memory.  I don't know if and where the abstraction breaks,
> and what the implementation issues would be, but I would find it
> easier to view and configure this as a two-stage swap, where one stage
> is chosen as zcache (but could be something else).

Zcache does (attempts to do) this also.  After very preliminary review,
zswap's two-stage solution may be one step further/better, though its
not clear to me if there are new races possible.

> 3. As far as I can tell, none of these compressors has a good way of
> balancing the amount of memory dedicated to compression against the
> pressure on the rest of the memory.  On both zram and zswap, we set a
> max size for the compressed data.  That size determines how much RAM
> is left for the working set, which should remain uncompressed.  But
> the size of the working set can vary significantly with the load.  If
> we choose the size based on a worst-case working set, we'll be
> underutilizing RAM on average.  If we choose it smaller than that, the
> worst-case working set will cause excessive CPU use and thrashing.

Zcache attempts to take a bigger picture, dynamically balancing both
compressed pagecache pages and compressed swap pages against other
system memory pressure (and, with ramster, across multiple machines,
and with Xen tmem, across multiple Xen guests).

Zram (for swap) and zswap are much more focused on compressing swap
pages in isolation with only a fixed upper size bound.  Thus they
have the advantage of simplicity.

But let's leave the gory detail for the new thread.

> Thanks!
> 
> P.S. For us, the situation in case 3 is improved from having
> centralized control over all (relevant) processes.  If we can detect
> thrashing, we can activate the tab discarder and decrease the load.

Yes, your use model is a bit different than most Linux systems...
because of your tab discarder, the equivalent of OOMs are OK as
long as you can predict them, whereas OOms are anathema on most systems.
It's not clear to me whether your use model and the more generic
Linux model will require different in-kernel compression
solutions or whether the same solutions will serve both use models.

> On Tue, Dec 11, 2012 at 1:55 PM, Seth Jennings
> <sjenning@linux.vnet.ibm.com> wrote:
> > Zswap Overview:
> >
> > Zswap is a lightweight compressed cache for swap pages. It takes
> > pages that are in the process of being swapped out and attempts to
> > compress them into a dynamically allocated RAM-based memory pool.
> > If this process is successful, the writeback to the swap device is
> > deferred and, in many cases, avoided completely.  This results in
> > a significant I/O reduction and performance gains for systems that
> > are swapping. The results of a kernel building benchmark indicate a
> > runtime reduction of 53% and an I/O reduction 76% with zswap vs normal
> > swapping with a kernel build under heavy memory pressure (see
> > Performance section for more).
> >
> > Patchset Structure:
> > 1-4: improvements/changes to zsmalloc
> > 5:   add atomic_t get/set to debugfs
> > 6:   promote zsmalloc to /lib
> > 7-8: add zswap and documentation
> >
> > Targeting this for linux-next.
> >
> > Rationale:
> >
> > Zswap provides compressed swap caching that basically trades CPU cycles
> > for reduced swap I/O.  This trade-off can result in a significant
> > performance improvement as reads to/writes from to the compressed
> > cache almost always faster that reading from a swap device
> > which incurs the latency of an asynchronous block I/O read.
> >
> > Some potential benefits:
> > * Desktop/laptop users with limited RAM capacities can mitigate the
> >     performance impact of swapping.
> > * Overcommitted guests that share a common I/O resource can
> >     dramatically reduce their swap I/O pressure, avoiding heavy
> >     handed I/O throttling by the hypervisor.  This allows more work
> >     to get done with less impact to the guest workload and guests
> >     sharing the I/O subsystem
> > * Users with SSDs as swap devices can extend the life of the device by
> >     drastically reducing life-shortening writes.
> >
> > Zswap evicts pages from compressed cache on an LRU basis to the backing
> > swap device when the compress pool reaches it size limit or the pool is
> > unable to obtain additional pages from the buddy allocator.  This
> > requirement had been identified in prior community discussions.
> >
> > Compressed swap is also provided in zcache, along with page cache
> > compression and RAM clustering through RAMSter. Zswap seeks to deliver
> > the benefit of swap  compression to users in a discrete function.
> > This design decision is akin to Unix design philosophy of doing one
> > thing well, it leaves file cache compression and other features
> > for separate code.
> >
> > Design:
> >
> > Zswap receives pages for compression through the Frontswap API and
> > is able to evict pages from its own compressed pool on an LRU basis
> > and write them back to the backing swap device in the case that the
> > compressed pool is full or unable to secure additional pages from
> > the buddy allocator.
> >
> > Zswap makes use of zsmalloc for the managing the compressed memory
> > pool.  This is because zsmalloc is specifically designed to minimize
> > fragmentation on large (> PAGE_SIZE/2) allocation sizes.  Each
> > allocation in zsmalloc is not directly accessible by address.
> > Rather, a handle is return by the allocation routine and that handle
> > must be mapped before being accessed.  The compressed memory pool grows
> > on demand and shrinks as compressed pages are freed.  The pool is
> > not preallocated.
> >
> > When a swap page is passed from frontswap to zswap, zswap maintains
> > a mapping of the swap entry, a combination of the swap type and swap
> > offset, to the zsmalloc handle that references that compressed swap
> > page.  This mapping is achieved with a red-black tree per swap type.
> > The swap offset is the search key for the tree nodes.
> >
> > Zswap seeks to be simple in its policies.  Sysfs attributes allow for
> > two user controlled policies:
> > * max_compression_ratio - Maximum compression ratio, as as percentage,
> >     for an acceptable compressed page. Any page that does not compress
> >     by at least this ratio will be rejected.
> > * max_pool_percent - The maximum percentage of memory that the compressed
> >     pool can occupy.
> >
> > To enabled zswap, the "enabled" attribute must be set to 1 at boot time.
> >
> > Zswap allows the compressor to be selected at kernel boot time by
> > setting the "compressor" attribute.  The default compressor is lzo.
> >
> > A debugfs interface is provided for various statistic about pool size,
> > number of pages stored, and various counters for the reasons pages
> > are rejected.
> >
> > Performance, Kernel Building:
> >
> > Setup
> > ========
> > Gentoo w/ kernel v3.7-rc7
> > Quad-core i5-2500 @ 3.3GHz
> > 512MB DDR3 1600MHz (limited with mem=512m on boot)
> > Filesystem and swap on 80GB HDD (about 58MB/s with hdparm -t)
> > majflt are major page faults reported by the time command
> > pswpin/out is the delta of pswpin/out from /proc/vmstat before and after
> > the make -jN
> >
> > Summary
> > ========
> > * Zswap reduces I/O and improves performance at all swap pressure levels.
> >
> > * Under heavy swaping at 24 threads, zswap reduced I/O by 76%, saving
> >   over 1.5GB of I/O, and cut runtime in half.
> >
> > Details
> > ========
> > I/O (in pages)
> >         base                            zswap                           change  change
> > N       pswpin  pswpout majflt  I/O sum pswpin  pswpout majflt  I/O sum %I/O    MB
> > 8       1       335     291     627     0       0       249     249     -60%    1
> > 12      3688    14315   5290    23293   123     860     5954    6937    -70%    64
> > 16      12711   46179   16803   75693   2936    7390    46092   56418   -25%    75
> > 20      42178   133781  49898   225857  9460    28382   92951   130793  -42%    371
> > 24      96079   357280  105242  558601  7719    18484   109309  135512  -76%    1653
> >
> > Runtime (in seconds)
> > N       base    zswap   %change
> > 8       107     107     0%
> > 12      128     110     -14%
> > 16      191     179     -6%
> > 20      371     240     -35%
> > 24      570     267     -53%
> >
> > %CPU utilization (out of 400% on 4 cpus)
> > N       base    zswap   %change
> > 8       317     319     1%
> > 12      267     311     16%
> > 16      179     191     7%
> > 20      94      143     52%
> > 24      60      128     113%
> >
> > Patchset is based on next-20121210
> >
> > Seth Jennings (8):
> >   staging: zsmalloc: add gfp flags to zs_create_pool
> >   staging: zsmalloc: remove unsed pool name
> >   staging: zsmalloc: add page alloc/free callbacks
> >   staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE
> >   debugfs: add get/set for atomic types
> >   zsmalloc: promote to lib/
> >   zswap: add to mm/
> >   zswap: add documentation
> >
> >  Documentation/vm/zswap.txt               |   74 ++
> >  drivers/staging/Kconfig                  |    2 -
> >  drivers/staging/Makefile                 |    1 -
> >  drivers/staging/zcache/zcache-main.c     |    7 +-
> >  drivers/staging/zram/zram_drv.c          |    4 +-
> >  drivers/staging/zram/zram_drv.h          |    3 +-
> >  drivers/staging/zsmalloc/Kconfig         |   10 -
> >  drivers/staging/zsmalloc/Makefile        |    3 -
> >  drivers/staging/zsmalloc/zsmalloc-main.c | 1064 -----------------------------
> >  drivers/staging/zsmalloc/zsmalloc.h      |   43 --
> >  fs/debugfs/file.c                        |   42 ++
> >  include/linux/debugfs.h                  |    2 +
> >  include/linux/swap.h                     |    4 +
> >  include/linux/zsmalloc.h                 |   49 ++
> >  lib/Kconfig                              |   18 +
> >  lib/Makefile                             |    1 +
> >  lib/zsmalloc.c                           | 1076 +++++++++++++++++++++++++++++
> >  mm/Kconfig                               |   15 +
> >  mm/Makefile                              |    1 +
> >  mm/page_io.c                             |   22 +-
> >  mm/swap_state.c                          |    2 +-
> >  mm/zswap.c                               | 1077 ++++++++++++++++++++++++++++++
> >  22 files changed, 2383 insertions(+), 1137 deletions(-)
> >  create mode 100644 Documentation/vm/zswap.txt
> >  delete mode 100644 drivers/staging/zsmalloc/Kconfig
> >  delete mode 100644 drivers/staging/zsmalloc/Makefile
> >  delete mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c
> >  delete mode 100644 drivers/staging/zsmalloc/zsmalloc.h
> >  create mode 100644 include/linux/zsmalloc.h
> >  create mode 100644 lib/zsmalloc.c
> >  create mode 100644 mm/zswap.c
> >
> > --
> > 1.7.9.5
> >
> > --
> > To unsubscribe, send a message with 'unsubscribe linux-mm' in
> > the body to majordomo@kvack.org.  For more info on Linux MM,
> > see: http://www.linux-mm.org/ .
> > Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 0/8] zswap: compressed swap caching
  2012-12-12 22:49 ` Luigi Semenzato
  2012-12-12 23:46   ` Dan Magenheimer
@ 2012-12-14 15:59   ` Seth Jennings
  1 sibling, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2012-12-14 15:59 UTC (permalink / raw)
  To: Luigi Semenzato
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

Hey Luigi,

To echo Dan, it'd be great if we could move this discussion to a new
thread as to leaving this one for code review/comments.  But I did
want to respond.

On 12/12/2012 04:49 PM, Luigi Semenzato wrote:
> Just a couple of questions and comments as a user.  I apologize if
> this is the wrong time to make them, feel free to ignore them.
> 
> 1. It's becoming difficult to understand how zcache, zcache2, zram,
> and now zswap, interact and/or overlap with each other.  For instance,
> I am examining the possibility of using zcache2 and zram in parallel.
> Should I also/instead consider zswap, using a plain RAM disk as the
> swap device?

I guess the short answer is:
* Zcache is no more, replaced by zcache2 (so I stop making the
distinction)
* You wouldn't use zcache and zswap together unless you first disabled
the frontswap part of zcache, however this configuration isn't
recommended since there would be no cooperation between the compressed
page cache and compressed swap cache.
* Zram could work with either zswap or zcache although the
interactions would be interesting; namely, I think zram stores would
likely fail with high frequency since the primary reason the page
would not be captured by either zswap or zcache before it reaches zram
is that zswap/zcache was unable to allocate space.  So zram would
likely fail for the same reason, causing swap slots to be marked as
bad and rapidly shrink the effective size of the zram device.

> 2. Zswap looks like a two-stage swap device, with one stage being
> compressed memory.  I don't know if and where the abstraction breaks,
> and what the implementation issues would be, but I would find it
> easier to view and configure this as a two-stage swap, where one stage
> is chosen as zcache (but could be something else).

I didn't follow this.

> 3. As far as I can tell, none of these compressors has a good way of
> balancing the amount of memory dedicated to compression against the
> pressure on the rest of the memory.  On both zram and zswap, we set a
> max size for the compressed data.  That size determines how much RAM
> is left for the working set, which should remain uncompressed.  But
> the size of the working set can vary significantly with the load.  If
> we choose the size based on a worst-case working set, we'll be
> underutilizing RAM on average.  If we choose it smaller than that, the
> worst-case working set will cause excessive CPU use and thrashing.

This kind of adaptive tuning would be hard to implement and even
harder to get right for everyone, if it could be done at all.
However, one way to achieve this would be to have something in
userspace periodically change the sysfs attribute that controls
maximum compressed pool size based on your own policy.  Both zswap and
zcache have this tunable.

Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
       [not found] ` <<1355262966-15281-8-git-send-email-sjenning@linux.vnet.ibm.com>
@ 2012-12-31 23:06   ` Dan Magenheimer
  2013-01-01 17:52     ` Seth Jennings
  0 siblings, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2012-12-31 23:06 UTC (permalink / raw)
  To: Seth Jennings, Greg Kroah-Hartman, Andrew Morton
  Cc: Nitin Gupta, Minchan Kim, Konrad Rzeszutek Wilk, Dan Magenheimer,
	Robert Jennings, Jenifer Hopper, Mel Gorman, Johannes Weiner,
	Rik van Riel, Larry Woodman, linux-mm, linux-kernel, devel

> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> Subject: [PATCH 7/8] zswap: add to mm/
> 
> zswap is a thin compression backend for frontswap. It receives
> pages from frontswap and attempts to store them in a compressed
> memory pool, resulting in an effective partial memory reclaim and
> dramatically reduced swap device I/O.

Hi Seth --

Happy (almost) New Year!

I am eagerly studying one of the details of your zswap "flush"
code in this patch to see how you solved a problem or two that
I was struggling with for the similar mechanism RFC'ed for zcache
(see https://lkml.org/lkml/2012/10/3/558).  I like the way
that you force the newly-uncompressed to-be-flushed page immediately
into a swap bio in zswap_flush_entry via the call to swap_writepage,
though I'm not entirely convinced that there aren't some race
conditions there.  However, won't swap_writepage simply call
frontswap_store instead and re-compress the page back into zswap?
The (very ugly) solution I used for this was to flag the page in a
frontswap_denial_map (see https://lkml.org/lkml/2012/10/3/560).
Don't you require something like that also, or am I missing some
magic in your code?

I'm also a bit concerned about the consequent recursion:

frontswap_store->
  zswap_fs_store->
    zswap_flush_entries->
      zswap_flush_entry->
        __swap_writepage->
          swap_writepage->
            frontswap_store->
              zswap_fs_store-> etc

It's not obvious to me how deeply this might recurse and/or
how the recursion is terminated.  The RFC'ed zcache code
calls its equivalence of your "flush" code only from the
shrinker thread to avoid this, though there may be a third,
better, way.

A second related issue that concerns me is that, although you
are now, like zcache2, using an LRU queue for compressed pages
(aka "zpages"), there is no relationship between that queue and
physical pageframes.  In other words, you may free up 100 zpages
out of zswap via zswap_flush_entries, but not free up a single
pageframe.  This seems like a significant design issue.  Or am
I misunderstanding the code?

A third concern is about scalability... the locking seems very
coarse-grained.  In zcache, you personally observed and fixed
hashbucket contention (see https://lkml.org/lkml/2011/9/29/215).
Doesn't zswap's tree_lock essentially use a single tree (per
swaptype), i.e. no scalability?

Thanks,
Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2012-12-31 23:06   ` [PATCH 7/8] zswap: add to mm/ Dan Magenheimer
@ 2013-01-01 17:52     ` Seth Jennings
  2013-01-02 15:55       ` Dave Hansen
  2013-01-02 17:08       ` Dan Magenheimer
  0 siblings, 2 replies; 38+ messages in thread
From: Seth Jennings @ 2013-01-01 17:52 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Robert Jennings, Jenifer Hopper,
	Mel Gorman, Johannes Weiner, Rik van Riel, Larry Woodman,
	linux-mm, linux-kernel, devel

On 12/31/2012 05:06 PM, Dan Magenheimer wrote:
>> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
>> Subject: [PATCH 7/8] zswap: add to mm/
>>
>> zswap is a thin compression backend for frontswap. It receives
>> pages from frontswap and attempts to store them in a compressed
>> memory pool, resulting in an effective partial memory reclaim and
>> dramatically reduced swap device I/O.
> 
> Hi Seth --
> 
> Happy (almost) New Year!

You too :)  Thanks for taking a look at the code.

> I am eagerly studying one of the details of your zswap "flush"
> code in this patch to see how you solved a problem or two that
> I was struggling with for the similar mechanism RFC'ed for zcache
> (see https://lkml.org/lkml/2012/10/3/558).  I like the way
> that you force the newly-uncompressed to-be-flushed page immediately
> into a swap bio in zswap_flush_entry via the call to swap_writepage,
> though I'm not entirely convinced that there aren't some race
> conditions there.  However, won't swap_writepage simply call
> frontswap_store instead and re-compress the page back into zswap?

I break part of swap_writepage() into a bottom half called
__swap_writepage() that doesn't include the call to frontswap_store().
__swap_writepage() is what is called from zswap_flush_entry().  That
is how I avoid flushed pages recycling back into zswap and the
potential recursion mentioned.

> A second related issue that concerns me is that, although you
> are now, like zcache2, using an LRU queue for compressed pages
> (aka "zpages"), there is no relationship between that queue and
> physical pageframes.  In other words, you may free up 100 zpages
> out of zswap via zswap_flush_entries, but not free up a single
> pageframe.  This seems like a significant design issue.  Or am
> I misunderstanding the code?

You understand correctly.  There is room for optimization here and it
is something I'm working on right now.

What I'm looking to do is give zswap a little insight into zsmalloc
internals, namely the ability figure out what class size a particular
allocation is in and, in the event the store can't be satisfied, flush
an entry from that exact class size so that we can be assured the
store will succeed with minimal flushing work.  In this solution,
there would be an LRU list per zsmalloc class size tracked in zswap.
The result is LRU-ish flushing overall with class size being the first
flush selection criteria and LRU as the second.

> A third concern is about scalability... the locking seems very
> coarse-grained.  In zcache, you personally observed and fixed
> hashbucket contention (see https://lkml.org/lkml/2011/9/29/215).
> Doesn't zswap's tree_lock essentially use a single tree (per
> swaptype), i.e. no scalability?

The reason the coarse lock isn't a problem for zswap like the hash
bucket locks where in zcache is that the lock is not held for long
periods time as it is in zcache.  It is only held while operating on
the tree, not during compression/decompression and larger memory
operations.

Also, I've done some lockstat checks and the zswap tree lock is way
down on the list contributing <1% of the lock contention wait time on
a 4-core system.  The anon_vma lock is the primary bottleneck.

Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-01 17:52     ` Seth Jennings
@ 2013-01-02 15:55       ` Dave Hansen
  2013-01-02 17:26         ` Dan Magenheimer
  2013-01-02 22:44         ` [PATCH 7/8] zswap: add to mm/ Seth Jennings
  2013-01-02 17:08       ` Dan Magenheimer
  1 sibling, 2 replies; 38+ messages in thread
From: Dave Hansen @ 2013-01-02 15:55 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Dan Magenheimer, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Minchan Kim, Konrad Rzeszutek Wilk, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/01/2013 09:52 AM, Seth Jennings wrote:
> On 12/31/2012 05:06 PM, Dan Magenheimer wrote:
>> A second related issue that concerns me is that, although you
>> are now, like zcache2, using an LRU queue for compressed pages
>> (aka "zpages"), there is no relationship between that queue and
>> physical pageframes.  In other words, you may free up 100 zpages
>> out of zswap via zswap_flush_entries, but not free up a single
>> pageframe.  This seems like a significant design issue.  Or am
>> I misunderstanding the code?
> 
> You understand correctly.  There is room for optimization here and it
> is something I'm working on right now.

It's the same "design issue" that the slab shrinkers have, and they are
likely to have some substantially consistently smaller object sizes.

>> A third concern is about scalability... the locking seems very
>> coarse-grained.  In zcache, you personally observed and fixed
>> hashbucket contention (see https://lkml.org/lkml/2011/9/29/215).
>> Doesn't zswap's tree_lock essentially use a single tree (per
>> swaptype), i.e. no scalability?
> 
> The reason the coarse lock isn't a problem for zswap like the hash
> bucket locks where in zcache is that the lock is not held for long
> periods time as it is in zcache.  It is only held while operating on
> the tree, not during compression/decompression and larger memory
> operations.

Lock hold times don't often dominate lock cost these days.  The limiting
factor tends to be the cost of atomic operations to bring the cacheline
over to the CPUs acquiring the lock.

> Also, I've done some lockstat checks and the zswap tree lock is way
> down on the list contributing <1% of the lock contention wait time on
> a 4-core system.  The anon_vma lock is the primary bottleneck.

4 cores these days is awfully small.  Some of our fellow colleagues at
IBM might be a _bit_ concerned if we told them that we were using a
4-core non-NUMA system and extrapolating lock contention from there. :)

It's curious that you chose the anon_vma lock, though.  It can only
possibly show _contention_ when you've got a bunch of CPUs beating on
the related VMAs.  That contention disappears in workloads that aren't
threaded, so it seems at least a bit imprecise to say anon_vma lock is
the primary bottleneck.


^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
  2013-01-01 17:52     ` Seth Jennings
  2013-01-02 15:55       ` Dave Hansen
@ 2013-01-02 17:08       ` Dan Magenheimer
  2013-01-02 23:25         ` Seth Jennings
  1 sibling, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-02 17:08 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel, Dave Hansen

> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> Subject: Re: [PATCH 7/8] zswap: add to mm/
> 
> > I am eagerly studying one of the details of your zswap "flush"
> > code in this patch to see how you solved a problem or two that
> > I was struggling with for the similar mechanism RFC'ed for zcache
> > (see https://lkml.org/lkml/2012/10/3/558).  I like the way
> > that you force the newly-uncompressed to-be-flushed page immediately
> > into a swap bio in zswap_flush_entry via the call to swap_writepage,
> > though I'm not entirely convinced that there aren't some race
> > conditions there.  However, won't swap_writepage simply call
> > frontswap_store instead and re-compress the page back into zswap?
> 
> I break part of swap_writepage() into a bottom half called
> __swap_writepage() that doesn't include the call to frontswap_store().
> __swap_writepage() is what is called from zswap_flush_entry().  That
> is how I avoid flushed pages recycling back into zswap and the
> potential recursion mentioned.

OK, I missed that.  Nice.  I will see if I can use the
same with zcache and, if so, would be happy to support
the change to swap_writepage.

In your next version, maybe you could break out that chunk
into a separate distinct patch so it can be pulled separately
into Andrew's tree?
 
> > A second related issue that concerns me is that, although you
> > are now, like zcache2, using an LRU queue for compressed pages
> > (aka "zpages"), there is no relationship between that queue and
> > physical pageframes.  In other words, you may free up 100 zpages
> > out of zswap via zswap_flush_entries, but not free up a single
> > pageframe.  This seems like a significant design issue.  Or am
> > I misunderstanding the code?
> 
> You understand correctly.  There is room for optimization here and it
> is something I'm working on right now.
> 
> What I'm looking to do is give zswap a little insight into zsmalloc
> internals,

Not to be at all snide, but had you been as eager to break
the zsmalloc abstraction last spring, a lot of unpleasantness
and extra work might have been avoided. :v(

> namely the ability figure out what class size a particular
> allocation is in and, in the event the store can't be satisfied, flush
> an entry from that exact class size so that we can be assured the
> store will succeed with minimal flushing work.  In this solution,
> there would be an LRU list per zsmalloc class size tracked in zswap.
> The result is LRU-ish flushing overall with class size being the first
> flush selection criteria and LRU as the second.

Clever and definitely useful, though I think there are two related
problems and IIUC this solves only one of them.  The problem it _does_
solve is (A) where to put a new zpage: Move a zpage from the same
class to real-swap-disk and then fill its slot with the new zpage.
The problem it _doesn't_ solve is (B) how to shrink the total number
of pageframes used by zswap, even by a single page.  I believe
(though cannot prove right now) that this latter problem will
need to be solved to implement any suitable MM policy for balancing
pages-used-for-compression vs pages-not-used-for-compression.

I fear that problem (B) is the fundamental concern with
using a high-density storage allocator such as zsmalloc, which
is why I abandoned zsmalloc in favor of a more-predictable-but-
less-dense allocator (zbud).  However, if you have a solution
for (B) as well, I would gladly abandon zbud in zcache (for _both_
cleancache and frontswap pages) and our respective in-kernel
compression efforts would be more easy to merge into one solution
in the future.

> > A third concern is about scalability... the locking seems very
> > coarse-grained.  In zcache, you personally observed and fixed
> > hashbucket contention (see https://lkml.org/lkml/2011/9/29/215).
> > Doesn't zswap's tree_lock essentially use a single tree (per
> > swaptype), i.e. no scalability?
> 
> The reason the coarse lock isn't a problem for zswap like the hash
> bucket locks where in zcache is that the lock is not held for long
> periods time as it is in zcache.  It is only held while operating on
> the tree, not during compression/decompression and larger memory
> operations.

Hmmm... IIRC, to avoid races in zcache, it was necessary to
update both the data (zpage) and meta-data ("tree" in zswap,
and tmem-data-structure in zcache) atomically.  I will need
to study your code more to understand how zswap avoids this
requirement.  Or if it is obvious to you, I would be grateful
if you would point it out to me.

Thanks,
Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
  2013-01-02 15:55       ` Dave Hansen
@ 2013-01-02 17:26         ` Dan Magenheimer
  2013-01-02 18:17           ` Dave Hansen
  2013-01-02 22:44         ` [PATCH 7/8] zswap: add to mm/ Seth Jennings
  1 sibling, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-02 17:26 UTC (permalink / raw)
  To: Dave Hansen, Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel

> From: Dave Hansen [mailto:dave@linux.vnet.ibm.com]
> Subject: Re: [PATCH 7/8] zswap: add to mm/
> 
> On 01/01/2013 09:52 AM, Seth Jennings wrote:
> > On 12/31/2012 05:06 PM, Dan Magenheimer wrote:
> >> A second related issue that concerns me is that, although you
> >> are now, like zcache2, using an LRU queue for compressed pages
> >> (aka "zpages"), there is no relationship between that queue and
> >> physical pageframes.  In other words, you may free up 100 zpages
> >> out of zswap via zswap_flush_entries, but not free up a single
> >> pageframe.  This seems like a significant design issue.  Or am
> >> I misunderstanding the code?
> >
> > You understand correctly.  There is room for optimization here and it
> > is something I'm working on right now.
> 
> It's the same "design issue" that the slab shrinkers have, and they are
> likely to have some substantially consistently smaller object sizes.

Understood Dave.  However if one compares the total percentage
of RAM used for zpages by zswap vs the total percentage of RAM
used by slab, I suspect that the zswap number will dominate,
perhaps because zswap is storing primarily data and slab is
storing primarily metadata?

I don't claim to be any kind of expert here, but I'd imagine
that MM doesn't try to manage the total amount of slab space
because slab is "a cost of doing business".  However, for
in-kernel compression to be widely useful, IMHO it will be
critical for MM to somehow load balance between total pageframes
used for compressed pages vs total pageframes used for
normal pages, just as today it needs to balance between
active and inactive pages.
 
> >> A third concern is about scalability... the locking seems very
> >
> > The reason the coarse lock isn't a problem for zswap like the hash
> 
> Lock hold times don't often dominate lock cost these days.  The limiting
> factor tends to be the cost of atomic operations to bring the cacheline
> over to the CPUs acquiring the lock.

[I'll bow out of the scalability discussion as long as someone
else is thinking about it.]

Dan


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-02 17:26         ` Dan Magenheimer
@ 2013-01-02 18:17           ` Dave Hansen
  2013-01-02 19:04             ` Dan Magenheimer
  0 siblings, 1 reply; 38+ messages in thread
From: Dave Hansen @ 2013-01-02 18:17 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Seth Jennings, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Minchan Kim, Konrad Wilk, Robert Jennings, Jenifer Hopper,
	Mel Gorman, Johannes Weiner, Rik van Riel, Larry Woodman,
	linux-mm, linux-kernel, devel

On 01/02/2013 09:26 AM, Dan Magenheimer wrote:
> However if one compares the total percentage
> of RAM used for zpages by zswap vs the total percentage of RAM
> used by slab, I suspect that the zswap number will dominate,
> perhaps because zswap is storing primarily data and slab is
> storing primarily metadata?

That's *obviously* 100% dependent on how you configure zswap.  But, that
said, most of _my_ systems tend to sit with about 5% of memory in
reclaimable slab which is certainly on par with how I'd expect to see
zswap used.

> I don't claim to be any kind of expert here, but I'd imagine
> that MM doesn't try to manage the total amount of slab space
> because slab is "a cost of doing business".  However, for
> in-kernel compression to be widely useful, IMHO it will be
> critical for MM to somehow load balance between total pageframes
> used for compressed pages vs total pageframes used for
> normal pages, just as today it needs to balance between
> active and inactive pages.

The issue isn't about balancing.  It's about reclaim where the VM only
cares about whole pages.  If our subsystem (zwhatever or slab) is only
designed to reclaim _parts_ of pages, can we be successful in returning
whole pages to the VM?

The slab shrinkers only work on parts of pages (singular slab objects).
 Yet, it does appear that they function well enough when we try to
reclaim from them.  I've never seen a slab's sizes spiral out of control
due to fragmentation.


^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
  2013-01-02 18:17           ` Dave Hansen
@ 2013-01-02 19:04             ` Dan Magenheimer
  2013-01-03  7:33               ` Dave Chinner
  0 siblings, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-02 19:04 UTC (permalink / raw)
  To: Dave Hansen
  Cc: Seth Jennings, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Minchan Kim, Konrad Wilk, Robert Jennings, Jenifer Hopper,
	Mel Gorman, Johannes Weiner, Rik van Riel, Larry Woodman,
	linux-mm, linux-kernel, devel

> From: Dave Hansen [mailto:dave@linux.vnet.ibm.com]
> Subject: Re: [PATCH 7/8] zswap: add to mm/

Hi Dave --

I suspect we are in violent agreement but just to make sure...

Although zswap is the current example, I guess I am discussing
a bigger issue, which IMHO is much more important:  How should
compression be utilized in the kernel (if at all)?  Zswap is
simply one implementation of in-kernel compression (handling
anonymous pages only) and zcache is another (handling both
anonymous pages and pagecache pages).   Each has some
limited policy, and policy defaults built-in, but neither IMHO
is adequately aware of (let alone integrated with) MM policy to
be useful to a broad set of end users and to be enabled by default
by generic distros.
 
> On 01/02/2013 09:26 AM, Dan Magenheimer wrote:
> > However if one compares the total percentage
> > of RAM used for zpages by zswap vs the total percentage of RAM
> > used by slab, I suspect that the zswap number will dominate,
> > perhaps because zswap is storing primarily data and slab is
> > storing primarily metadata?
> 
> That's *obviously* 100% dependent on how you configure zswap.  But, that
> said, most of _my_ systems tend to sit with about 5% of memory in
> reclaimable slab 

The 5% "sitting" number for slab is somewhat interesting, but
IMHO irrelevant here. The really interesting value is what percent
is used by slab when the system is under high memory pressure; I'd
imagine that number would be much smaller.  True?

> which is certainly on par with how I'd expect to see
> zswap used.

You are suggesting that the default zswap_max_pool_percent
should be set to 5?  (Current default is 20.)  Zswap has little
or no value on a system that would otherwise never swap.
Why would you set the zswap limit so low?  IMHO, even 20
may be too low.

> > I don't claim to be any kind of expert here, but I'd imagine
> > that MM doesn't try to manage the total amount of slab space
> > because slab is "a cost of doing business".  However, for
> > in-kernel compression to be widely useful, IMHO it will be
> > critical for MM to somehow load balance between total pageframes
> > used for compressed pages vs total pageframes used for
> > normal pages, just as today it needs to balance between
> > active and inactive pages.
> 
> The issue isn't about balancing.  It's about reclaim where the VM only
> cares about whole pages.  If our subsystem (zwhatever or slab) is only
> designed to reclaim _parts_ of pages, can we be successful in returning
> whole pages to the VM?

IMHO, it's about *both* balancing _and_ reclaim.  One remaining
major point of debate between zcache and zswap is that zcache
accepts lower density to ensure that whole pages can be easily
returned to the VM (and thus allow balancing) while zswap targets
best density (by using zsmalloc) and doesn't address returning
whole pages to the VM.

> The slab shrinkers only work on parts of pages (singular slab objects).
>  Yet, it does appear that they function well enough when we try to
> reclaim from them.  I've never seen a slab's sizes spiral out of control
> due to fragmentation.

Perhaps this is because the reclaimable slab objects are mostly
metadata which is highly connected to reclaimable data objects?
E.g. reclaiming most reclaimable data pages also coincidentally
reclaims most slab objects?

(Also, it is not the slab size that would be the issue here but
its density... i.e. if, after shrinking, 1000 pageframes contain
only 2000 various 4-byt objects, that would be "out of control".
Is there any easy visibility into slab density?)

In any case, I would posit that both the nature of zpages and their
average size relative to a whole page is quite unusual compared to slab.
So while there may be some useful comparisons between zswap
and slab, the differences may warrant dramatically different policy.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-02 15:55       ` Dave Hansen
  2013-01-02 17:26         ` Dan Magenheimer
@ 2013-01-02 22:44         ` Seth Jennings
  1 sibling, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2013-01-02 22:44 UTC (permalink / raw)
  To: Dave Hansen
  Cc: Dan Magenheimer, Greg Kroah-Hartman, Andrew Morton, Nitin Gupta,
	Minchan Kim, Konrad Rzeszutek Wilk, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/02/2013 09:55 AM, Dave Hansen wrote:
> On 01/01/2013 09:52 AM, Seth Jennings wrote:
>> On 12/31/2012 05:06 PM, Dan Magenheimer wrote:
>> Also, I've done some lockstat checks and the zswap tree lock is way
>> down on the list contributing <1% of the lock contention wait time on
>> a 4-core system.  The anon_vma lock is the primary bottleneck.
>
> It's curious that you chose the anon_vma lock, though.  It can only
> possibly show _contention_ when you've got a bunch of CPUs beating on
> the related VMAs.  That contention disappears in workloads that aren't
> threaded, so it seems at least a bit imprecise to say anon_vma lock is
> the primary bottleneck.

Sorry, should have qualified.  According to lockstat, the locks with
the most contention during a -j16 kernel build on a memory restricted
4-core machine were:

1  sb_lock:				252400
2  swap_lock:				191499
3  &(&mm->page_table_lock)->rlock:	69725
4  &anon_vma->mutex:			51369
5  swapper_space.tree_lock:		42261
6  &(&zone->lru_lock)->rlock:		38909
7  &rq->lock:				19586
8  rcu_node_0:				18467
9  &(&tree->lock)->rlock:		12776 <-- zswap tree lock
10 &rsp->gp_wq:				11909

The zswap tree lock accounts for <2% of contentions within the top 10
contended locks.

During the same build, the locks with the most wait time were:

1  &type->i_mutex_dir_key#4:		137134027.28
2  &anon_vma->mutex:			43569273.66
3  &mapping->i_mmap_mutex:		12041326.01
4  &sb->s_type->i_mutex_key#3/1:	3574244.56
5  &(&mm->page_table_lock)->rlock:	701280.1
6  sysfs_mutex:				628204.76
7  &sb->s_type->i_mutex_key#3:		598007.84
8  swap_lock:				333334.8
9  &rsp->gp_wq:				177479.84
10 &tty->atomic_write_lock:		142573.89
...
18 &(&tree->lock)->rlock:		13451.57

The zswap tree lock wait numbers are noise here.

During a single-threaded test with memknobs, a single threaded
application that simply allocates/touches a large anonymous memory
section and then randomly reads from it, there were no contentions.

I also did a tmpfs test, where I copied the kernel source tree into a
tmpfs mount that overflowed into swap by around 300MB.  Zswap captured
all the pages that compressed well enough according to policy and
there were no contentions on the zswap tree lock.

So I'm not seeing any cases where the zswap locking is causing a
measurable issue.  In the cases where there contention occurs, the
vast majority of the contention and wait time happens in other layers.

Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-02 17:08       ` Dan Magenheimer
@ 2013-01-02 23:25         ` Seth Jennings
  2013-01-03 22:33           ` Dan Magenheimer
  0 siblings, 1 reply; 38+ messages in thread
From: Seth Jennings @ 2013-01-02 23:25 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel, Dave Hansen

On 01/02/2013 11:08 AM, Dan Magenheimer wrote:
>> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
>> Subject: Re: [PATCH 7/8] zswap: add to mm/
>>
>>> I am eagerly studying one of the details of your zswap "flush"
>>> code in this patch to see how you solved a problem or two that
>>> I was struggling with for the similar mechanism RFC'ed for zcache
>>> (see https://lkml.org/lkml/2012/10/3/558).  I like the way
>>> that you force the newly-uncompressed to-be-flushed page immediately
>>> into a swap bio in zswap_flush_entry via the call to swap_writepage,
>>> though I'm not entirely convinced that there aren't some race
>>> conditions there.  However, won't swap_writepage simply call
>>> frontswap_store instead and re-compress the page back into zswap?
>>
>> I break part of swap_writepage() into a bottom half called
>> __swap_writepage() that doesn't include the call to frontswap_store().
>> __swap_writepage() is what is called from zswap_flush_entry().  That
>> is how I avoid flushed pages recycling back into zswap and the
>> potential recursion mentioned.
> 
> OK, I missed that.  Nice.  I will see if I can use the
> same with zcache and, if so, would be happy to support
> the change to swap_writepage.
> 
> In your next version, maybe you could break out that chunk
> into a separate distinct patch so it can be pulled separately
> into Andrew's tree?

Sure.

>>> A second related issue that concerns me is that, although you
>>> are now, like zcache2, using an LRU queue for compressed pages
>>> (aka "zpages"), there is no relationship between that queue and
>>> physical pageframes.  In other words, you may free up 100 zpages
>>> out of zswap via zswap_flush_entries, but not free up a single
>>> pageframe.  This seems like a significant design issue.  Or am
>>> I misunderstanding the code?
>>
>> You understand correctly.  There is room for optimization here and it
>> is something I'm working on right now.
>>
>> What I'm looking to do is give zswap a little insight into zsmalloc
>> internals,
> 
> Not to be at all snide, but had you been as eager to break
> the zsmalloc abstraction last spring, a lot of unpleasantness
> and extra work might have been avoided. :v(

Well _some_ of it could have been avoided.

I'm putting at lot of thought into how to do it cleanly, while
maintaining the generic nature of zsmalloc.  I did a PoC already, but
I wasn't comfortable with how much had to be exposed to achieve it.
So I'm still working on it.

>> namely the ability figure out what class size a particular
>> allocation is in and, in the event the store can't be satisfied, flush
>> an entry from that exact class size so that we can be assured the
>> store will succeed with minimal flushing work.  In this solution,
>> there would be an LRU list per zsmalloc class size tracked in zswap.
>> The result is LRU-ish flushing overall with class size being the first
>> flush selection criteria and LRU as the second.
> 
> Clever and definitely useful, though I think there are two related
> problems and IIUC this solves only one of them.  The problem it _does_
> solve is (A) where to put a new zpage: Move a zpage from the same
> class to real-swap-disk and then fill its slot with the new zpage.
> The problem it _doesn't_ solve is (B) how to shrink the total number
> of pageframes used by zswap, even by a single page.  I believe
> (though cannot prove right now) that this latter problem will
> need to be solved to implement any suitable MM policy for balancing
> pages-used-for-compression vs pages-not-used-for-compression.
>
> I fear that problem (B) is the fundamental concern with
> using a high-density storage allocator such as zsmalloc, which
> is why I abandoned zsmalloc in favor of a more-predictable-but-
> less-dense allocator (zbud).  However, if you have a solution
> for (B) as well, I would gladly abandon zbud in zcache (for _both_
> cleancache and frontswap pages) and our respective in-kernel
> compression efforts would be more easy to merge into one solution
> in the future.

The only difference afaict between zbud and zsmalloc wrt vacating a
page frame is that zbud will only ever need to write back two pages to
swap where zsmalloc might have to write back more to free up the
zspage that contains the page frame to be vacated.  This is doable
with zsmalloc.  The question that remains for me is how cleanly can it
be done (for either approach).

>>> A third concern is about scalability... the locking seems very
>>> coarse-grained.  In zcache, you personally observed and fixed
>>> hashbucket contention (see https://lkml.org/lkml/2011/9/29/215).
>>> Doesn't zswap's tree_lock essentially use a single tree (per
>>> swaptype), i.e. no scalability?
>>
>> The reason the coarse lock isn't a problem for zswap like the hash
>> bucket locks where in zcache is that the lock is not held for long
>> periods time as it is in zcache.  It is only held while operating on
>> the tree, not during compression/decompression and larger memory
>> operations.
> 
> Hmmm... IIRC, to avoid races in zcache, it was necessary to
> update both the data (zpage) and meta-data ("tree" in zswap,
> and tmem-data-structure in zcache) atomically.  I will need
> to study your code more to understand how zswap avoids this
> requirement.  Or if it is obvious to you, I would be grateful
> if you would point it out to me.

Without the flushing mechanism, a simple lock guarding the tree was
enough in zswap.  The per-entry serialization of the
store/load/invalidate paths are all handled at a higher level.  For
example, we never get a load and invalidate concurrently on the same
swap entry.

However, once the flushing code was introduced and could free an entry
from the zswap_fs_store() path, it became necessary to add a per-entry
refcount to make sure that the entry isn't freed while another code
path was operating on it.

Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-02 19:04             ` Dan Magenheimer
@ 2013-01-03  7:33               ` Dave Chinner
  2013-01-03 22:37                 ` Dan Magenheimer
  2013-01-22 23:58                 ` High slab usage testing with zcache/zswap (Was: [PATCH 7/8] zswap: add to mm/) Dan Magenheimer
  0 siblings, 2 replies; 38+ messages in thread
From: Dave Chinner @ 2013-01-03  7:33 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Dave Hansen, Seth Jennings, Greg Kroah-Hartman, Andrew Morton,
	Nitin Gupta, Minchan Kim, Konrad Wilk, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Wed, Jan 02, 2013 at 11:04:24AM -0800, Dan Magenheimer wrote:
> > From: Dave Hansen [mailto:dave@linux.vnet.ibm.com]
> > Subject: Re: [PATCH 7/8] zswap: add to mm/
> 
> Hi Dave --
> 
> I suspect we are in violent agreement but just to make sure...
> 
> Although zswap is the current example, I guess I am discussing
> a bigger issue, which IMHO is much more important:  How should
> compression be utilized in the kernel (if at all)?  Zswap is
> simply one implementation of in-kernel compression (handling
> anonymous pages only) and zcache is another (handling both
> anonymous pages and pagecache pages).   Each has some
> limited policy, and policy defaults built-in, but neither IMHO
> is adequately aware of (let alone integrated with) MM policy to
> be useful to a broad set of end users and to be enabled by default
> by generic distros.
>  
> > On 01/02/2013 09:26 AM, Dan Magenheimer wrote:
> > > However if one compares the total percentage
> > > of RAM used for zpages by zswap vs the total percentage of RAM
> > > used by slab, I suspect that the zswap number will dominate,
> > > perhaps because zswap is storing primarily data and slab is
> > > storing primarily metadata?
> > 
> > That's *obviously* 100% dependent on how you configure zswap.  But, that
> > said, most of _my_ systems tend to sit with about 5% of memory in
> > reclaimable slab 
> 
> The 5% "sitting" number for slab is somewhat interesting, but
> IMHO irrelevant here. The really interesting value is what percent
> is used by slab when the system is under high memory pressure; I'd
> imagine that number would be much smaller.  True?

Not at all. The amount of slab memory used is wholly dependent on
workload. I have plenty of workloads with severe memory pressure
that I test with that sit at a steady state of >80% of ram in slab
caches. These workloads are filesytem metadata intensive rather than
data intensive, that's exactly the right cache balance for the
system to have....

Thinking that there is a fixed amount of memory that you should
reserve for some subsystem is simply the wrong approach to take.
caches are dynamic and the correct system balance should result of
the natural behaviour of the reclaim algorithms.

The shrinker infrastructure doesn't set any set size goals - it
simply tries to balance the reclaim across all the shrinkers and
relative to the page cache.  If a cache is under allocation
pressure, then it will grow to the point that reclaim is balanced
with the allocation pressure and they won't grow any further. If the
allocation pressure drops, then the cache will shrink if overall
memory pressure is maintained.....

> > > I don't claim to be any kind of expert here, but I'd imagine
> > > that MM doesn't try to manage the total amount of slab space
> > > because slab is "a cost of doing business".

>From the above it should be obvious that the MM subsystem really
does manage the total amount of slab space being used....

> > > However, for
> > > in-kernel compression to be widely useful, IMHO it will be
> > > critical for MM to somehow load balance between total pageframes
> > > used for compressed pages vs total pageframes used for
> > > normal pages, just as today it needs to balance between
> > > active and inactive pages.
> > 
> > The issue isn't about balancing.  It's about reclaim where the VM only
> > cares about whole pages.  If our subsystem (zwhatever or slab) is only
> > designed to reclaim _parts_ of pages, can we be successful in returning
> > whole pages to the VM?
> 
> IMHO, it's about *both* balancing _and_ reclaim.  One remaining
> major point of debate between zcache and zswap is that zcache
> accepts lower density to ensure that whole pages can be easily
> returned to the VM (and thus allow balancing) while zswap targets
> best density (by using zsmalloc) and doesn't address returning
> whole pages to the VM.

And so the two subsystems need different reclaim implementations.
And, well, that's exactly what we have shrinkers for - implmenting
subsystem specific reclaim policy. The shrinker infrastructure is
responsible for them keeping balance between all the caches that
have shrinkers and the size of the page cache...

> > The slab shrinkers only work on parts of pages (singular slab objects).
> >  Yet, it does appear that they function well enough when we try to
> > reclaim from them.  I've never seen a slab's sizes spiral out of control
> > due to fragmentation.
> 
> Perhaps this is because the reclaimable slab objects are mostly
> metadata which is highly connected to reclaimable data objects?
> E.g. reclaiming most reclaimable data pages also coincidentally
> reclaims most slab objects?

No, that's not true. Caches can have some very complex
heirarchies with dependencies across multiple slabs and shrinkers,
not to mention that the caches don't even need to be related to filesystems or the
page cache. Indeed, look at the shrinkers attached to the memory
pools used by the acceleration engines for graphics hardware...

There are also cases where we've moved metadata caches out of the
page cache into shrinker controlled caches because the page cache
reclaim is too simplistic to handle the complex relationships
between filesystem metadata. We've done this in XFS, and IIRC btrfs
did this recently as well...

> (Also, it is not the slab size that would be the issue here but
> its density... i.e. if, after shrinking, 1000 pageframes contain
> only 2000 various 4-byt objects, that would be "out of control".
> Is there any easy visibility into slab density?)

/proc/slabinfo via slabtop, perhaps?

Active / Total Objects (% used)    : 1798915 / 1913060 (94.0%)
 Active / Total Slabs (% used)      : 238160 / 238169 (100.0%)
 Active / Total Caches (% used)     : 119 / 203 (58.6%)
 Active / Total Size (% used)       : 843529.59K / 859896.40K (98.1%)
 Minimum / Average / Maximum Object : 0.02K / 0.45K / 4096.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
689872 688486  99%    0.88K 172468        4    689872K xfs_inode
294320 288968  98%    0.19K  14716       20     58864K dentry
212380 173765  81%    0.10K   5740       37     22960K buffer_head
192576 179537  93%    0.06K   3264       59     13056K size-64
 82705  82248  99%    0.22K   4865       17     19460K xfs_ili
 48180  43787  90%    0.31K   4015       12     16060K xfs_buf
 47712  40480  84%    0.03K    426      112      1704K size-32
 44572  38447  86%    0.17K   2026       22      8104K vm_area_struct
.....

> In any case, I would posit that both the nature of zpages and their
> average size relative to a whole page is quite unusual compared to slab.

Doesn't sound at all unusual.

> So while there may be some useful comparisons between zswap
> and slab, the differences may warrant dramatically different policy.

There may be differences, but it doesn't sound like there's anything
you can't implment with an appropriate shrinker implmentation....

Cheers,

Dave.
-- 
Dave Chinner
david@fromorbit.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 0/8] zswap: compressed swap caching
  2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
                   ` (10 preceding siblings ...)
  2012-12-12 22:49 ` Luigi Semenzato
@ 2013-01-03 16:01 ` Seth Jennings
  11 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2013-01-03 16:01 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

Now that we are clear of the merge window, I was hoping others could
take a look at this patchset.  If you have a chance, I'd greatly
appreciate the feedback!

Thanks,
Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2012-12-11 21:56 ` [PATCH 7/8] zswap: add to mm/ Seth Jennings
@ 2013-01-03 16:07   ` Seth Jennings
  0 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2013-01-03 16:07 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Rzeszutek Wilk, Dan Magenheimer, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 12/11/2012 03:56 PM, Seth Jennings wrote:
> zswap is a thin compression backend for frontswap. It receives
> pages from frontswap and attempts to store them in a compressed
> memory pool, resulting in an effective partial memory reclaim and
> dramatically reduced swap device I/O.
> 
> Additional, in most cases, pages can be retrieved from this
> compressed store much more quickly than reading from tradition
> swap devices resulting in faster performance for many workloads.
> 
> This patch adds the zswap driver to mm/
> 
> Signed-off-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
> ---
>  include/linux/swap.h |    4 +
>  mm/Kconfig           |   15 +
>  mm/Makefile          |    1 +
>  mm/page_io.c         |   22 +-
>  mm/swap_state.c      |    2 +-
>  mm/zswap.c           | 1077 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 1115 insertions(+), 6 deletions(-)
>  create mode 100644 mm/zswap.c
> 
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index 68df9c1..98981f0 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -321,6 +321,9 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t ent)
>  /* linux/mm/page_io.c */
>  extern int swap_readpage(struct page *);
>  extern int swap_writepage(struct page *page, struct writeback_control *wbc);
> +extern void end_swap_bio_write(struct bio *bio, int err);
> +extern int __swap_writepage(struct page *page, struct writeback_control *wbc,
> +	void (*end_write_func)(struct bio *, int));
>  extern int swap_set_page_dirty(struct page *page);
>  extern void end_swap_bio_read(struct bio *bio, int err);
> 
> @@ -335,6 +338,7 @@ extern struct address_space swapper_space;
>  extern void show_swap_cache_info(void);
>  extern int add_to_swap(struct page *);
>  extern int add_to_swap_cache(struct page *, swp_entry_t, gfp_t);
> +extern int __add_to_swap_cache(struct page *page, swp_entry_t entry);
>  extern void __delete_from_swap_cache(struct page *);
>  extern void delete_from_swap_cache(struct page *);
>  extern void free_page_and_swap_cache(struct page *);
> diff --git a/mm/Kconfig b/mm/Kconfig
> index 1680a012..68cd1b6 100644
> --- a/mm/Kconfig
> +++ b/mm/Kconfig
> @@ -435,3 +435,18 @@ config FRONTSWAP
>  	  and swap data is stored as normal on the matching swap device.
> 
>  	  If unsure, say Y to enable frontswap.
> +
> +config ZSWAP
> +	bool "In-kernel swap page compression"
> +	depends on FRONTSWAP && CRYPTO
> +	select CRYPTO_LZO
> +	select ZSMALLOC
> +	default n
> +	help
> +	  Zswap is a backend for the frontswap mechanism in the VMM.
> +	  It receives pages from frontswap and attempts to store them
> +	  in a compressed memory pool, resulting in an effective
> +	  partial memory reclaim.  In addition, pages and be retrieved
> +	  from this compressed store much faster than most tradition
> +	  swap devices resulting in reduced I/O and faster performance
> +	  for many workloads.
> diff --git a/mm/Makefile b/mm/Makefile
> index 3a46287..1b1ed5c 100644
> --- a/mm/Makefile
> +++ b/mm/Makefile
> @@ -32,6 +32,7 @@ obj-$(CONFIG_HAVE_MEMBLOCK) += memblock.o
>  obj-$(CONFIG_BOUNCE)	+= bounce.o
>  obj-$(CONFIG_SWAP)	+= page_io.o swap_state.o swapfile.o
>  obj-$(CONFIG_FRONTSWAP)	+= frontswap.o
> +obj-$(CONFIG_ZSWAP)	+= zswap.o
>  obj-$(CONFIG_HAS_DMA)	+= dmapool.o
>  obj-$(CONFIG_HUGETLBFS)	+= hugetlb.o
>  obj-$(CONFIG_NUMA) 	+= mempolicy.o
> diff --git a/mm/page_io.c b/mm/page_io.c
> index 78eee32..56276fe 100644
> --- a/mm/page_io.c
> +++ b/mm/page_io.c
> @@ -42,7 +42,7 @@ static struct bio *get_swap_bio(gfp_t gfp_flags,
>  	return bio;
>  }
> 
> -static void end_swap_bio_write(struct bio *bio, int err)
> +void end_swap_bio_write(struct bio *bio, int err)
>  {
>  	const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
>  	struct page *page = bio->bi_io_vec[0].bv_page;
> @@ -179,15 +179,16 @@ bad_bmap:
>  	goto out;
>  }
> 
> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
> +	void (*end_write_func)(struct bio *, int));
> +
>  /*
>   * We may have stale swap cache pages in memory: notice
>   * them here and get rid of the unnecessary final write.
>   */
>  int swap_writepage(struct page *page, struct writeback_control *wbc)
>  {
> -	struct bio *bio;
> -	int ret = 0, rw = WRITE;
> -	struct swap_info_struct *sis = page_swap_info(page);
> +	int ret = 0;
> 
>  	if (try_to_free_swap(page)) {
>  		unlock_page(page);
> @@ -199,6 +200,17 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
>  		end_page_writeback(page);
>  		goto out;
>  	}
> +	ret = __swap_writepage(page, wbc, end_swap_bio_write);
> +out:
> +	return ret;
> +}
> +
> +int __swap_writepage(struct page *page, struct writeback_control *wbc,
> +	void (*end_write_func)(struct bio *, int))
> +{
> +	struct bio *bio;
> +	int ret = 0, rw = WRITE;
> +	struct swap_info_struct *sis = page_swap_info(page);
> 
>  	if (sis->flags & SWP_FILE) {
>  		struct kiocb kiocb;
> @@ -226,7 +238,7 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
>  		return ret;
>  	}
> 
> -	bio = get_swap_bio(GFP_NOIO, page, end_swap_bio_write);
> +	bio = get_swap_bio(GFP_NOIO, page, end_write_func);
>  	if (bio == NULL) {
>  		set_page_dirty(page);
>  		unlock_page(page);
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index d1f6c2d..95a8597 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -68,7 +68,7 @@ void show_swap_cache_info(void)
>   * __add_to_swap_cache resembles add_to_page_cache_locked on swapper_space,
>   * but sets SwapCache flag and private instead of mapping and index.
>   */
> -static int __add_to_swap_cache(struct page *page, swp_entry_t entry)
> +int __add_to_swap_cache(struct page *page, swp_entry_t entry)
>  {
>  	int error;

I'd like to draw attention to these changes made to mm/page_io.c,
mm/swap_state.c, and include/linux/swap.h.  These changes were made to
accommodate zswap's ability to push pages out to the swap device.  I'd
like to make sure that no one has issue with these changes.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
  2013-01-02 23:25         ` Seth Jennings
@ 2013-01-03 22:33           ` Dan Magenheimer
  2013-01-04 15:42             ` Seth Jennings
  0 siblings, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-03 22:33 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel, Dave Hansen

> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> Subject: Re: [PATCH 7/8] zswap: add to mm/
> 
> >> What I'm looking to do is give zswap a little insight into zsmalloc
> >> internals,
> 
> I'm putting at lot of thought into how to do it cleanly, while
> maintaining the generic nature of zsmalloc.  I did a PoC already, but
> I wasn't comfortable with how much had to be exposed to achieve it.
> So I'm still working on it.

Zbud exposes LRU-ness as well... why not also do that
for zsmalloc?  I guess I am asking, as long as you are doing
a "new API" for zsmalloc that zram doesn't benefit from, why not
eliminate the abstraction layer and consider your "zsmalloc2" to
be a captive allocator, like zbud?  What's the point of limiting
"how much had to be exposed" unless you are certain that other
kernel-internal-users will benefit?
 
> > I fear that problem (B) is the fundamental concern with
> > using a high-density storage allocator such as zsmalloc, which
> > is why I abandoned zsmalloc in favor of a more-predictable-but-
> > less-dense allocator (zbud).  However, if you have a solution
> > for (B) as well, I would gladly abandon zbud in zcache (for _both_
> > cleancache and frontswap pages) and our respective in-kernel
> > compression efforts would be more easy to merge into one solution
> > in the future.
> 
> The only difference afaict between zbud and zsmalloc wrt vacating a
> page frame is that zbud will only ever need to write back two pages to
> swap where zsmalloc might have to write back more to free up the
> zspage that contains the page frame to be vacated.  This is doable
> with zsmalloc.  The question that remains for me is how cleanly can it
> be done (for either approach).

One other major difference is that zpages in zsmalloc can cross pageframe
boundaries.  I don't understand zsmalloc well enough to estimate
how frequently this is true, but it is does complicate the set of
race conditions.

Also, unless I misunderstand, ensuring a pageframe can be freed
will require you to somehow "lock" all the zpages and remove
them atomically.  At least, that's what was required for zbud
to avoid races.  If this is true, increasing the number of zpages
to be freed would have a significant impact.

You may have a way around this... I'm just cautioning.

> > Hmmm... IIRC, to avoid races in zcache, it was necessary to
> > update both the data (zpage) and meta-data ("tree" in zswap,
> > and tmem-data-structure in zcache) atomically.  I will need
> > to study your code more to understand how zswap avoids this
> > requirement.  Or if it is obvious to you, I would be grateful
> > if you would point it out to me.
> 
> Without the flushing mechanism, a simple lock guarding the tree was
> enough in zswap.  The per-entry serialization of the
> store/load/invalidate paths are all handled at a higher level.  For
> example, we never get a load and invalidate concurrently on the same
> swap entry.
> 
> However, once the flushing code was introduced and could free an entry
> from the zswap_fs_store() path, it became necessary to add a per-entry
> refcount to make sure that the entry isn't freed while another code
> path was operating on it.

Hmmm... doesn't the refcount at least need to be an atomic_t?

Also, how can you "free" any entry of an rbtree while another
thread is walking the rbtree?  (Deleting an entry from an rbtree
causes rebalancing... afaik there is no equivalent RCU
implementation for rbtrees... not that RCU would necessarily
work well for this anyway.)

BTW, in case it appears otherwise, I'm trying to be helpful, not
critical.  In the end, I think we are in agreement that in-kernel
compression is very important and that the frontswap (and/or
cleancache) interface(s) are the right way to identify compressible
data, and we are mostly arguing allocation and implementation details.

Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
  2013-01-03  7:33               ` Dave Chinner
@ 2013-01-03 22:37                 ` Dan Magenheimer
  2013-01-04  2:30                   ` Dave Chinner
  2013-01-22 23:58                 ` High slab usage testing with zcache/zswap (Was: [PATCH 7/8] zswap: add to mm/) Dan Magenheimer
  1 sibling, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-03 22:37 UTC (permalink / raw)
  To: Dave Chinner
  Cc: Dave Hansen, Seth Jennings, Greg Kroah-Hartman, Andrew Morton,
	Nitin Gupta, Minchan Kim, Konrad Wilk, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

> From: Dave Chinner [mailto:david@fromorbit.com]
> Subject: Re: [PATCH 7/8] zswap: add to mm/
> 
> <much useful info from Dave deleted>

OK, I have suitably proven how little I know about slab
and have received some needed education from your
response... Thanks for that Dave.

So let me ask some questions instead of making
stupid assumptions.

> Thinking that there is a fixed amount of memory that you should
> reserve for some subsystem is simply the wrong approach to take.
> caches are dynamic and the correct system balance should result of
> the natural behaviour of the reclaim algorithms.
>
> The shrinker infrastructure doesn't set any set size goals - it
> simply tries to balance the reclaim across all the shrinkers and
> relative to the page cache... 

First, it's important to note that zcache/zswap is not
really a subsystem.  It's simply a way of increasing
the number of anonymous pages (zswap and zcache) and
pagecache pages (zcache only) in RAM by using compression.
Because compressed pages can't be byte-addressed directly,
pages enter zcache/zswap through a "transformation"
process I've likened to a Fourier transform:  In
their compressed state, they must be managed differently
than normal whole pages.  Compressed anonymous pages must
transition back to uncompressed before they can be used.
Compressed pagecache pages (zcache only) can be either
uncompressed when needed or gratuitously discarded (eventually)
when not needed.

So I've been proceeding with the assumption that it is the
sum of wholepages used by both compressed-anonymous pages
and uncompressed-anonymous pages that must be managed/balanced,
and that this sum should be managed similarly to the non-zxxxx
case of the total number of anonymous pages in the system
(and similarly for compressed+uncompressed pagecache pages).

Are you suggesting that slab can/should be used instead?

> And so the two subsystems need different reclaim implementations.
> And, well, that's exactly what we have shrinkers for - implmenting
> subsystem specific reclaim policy. The shrinker infrastructure is
> responsible for them keeping balance between all the caches that
> have shrinkers and the size of the page cache...

Given the above, do you think either compressed-anonymous-pages or
compressed-pagecache-pages are suitable candidates for the shrinker
infrastructure?

Note that compressed anonymous pages are always dirty so
cannot be "reclaimed" as such.  But the mechanism that Seth
and I are working on causes compressed anonymous pages to
be decompressed and then sent to backing store, which does
(eventually, after I/O latency) free up pageframes.

Currently zcache does use the shrinker API for reclaiming
pageframes-used-for-compressed-pagecache-pages.  Since
these _are_ a form of pagecache pages, is the shrinker suitable?
 
> There are also cases where we've moved metadata caches out of the
> page cache into shrinker controlled caches because the page cache
> reclaim is too simplistic to handle the complex relationships
> between filesystem metadata. We've done this in XFS, and IIRC btrfs
> did this recently as well...

So although the objects in zswap/zcache are less than one page,
they are still "data" not "metadata", true?  In your opinion,
then, should they be managed by core MM, or by shrinker-controlled
caches, by some combination, or independently of either?

> > In any case, I would posit that both the nature of zpages and their
> > average size relative to a whole page is quite unusual compared to slab.
> 
> Doesn't sound at all unusual.

I think I've addressed the different "nature" above, so let
me ask about the size...

Can slab today suitably manage "larger" objects that exceed
half-PAGESIZE?  Or "larger" objects, such as 35%-PAGESIZE where
there would be a great deal of fragmentation?

If so, we should definitely consider slab as an alternative
for zpage allocation.

> > So while there may be some useful comparisons between zswap
> > and slab, the differences may warrant dramatically different policy.
> 
> There may be differences, but it doesn't sound like there's anything
> you can't implment with an appropriate shrinker implmentation....

Depending on your answers above, we may definitely need to
consider that as well!

Thanks,
Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-03 22:37                 ` Dan Magenheimer
@ 2013-01-04  2:30                   ` Dave Chinner
  2013-01-04 15:55                     ` Seth Jennings
  2013-01-04 18:45                     ` Dan Magenheimer
  0 siblings, 2 replies; 38+ messages in thread
From: Dave Chinner @ 2013-01-04  2:30 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Dave Hansen, Seth Jennings, Greg Kroah-Hartman, Andrew Morton,
	Nitin Gupta, Minchan Kim, Konrad Wilk, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On Thu, Jan 03, 2013 at 02:37:01PM -0800, Dan Magenheimer wrote:
> > From: Dave Chinner [mailto:david@fromorbit.com]
> > Subject: Re: [PATCH 7/8] zswap: add to mm/
> > 
> > <much useful info from Dave deleted>
> 
> OK, I have suitably proven how little I know about slab
> and have received some needed education from your
> response... Thanks for that Dave.
> 
> So let me ask some questions instead of making
> stupid assumptions.
> 
> > Thinking that there is a fixed amount of memory that you should
> > reserve for some subsystem is simply the wrong approach to take.
> > caches are dynamic and the correct system balance should result of
> > the natural behaviour of the reclaim algorithms.
> >
> > The shrinker infrastructure doesn't set any set size goals - it
> > simply tries to balance the reclaim across all the shrinkers and
> > relative to the page cache... 
> 
> First, it's important to note that zcache/zswap is not
> really a subsystem.  It's simply a way of increasing
> the number of anonymous pages (zswap and zcache) and
> pagecache pages (zcache only) in RAM by using compression.
> Because compressed pages can't be byte-addressed directly,
> pages enter zcache/zswap through a "transformation"
> process I've likened to a Fourier transform:  In
> their compressed state, they must be managed differently
> than normal whole pages.  Compressed anonymous pages must
> transition back to uncompressed before they can be used.
> Compressed pagecache pages (zcache only) can be either
> uncompressed when needed or gratuitously discarded (eventually)
> when not needed.
> 
> So I've been proceeding with the assumption that it is the
> sum of wholepages used by both compressed-anonymous pages
> and uncompressed-anonymous pages that must be managed/balanced,
> and that this sum should be managed similarly to the non-zxxxx
> case of the total number of anonymous pages in the system
> (and similarly for compressed+uncompressed pagecache pages).
> 
> Are you suggesting that slab can/should be used instead?

I'm not suggesting that any specific solution can/should be used.
What I'm trying to point out that is caches and shrinkers do not
need to be slab based. i.e. all that matters is that you have some
allocation method, some method of tracking the allocated objects,
and some method of reclaiming them, and all the details/policies can
be hidden within the subsystem via shrinker based reclaim...

> > And so the two subsystems need different reclaim implementations.
> > And, well, that's exactly what we have shrinkers for - implmenting
> > subsystem specific reclaim policy. The shrinker infrastructure is
> > responsible for them keeping balance between all the caches that
> > have shrinkers and the size of the page cache...
> 
> Given the above, do you think either compressed-anonymous-pages or
> compressed-pagecache-pages are suitable candidates for the shrinker
> infrastructure?

I don't know all the details of what you are trying to do, but you
seem to be describing a two-level heirarchy - a pool of compressed
data and a pool of uncompressed data, and under memory pressure are
migrating data from the uncompressed pool to the compressed pool. On
access, you are migrating back the other way.  Hence it seems to me
that you could implement the process of migration from the
uncompressed pool to the compressed pool as a shrinker so that it
only happens as a result of memory pressure....

> Note that compressed anonymous pages are always dirty so
> cannot be "reclaimed" as such.  But the mechanism that Seth
> and I are working on causes compressed anonymous pages to
> be decompressed and then sent to backing store, which does
> (eventually, after I/O latency) free up pageframes.

The lack of knowledge I have about zcache/zswap means I might be
saying something stupid, but why wouldn't you simply write the
uncompressed page to the backing store and then compress it on IO
completion? If you have to uncompress it for the application to
either modify the page again or write it to the backing store,
doesn't it make things much simpler if the cache only holds clean
pages? And if it only holds clean pages, then another shrinker could
be used to keep the size of it in check....

> Currently zcache does use the shrinker API for reclaiming
> pageframes-used-for-compressed-pagecache-pages.  Since
> these _are_ a form of pagecache pages, is the shrinker suitable?

Yes.

> > There are also cases where we've moved metadata caches out of the
> > page cache into shrinker controlled caches because the page cache
> > reclaim is too simplistic to handle the complex relationships
> > between filesystem metadata. We've done this in XFS, and IIRC btrfs
> > did this recently as well...
> 
> So although the objects in zswap/zcache are less than one page,
> they are still "data" not "metadata", true?

The page cache can be used to hold both filesystem metadata and user
data. As far as you're concerned, the page cache holds "information"
and you cannot make judgements about it's contents....

> In your opinion,
> then, should they be managed by core MM, or by shrinker-controlled
> caches, by some combination, or independently of either?

I think the entire MM could be run by the shrinker based reclaim
infrastructure. You should probably have a read of the discussions
in this thread to get an idea of where we are trying to get to with
the shrinker infrastructure:

https://lkml.org/lkml/2012/11/27/567

(Warning: I don't say very nice things about the zcache/ramster
shrinkers in that patch series. :/ )

> Can slab today suitably manage "larger" objects that exceed
> half-PAGESIZE?  Or "larger" objects, such as 35%-PAGESIZE where
> there would be a great deal of fragmentation?

Have a look at how the kernel heap is implemented:

# grep "^# name\|^size-[0-9]* " /proc/slabinfo | cut -d ":" -f 1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> 
size-4194304           0      0 4194304    1 1024 
size-2097152           0      0 2097152    1  512 
size-1048576           2      2 1048576    1  256 
size-524288            0      0 524288    1  128 
size-262144            0      0 262144    1   64 
size-131072            0      0 131072    1   32 
size-65536            24     24  65536    1   16 
size-32768             3      3  32768    1    8 
size-16384           281    288  16384    1    4 
size-8192             70     70   8192    1    2 
size-4096            346    346   4096    1    1 
size-2048            580    608   2048    2    1 
size-1024          18745  18980   1024    4    1 
size-512            1234   1264    512    8    1 
size-256            1549   1695    256   15    1 
size-192            4578   5340    192   20    1 
size-64           148919 194405     64   59    1 
size-128           35906  37080    128   30    1 
size-32            40743  47488     32  112    1 

i.e. it's implemented as a bunch of power-of-2 sized slab caches,
with object sizes that range up to 4MB. IIRC, SLUB is better suited
to odd sized objects than SLAB due to it's ability to have multiple
pages per slab even for objects smaller than page sized......

> If so, we should definitely consider slab as an alternative
> for zpage allocation.

Or you could just use kmalloc... ;)

As I said initially - don't think of whether you need to use slab
allocation or otherwise. Start with simple allocation, a tracking
mechanism and a rudimetary shrinker, and then optimise allocation and
reclaim once you understand the limitations of the simple
solution....

Cheers,

Dave.
-- 
Dave Chinner
david@fromorbit.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-03 22:33           ` Dan Magenheimer
@ 2013-01-04 15:42             ` Seth Jennings
  2013-01-04 22:45               ` Dan Magenheimer
  0 siblings, 1 reply; 38+ messages in thread
From: Seth Jennings @ 2013-01-04 15:42 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel, Dave Hansen

On 01/03/2013 04:33 PM, Dan Magenheimer wrote:
>> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
>>
>> However, once the flushing code was introduced and could free an entry
>> from the zswap_fs_store() path, it became necessary to add a per-entry
>> refcount to make sure that the entry isn't freed while another code
>> path was operating on it.
> 
> Hmmm... doesn't the refcount at least need to be an atomic_t?

An entry's refcount is only ever changed under the tree lock, so
making them atomic_t would be redundantly atomic.

I should add a comment to that effect though, including all elements
that are protected by the tree lock which include:
* the tree structure
* the lru list
* the per-entry refcounts

I'll put that change in the queue for v2.

> Also, how can you "free" any entry of an rbtree while another
> thread is walking the rbtree?  (Deleting an entry from an rbtree
> causes rebalancing... afaik there is no equivalent RCU
> implementation for rbtrees... not that RCU would necessarily
> work well for this anyway.)

This also can't happen since a thread must obtain the tree lock before
accessing or changing the tree.

Regarding RCU, I saw that some work had been done on RCU aware rbtree
functions but they weren't ready yet.

> BTW, in case it appears otherwise, I'm trying to be helpful, not
> critical.  In the end, I think we are in agreement that in-kernel
> compression is very important and that the frontswap (and/or
> cleancache) interface(s) are the right way to identify compressible
> data, and we are mostly arguing allocation and implementation details.

Yes. I'm always grateful for comments about the code :)  At the very
least, it rehashes the justifications for design decisions.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-04  2:30                   ` Dave Chinner
@ 2013-01-04 15:55                     ` Seth Jennings
  2013-01-04 18:45                     ` Dan Magenheimer
  1 sibling, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2013-01-04 15:55 UTC (permalink / raw)
  To: Dave Chinner
  Cc: Dan Magenheimer, Dave Hansen, Greg Kroah-Hartman, Andrew Morton,
	Nitin Gupta, Minchan Kim, Konrad Wilk, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

On 01/03/2013 08:30 PM, Dave Chinner wrote:
>>> And so the two subsystems need different reclaim implementations.
>>> > > And, well, that's exactly what we have shrinkers for - implmenting
>>> > > subsystem specific reclaim policy. The shrinker infrastructure is
>>> > > responsible for them keeping balance between all the caches that
>>> > > have shrinkers and the size of the page cache...
>> > 
>> > Given the above, do you think either compressed-anonymous-pages or
>> > compressed-pagecache-pages are suitable candidates for the shrinker
>> > infrastructure?
> I don't know all the details of what you are trying to do, but you
> seem to be describing a two-level heirarchy - a pool of compressed
> data and a pool of uncompressed data, and under memory pressure are
> migrating data from the uncompressed pool to the compressed pool. On
> access, you are migrating back the other way.  Hence it seems to me
> that you could implement the process of migration from the
> uncompressed pool to the compressed pool as a shrinker so that it
> only happens as a result of memory pressure....

In our case, the mechanism for moving pages from the uncompressed pool
(anonymous memory) to the compressed pool is the swapping mechanism
itself.  The mechanism for moving pages the other way is the page
fault handler.

To summarize my ideas wrt to zswap and the shrinker interface, I don't
think there is a good use case here because all of the compressed
pages in zswap are conceptually dirty.  The writeback for these pages
is both slow (bio write) and requires memory allocation.  So zswap
isn't a cache in the usual sense, where cache contents are clean,
exist in RAM only for performance reasons, and can be freed in a
lightweight way at any time.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
  2013-01-04  2:30                   ` Dave Chinner
  2013-01-04 15:55                     ` Seth Jennings
@ 2013-01-04 18:45                     ` Dan Magenheimer
  1 sibling, 0 replies; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-04 18:45 UTC (permalink / raw)
  To: Dave Chinner
  Cc: Dave Hansen, Seth Jennings, Greg Kroah-Hartman, Andrew Morton,
	Nitin Gupta, Minchan Kim, Konrad Wilk, Robert Jennings,
	Jenifer Hopper, Mel Gorman, Johannes Weiner, Rik van Riel,
	Larry Woodman, linux-mm, linux-kernel, devel

> From: Dave Chinner [mailto:david@fromorbit.com]
> Subject: Re: [PATCH 7/8] zswap: add to mm/

Hi Dave --

Thanks for your continued helpful feedback and expertise!
 
> > Given the above, do you think either compressed-anonymous-pages or
> > compressed-pagecache-pages are suitable candidates for the shrinker
> > infrastructure?
> 
> I don't know all the details of what you are trying to do, but you
> seem to be describing a two-level heirarchy - a pool of compressed
> data and a pool of uncompressed data, and under memory pressure are
> migrating data from the uncompressed pool to the compressed pool. On
> access, you are migrating back the other way.  Hence it seems to me
> that you could implement the process of migration from the
> uncompressed pool to the compressed pool as a shrinker so that it
> only happens as a result of memory pressure....

I suppose that would be an option, but the current triggers
for compression are: (for anonymous pages) the decision by
the MM subsystem to swap-out a specific page; and (for
pagecache pages) the decision by the MM subsystem to reclaim
a specific pagecache page.  This is all handled by the cleancache
and frontswap APIs/hooks that Linus merged at 3.0/3.5.

This approach leveraged all the existing MM mechanisms to
ensure that all existing memory pressure valves are honored
unchanged, and also ensures that MM has selected the lowest
priority pages (and thus presumably the pages least likely
to be directly addressed soon).

You're correct that the normal trigger for decompression is
access, but this is handled through frontswap/cleancache
hooks in the existing pagefault paths.  So this also honors
all existing memory pressure mechanisms.

So, it is the "abnormal" decompression triggers that we are
mostly exploring here:  For anonymous pages, we reach a point
where zcache/zswap is "full" and we wish we would have used
the swap disk for the LRU pages... so we need to decompress
some pages and move them to the "real" swap device.  And for
pagecache pages, we somehow determine that we need to throw
away some zpages, and we'd like to throw away as few zpages
as possible (preferably in some kind of LRU order), while
freeing up as many wholepages as possible.

This last is the only current (feebly attempted) use of the
shrinker API.

> > Note that compressed anonymous pages are always dirty so
> > cannot be "reclaimed" as such.  But the mechanism that Seth
> > and I are working on causes compressed anonymous pages to
> > be decompressed and then sent to backing store, which does
> > (eventually, after I/O latency) free up pageframes.
> 
> The lack of knowledge I have about zcache/zswap means I might be
> saying something stupid, but why wouldn't you simply write the
> uncompressed page to the backing store and then compress it on IO
> completion? If you have to uncompress it for the application to
> either modify the page again or write it to the backing store,
> doesn't it make things much simpler if the cache only holds clean
> pages? And if it only holds clean pages, then another shrinker could
> be used to keep the size of it in check....

A good point, and this is actually already implemented as an option.
(See frontswap_writethrough_enabled.)  But it has the unfortunate
side effect of generating a lot of swap-disk write traffic that,
in many circumstances, could have been completely avoided.
For some reason, performance also sucked... though that was
never investigated so may have been some silly bug and we should
revisit it.
 
> > In your opinion,
> > then, should they be managed by core MM, or by shrinker-controlled
> > caches, by some combination, or independently of either?
> 
> I think the entire MM could be run by the shrinker based reclaim
> infrastructure. You should probably have a read of the discussions
> in this thread to get an idea of where we are trying to get to with
> the shrinker infrastructure:
> 
> https://lkml.org/lkml/2012/11/27/567
> 
> (Warning: I don't say very nice things about the zcache/ramster
> shrinkers in that patch series. :/ )

Heh.  No offense taken.  I hope your brain has recovered and you
managed to avoid tearing out your eyeballs.  That code was definitely
not ready for primetime and not really even ready for staging,
but had to be published due to various unfortunate circumstances.

If you have suggestions for other improvements (in addition
to your broader patchset), we would be eager for your help!

> > Can slab today suitably manage "larger" objects that exceed
> > half-PAGESIZE?  Or "larger" objects, such as 35%-PAGESIZE where
> > there would be a great deal of fragmentation?
> 
> Have a look at how the kernel heap is implemented:
> 
> <snip>
> 
> i.e. it's implemented as a bunch of power-of-2 sized slab caches,
> with object sizes that range up to 4MB. IIRC, SLUB is better suited
> to odd sized objects than SLAB due to it's ability to have multiple
> pages per slab even for objects smaller than page sized......

Hmmm... I was unclear.  *All* objects (aka zpages) stored by zcache/zswap
are less than PAGESIZE, and a large percent are between PAGESIZE/2
and PAGESIZE, and a large percent are between PAGESIZE/3 and PAGESIZE/2.
I don't believe slab (or slub or kmalloc) can handle these efficiently
without significant fragmentation, though it may be my poor understanding
of slab/slub.
 
> > If so, we should definitely consider slab as an alternative
> > for zpage allocation.
> 
> Or you could just use kmalloc... ;)
> 
> As I said initially - don't think of whether you need to use slab
> allocation or otherwise. Start with simple allocation, a tracking
> mechanism and a rudimetary shrinker, and then optimise allocation and
> reclaim once you understand the limitations of the simple
> solution....

Indeed.  I think that's where we are at... optimising the reclaim
now that we understand the limitations of the rudimentary (eye-clawing-out)
shrinker.  Please help if you have ideas!

Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

* RE: [PATCH 7/8] zswap: add to mm/
  2013-01-04 15:42             ` Seth Jennings
@ 2013-01-04 22:45               ` Dan Magenheimer
  2013-01-07 14:47                 ` Seth Jennings
  0 siblings, 1 reply; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-04 22:45 UTC (permalink / raw)
  To: Seth Jennings
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel, Dave Hansen

> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> Subject: Re: [PATCH 7/8] zswap: add to mm/
> 
> On 01/03/2013 04:33 PM, Dan Magenheimer wrote:
> >> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
> >>
> >> However, once the flushing code was introduced and could free an entry
> >> from the zswap_fs_store() path, it became necessary to add a per-entry
> >> refcount to make sure that the entry isn't freed while another code
> >> path was operating on it.
> >
> > Hmmm... doesn't the refcount at least need to be an atomic_t?
> 
> An entry's refcount is only ever changed under the tree lock, so
> making them atomic_t would be redundantly atomic.

Maybe I'm missing something still but then I think you also
need to evaluate and act on the refcount (not just read it) while
your treelock is held.  I.e., in:

> +		/* page is already in the swap cache, ignore for now */
> +		spin_lock(&tree->lock);
> +		refcount = zswap_entry_put(entry);
> +		spin_unlock(&tree->lock);
> +
> +		if (likely(refcount))
> +			return 0;
> +
> +		/* if the refcount is zero, invalidate must have come in */
> +		/* free */
> +		zs_free(tree->pool, entry->handle);
> +		zswap_entry_cache_free(entry);
> +		atomic_dec(&zswap_stored_pages);

the entry's refcount may be changed by another processor
immediately after the unlock, and then the "if (refcount)"
is testing a stale value and you will get (I think) a memory leak.

There is similar racy code in zswap_fs_invalidate_page which
I think could lead to a double free.  There's another
I think in zswap_fs_load...  And the refcount is dec'd
in one path inside of zswap_fs_store as well which may
race with the above.

When flushing multiple zpages to free a pageframe, you may
need to test refcounts for all the entries while within the lock.
If so, this is one place where the high-density storage will make
things messy, especially if page boundaries are crossed.

A nit: Even I, steeped in tmem terminology, was confused by
your use of "fs"... to nearly all readers it will
be translated as "filesystem" which is mystifying.
Just spell it out "frontswap", even if it causes a few
lines to be wrapped.

Have a good weekend!
Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 7/8] zswap: add to mm/
  2013-01-04 22:45               ` Dan Magenheimer
@ 2013-01-07 14:47                 ` Seth Jennings
  0 siblings, 0 replies; 38+ messages in thread
From: Seth Jennings @ 2013-01-07 14:47 UTC (permalink / raw)
  To: Dan Magenheimer
  Cc: Greg Kroah-Hartman, Andrew Morton, Nitin Gupta, Minchan Kim,
	Konrad Wilk, Robert Jennings, Jenifer Hopper, Mel Gorman,
	Johannes Weiner, Rik van Riel, Larry Woodman, linux-mm,
	linux-kernel, devel, Dave Hansen

On 01/04/2013 04:45 PM, Dan Magenheimer wrote:
>> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
>> Subject: Re: [PATCH 7/8] zswap: add to mm/
>>
>> On 01/03/2013 04:33 PM, Dan Magenheimer wrote:
>>>> From: Seth Jennings [mailto:sjenning@linux.vnet.ibm.com]
>>>>
>>>> However, once the flushing code was introduced and could free an entry
>>>> from the zswap_fs_store() path, it became necessary to add a per-entry
>>>> refcount to make sure that the entry isn't freed while another code
>>>> path was operating on it.
>>>
>>> Hmmm... doesn't the refcount at least need to be an atomic_t?
>>
>> An entry's refcount is only ever changed under the tree lock, so
>> making them atomic_t would be redundantly atomic.
> 
> Maybe I'm missing something still but then I think you also
> need to evaluate and act on the refcount (not just read it) while
> your treelock is held.  I.e., in:
> 
>> +		/* page is already in the swap cache, ignore for now */
>> +		spin_lock(&tree->lock);
>> +		refcount = zswap_entry_put(entry);
>> +		spin_unlock(&tree->lock);
>> +
>> +		if (likely(refcount))
>> +			return 0;
>> +
>> +		/* if the refcount is zero, invalidate must have come in */
>> +		/* free */
>> +		zs_free(tree->pool, entry->handle);
>> +		zswap_entry_cache_free(entry);
>> +		atomic_dec(&zswap_stored_pages);
> 
> the entry's refcount may be changed by another processor
> immediately after the unlock, and then the "if (refcount)"
> is testing a stale value and you will get (I think) a memory leak.

It is true that the refcount could be stale by the time we do the
check. However, all functions that do a zswap_entry_put(), which
potentially drops the refcount to 0, check the refcount and free the
entry if they need to.  All the functions that do a zswap_entry_put()
that result in the refcount being 0 also ensure that there is no way
for another thread to gain a reference to entry by either the tree or
lru list before releasing the lock.  That way the cleanup can happen
outside the lock with the risk of someone gaining access to the entry
being freed in the meantime.

<snip>
> A nit: Even I, steeped in tmem terminology, was confused by
> your use of "fs"... to nearly all readers it will
> be translated as "filesystem" which is mystifying.
> Just spell it out "frontswap", even if it causes a few
> lines to be wrapped.

Sound good. I'll queue it up.

Thanks,
Seth


^ permalink raw reply	[flat|nested] 38+ messages in thread

* High slab usage testing with zcache/zswap (Was: [PATCH 7/8] zswap: add to mm/)
  2013-01-03  7:33               ` Dave Chinner
  2013-01-03 22:37                 ` Dan Magenheimer
@ 2013-01-22 23:58                 ` Dan Magenheimer
  1 sibling, 0 replies; 38+ messages in thread
From: Dan Magenheimer @ 2013-01-22 23:58 UTC (permalink / raw)
  To: Dave Chinner; +Cc: Seth Jennings, Konrad Wilk, linux-mm, linux-kernel

> From: Dave Chinner [mailto:david@fromorbit.com]
> Sent: Thursday, January 03, 2013 12:34 AM
> Subject: Re: [PATCH 7/8] zswap: add to mm/
> 
> > > On 01/02/2013 09:26 AM, Dan Magenheimer wrote:
> > > > However if one compares the total percentage
> > > > of RAM used for zpages by zswap vs the total percentage of RAM
> > > > used by slab, I suspect that the zswap number will dominate,
> > > > perhaps because zswap is storing primarily data and slab is
> > > > storing primarily metadata?
> > >
> > > That's *obviously* 100% dependent on how you configure zswap.  But, that
> > > said, most of _my_ systems tend to sit with about 5% of memory in
> > > reclaimable slab
> >
> > The 5% "sitting" number for slab is somewhat interesting, but
> > IMHO irrelevant here. The really interesting value is what percent
> > is used by slab when the system is under high memory pressure; I'd
> > imagine that number would be much smaller.  True?
> 
> Not at all. The amount of slab memory used is wholly dependent on
> workload. I have plenty of workloads with severe memory pressure
> that I test with that sit at a steady state of >80% of ram in slab
> caches. These workloads are filesytem metadata intensive rather than
> data intensive, that's exactly the right cache balance for the
> system to have....

Hey Dave --

I'd like to do some zcache policy testing where the severe
memory pressure is a result of something like the above
where >80% of ram is in slab caches.  Any thoughts on how
to do this or easily simulate it on a very simple hardware
system (e.g. PC with one SATA disk)?  Or is a "big data"
configuration required?

Thanks for any advice!
Dan

^ permalink raw reply	[flat|nested] 38+ messages in thread

end of thread, other threads:[~2013-01-22 23:59 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <<1355262966-15281-1-git-send-email-sjenning@linux.vnet.ibm.com>
     [not found] ` <<1355262966-15281-8-git-send-email-sjenning@linux.vnet.ibm.com>
2012-12-31 23:06   ` [PATCH 7/8] zswap: add to mm/ Dan Magenheimer
2013-01-01 17:52     ` Seth Jennings
2013-01-02 15:55       ` Dave Hansen
2013-01-02 17:26         ` Dan Magenheimer
2013-01-02 18:17           ` Dave Hansen
2013-01-02 19:04             ` Dan Magenheimer
2013-01-03  7:33               ` Dave Chinner
2013-01-03 22:37                 ` Dan Magenheimer
2013-01-04  2:30                   ` Dave Chinner
2013-01-04 15:55                     ` Seth Jennings
2013-01-04 18:45                     ` Dan Magenheimer
2013-01-22 23:58                 ` High slab usage testing with zcache/zswap (Was: [PATCH 7/8] zswap: add to mm/) Dan Magenheimer
2013-01-02 22:44         ` [PATCH 7/8] zswap: add to mm/ Seth Jennings
2013-01-02 17:08       ` Dan Magenheimer
2013-01-02 23:25         ` Seth Jennings
2013-01-03 22:33           ` Dan Magenheimer
2013-01-04 15:42             ` Seth Jennings
2013-01-04 22:45               ` Dan Magenheimer
2013-01-07 14:47                 ` Seth Jennings
2012-12-11 21:55 [PATCH 0/8] zswap: compressed swap caching Seth Jennings
2012-12-11 21:55 ` [PATCH 1/8] staging: zsmalloc: add gfp flags to zs_create_pool Seth Jennings
2012-12-11 21:56 ` [PATCH 2/8] staging: zsmalloc: remove unsed pool name Seth Jennings
2012-12-11 21:56 ` [PATCH 3/8] staging: zsmalloc: add page alloc/free callbacks Seth Jennings
2012-12-11 21:56 ` [PATCH 4/8] staging: zsmalloc: make CLASS_DELTA relative to PAGE_SIZE Seth Jennings
2012-12-11 21:56 ` [PATCH 5/8] debugfs: add get/set for atomic types Seth Jennings
2012-12-11 21:56 ` [PATCH 6/8] zsmalloc: promote to lib/ Seth Jennings
2012-12-11 21:56 ` [PATCH 7/8] zswap: add to mm/ Seth Jennings
2013-01-03 16:07   ` Seth Jennings
2012-12-11 21:56 ` [PATCH 8/8] zswap: add documentation Seth Jennings
2012-12-11 22:01 ` [PATCH 0/8] zswap: compressed swap caching Greg Kroah-Hartman
2012-12-12 16:29   ` Seth Jennings
2012-12-12 17:27     ` Dan Magenheimer
2012-12-12 18:32       ` Seth Jennings
2012-12-12 18:36 ` Seth Jennings
2012-12-12 22:49 ` Luigi Semenzato
2012-12-12 23:46   ` Dan Magenheimer
2012-12-14 15:59   ` Seth Jennings
2013-01-03 16:01 ` Seth Jennings

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).