diff options
author | Srividya Desireddy <srividya.dr@samsung.com> | 2018-01-31 19:15:59 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-01-31 20:18:36 -0500 |
commit | a85f878b443f8d2b91ba76f09da21ac0af22e07f (patch) | |
tree | 6328e7fe0d39c9ba4e3fbc437d2c7291a135f8fe | |
parent | 4a01768e9e91082efc9a6384b1ef579fdcbce828 (diff) |
zswap: same-filled pages handling
Zswap is a cache which compresses the pages that are being swapped out
and stores them into a dynamically allocated RAM-based memory pool.
Experiments have shown that around 10-20% of pages stored in zswap are
same-filled pages (i.e. contents of the page are all same), but these
pages are handled as normal pages by compressing and allocating memory
in the pool.
This patch adds a check in zswap_frontswap_store() to identify
same-filled page before compression of the page. If the page is a
same-filled page, set zswap_entry.length to zero, save the same-filled
value and skip the compression of the page and alloction of memory in
zpool. In zswap_frontswap_load(), check if value of zswap_entry.length
is zero corresponding to the page to be loaded. If zswap_entry.length
is zero, fill the page with same-filled value. This saves the
decompression time during load.
On a ARM Quad Core 32-bit device with 1.5GB RAM by launching and
relaunching different applications, out of ~64000 pages stored in zswap,
~11000 pages were same-value filled pages (including zero-filled pages)
and ~9000 pages were zero-filled pages.
An average of 17% of pages(including zero-filled pages) in zswap are
same-value filled pages and 14% pages are zero-filled pages. An average
of 3% of pages are same-filled non-zero pages.
The below table shows the execution time profiling with the patch.
Baseline With patch % Improvement
-----------------------------------------------------------------
*Zswap Store Time 26.5ms 18ms 32%
(of same value pages)
*Zswap Load Time
(of same value pages) 25.5ms 13ms 49%
-----------------------------------------------------------------
On Ubuntu PC with 2GB RAM, while executing kernel build and other test
scripts and running multimedia applications, out of 360000 pages stored
in zswap 78000(~22%) of pages were found to be same-value filled pages
(including zero-filled pages) and 64000(~17%) are zero-filled pages. So
an average of %5 of pages are same-filled non-zero pages.
The below table shows the execution time profiling with the patch.
Baseline With patch % Improvement
-----------------------------------------------------------------
*Zswap Store Time 91ms 74ms 19%
(of same value pages)
*Zswap Load Time 50ms 7.5ms 85%
(of same value pages)
-----------------------------------------------------------------
*The execution times may vary with test device used.
Dan said:
: I did test this patch out this week, and I added some instrumentation to
: check the performance impact, and tested with a small program to try to
: check the best and worst cases.
:
: When doing a lot of swap where all (or almost all) pages are same-value, I
: found this patch does save both time and space, significantly. The exact
: improvement in time and space depends on which compressor is being used,
: but roughly agrees with the numbers you listed.
:
: In the worst case situation, where all (or almost all) pages have the
: same-value *except* the final long (meaning, zswap will check each long on
: the entire page but then still have to pass the page to the compressor),
: the same-value check is around 10-15% of the total time spent in
: zswap_frontswap_store(). That's a not-insignificant amount of time, but
: it's not huge. Considering that most systems will probably be swapping
: pages that aren't similar to the worst case (although I don't have any
: data to know that), I'd say the improvement is worth the possible
: worst-case performance impact.
[srividya.dr@samsung.com: add memset_l instead of for loop]
Link: http://lkml.kernel.org/r/20171018104832epcms5p1b2232e2236258de3d03d1344dde9fce0@epcms5p1
Signed-off-by: Srividya Desireddy <srividya.dr@samsung.com>
Acked-by: Dan Streetman <ddstreet@ieee.org>
Cc: Seth Jennings <sjenning@redhat.com>
Cc: Pekka Enberg <penberg@kernel.org>
Cc: Dinakar Reddy Pathireddy <dinakar.p@samsung.com>
Cc: SHARAN ALLUR <sharan.allur@samsung.com>
Cc: RAJIB BASU <rajib.basu@samsung.com>
Cc: JUHUN KIM <juhunkim@samsung.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Timofey Titovets <nefelim4ag@gmail.com>
Cc: Andi Kleen <ak@linux.intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | mm/zswap.c | 71 |
1 files changed, 66 insertions, 5 deletions
diff --git a/mm/zswap.c b/mm/zswap.c index d39581a076c3..1133b4ceb72e 100644 --- a/mm/zswap.c +++ b/mm/zswap.c | |||
@@ -49,6 +49,8 @@ | |||
49 | static u64 zswap_pool_total_size; | 49 | static u64 zswap_pool_total_size; |
50 | /* The number of compressed pages currently stored in zswap */ | 50 | /* The number of compressed pages currently stored in zswap */ |
51 | static atomic_t zswap_stored_pages = ATOMIC_INIT(0); | 51 | static atomic_t zswap_stored_pages = ATOMIC_INIT(0); |
52 | /* The number of same-value filled pages currently stored in zswap */ | ||
53 | static atomic_t zswap_same_filled_pages = ATOMIC_INIT(0); | ||
52 | 54 | ||
53 | /* | 55 | /* |
54 | * The statistics below are not protected from concurrent access for | 56 | * The statistics below are not protected from concurrent access for |
@@ -116,6 +118,11 @@ module_param_cb(zpool, &zswap_zpool_param_ops, &zswap_zpool_type, 0644); | |||
116 | static unsigned int zswap_max_pool_percent = 20; | 118 | static unsigned int zswap_max_pool_percent = 20; |
117 | module_param_named(max_pool_percent, zswap_max_pool_percent, uint, 0644); | 119 | module_param_named(max_pool_percent, zswap_max_pool_percent, uint, 0644); |
118 | 120 | ||
121 | /* Enable/disable handling same-value filled pages (enabled by default) */ | ||
122 | static bool zswap_same_filled_pages_enabled = true; | ||
123 | module_param_named(same_filled_pages_enabled, zswap_same_filled_pages_enabled, | ||
124 | bool, 0644); | ||
125 | |||
119 | /********************************* | 126 | /********************************* |
120 | * data structures | 127 | * data structures |
121 | **********************************/ | 128 | **********************************/ |
@@ -145,9 +152,10 @@ struct zswap_pool { | |||
145 | * be held while changing the refcount. Since the lock must | 152 | * be held while changing the refcount. Since the lock must |
146 | * be held, there is no reason to also make refcount atomic. | 153 | * be held, there is no reason to also make refcount atomic. |
147 | * length - the length in bytes of the compressed page data. Needed during | 154 | * length - the length in bytes of the compressed page data. Needed during |
148 | * decompression | 155 | * decompression. For a same value filled page length is 0. |
149 | * pool - the zswap_pool the entry's data is in | 156 | * pool - the zswap_pool the entry's data is in |
150 | * handle - zpool allocation handle that stores the compressed page data | 157 | * handle - zpool allocation handle that stores the compressed page data |
158 | * value - value of the same-value filled pages which have same content | ||
151 | */ | 159 | */ |
152 | struct zswap_entry { | 160 | struct zswap_entry { |
153 | struct rb_node rbnode; | 161 | struct rb_node rbnode; |
@@ -155,7 +163,10 @@ struct zswap_entry { | |||
155 | int refcount; | 163 | int refcount; |
156 | unsigned int length; | 164 | unsigned int length; |
157 | struct zswap_pool *pool; | 165 | struct zswap_pool *pool; |
158 | unsigned long handle; | 166 | union { |
167 | unsigned long handle; | ||
168 | unsigned long value; | ||
169 | }; | ||
159 | }; | 170 | }; |
160 | 171 | ||
161 | struct zswap_header { | 172 | struct zswap_header { |
@@ -320,8 +331,12 @@ static void zswap_rb_erase(struct rb_root *root, struct zswap_entry *entry) | |||
320 | */ | 331 | */ |
321 | static void zswap_free_entry(struct zswap_entry *entry) | 332 | static void zswap_free_entry(struct zswap_entry *entry) |
322 | { | 333 | { |
323 | zpool_free(entry->pool->zpool, entry->handle); | 334 | if (!entry->length) |
324 | zswap_pool_put(entry->pool); | 335 | atomic_dec(&zswap_same_filled_pages); |
336 | else { | ||
337 | zpool_free(entry->pool->zpool, entry->handle); | ||
338 | zswap_pool_put(entry->pool); | ||
339 | } | ||
325 | zswap_entry_cache_free(entry); | 340 | zswap_entry_cache_free(entry); |
326 | atomic_dec(&zswap_stored_pages); | 341 | atomic_dec(&zswap_stored_pages); |
327 | zswap_update_total_size(); | 342 | zswap_update_total_size(); |
@@ -953,6 +968,28 @@ static int zswap_shrink(void) | |||
953 | return ret; | 968 | return ret; |
954 | } | 969 | } |
955 | 970 | ||
971 | static int zswap_is_page_same_filled(void *ptr, unsigned long *value) | ||
972 | { | ||
973 | unsigned int pos; | ||
974 | unsigned long *page; | ||
975 | |||
976 | page = (unsigned long *)ptr; | ||
977 | for (pos = 1; pos < PAGE_SIZE / sizeof(*page); pos++) { | ||
978 | if (page[pos] != page[0]) | ||
979 | return 0; | ||
980 | } | ||
981 | *value = page[0]; | ||
982 | return 1; | ||
983 | } | ||
984 | |||
985 | static void zswap_fill_page(void *ptr, unsigned long value) | ||
986 | { | ||
987 | unsigned long *page; | ||
988 | |||
989 | page = (unsigned long *)ptr; | ||
990 | memset_l(page, value, PAGE_SIZE / sizeof(unsigned long)); | ||
991 | } | ||
992 | |||
956 | /********************************* | 993 | /********************************* |
957 | * frontswap hooks | 994 | * frontswap hooks |
958 | **********************************/ | 995 | **********************************/ |
@@ -965,7 +1002,7 @@ static int zswap_frontswap_store(unsigned type, pgoff_t offset, | |||
965 | struct crypto_comp *tfm; | 1002 | struct crypto_comp *tfm; |
966 | int ret; | 1003 | int ret; |
967 | unsigned int dlen = PAGE_SIZE, len; | 1004 | unsigned int dlen = PAGE_SIZE, len; |
968 | unsigned long handle; | 1005 | unsigned long handle, value; |
969 | char *buf; | 1006 | char *buf; |
970 | u8 *src, *dst; | 1007 | u8 *src, *dst; |
971 | struct zswap_header *zhdr; | 1008 | struct zswap_header *zhdr; |
@@ -993,6 +1030,19 @@ static int zswap_frontswap_store(unsigned type, pgoff_t offset, | |||
993 | goto reject; | 1030 | goto reject; |
994 | } | 1031 | } |
995 | 1032 | ||
1033 | if (zswap_same_filled_pages_enabled) { | ||
1034 | src = kmap_atomic(page); | ||
1035 | if (zswap_is_page_same_filled(src, &value)) { | ||
1036 | kunmap_atomic(src); | ||
1037 | entry->offset = offset; | ||
1038 | entry->length = 0; | ||
1039 | entry->value = value; | ||
1040 | atomic_inc(&zswap_same_filled_pages); | ||
1041 | goto insert_entry; | ||
1042 | } | ||
1043 | kunmap_atomic(src); | ||
1044 | } | ||
1045 | |||
996 | /* if entry is successfully added, it keeps the reference */ | 1046 | /* if entry is successfully added, it keeps the reference */ |
997 | entry->pool = zswap_pool_current_get(); | 1047 | entry->pool = zswap_pool_current_get(); |
998 | if (!entry->pool) { | 1048 | if (!entry->pool) { |
@@ -1037,6 +1087,7 @@ static int zswap_frontswap_store(unsigned type, pgoff_t offset, | |||
1037 | entry->handle = handle; | 1087 | entry->handle = handle; |
1038 | entry->length = dlen; | 1088 | entry->length = dlen; |
1039 | 1089 | ||
1090 | insert_entry: | ||
1040 | /* map */ | 1091 | /* map */ |
1041 | spin_lock(&tree->lock); | 1092 | spin_lock(&tree->lock); |
1042 | do { | 1093 | do { |
@@ -1089,6 +1140,13 @@ static int zswap_frontswap_load(unsigned type, pgoff_t offset, | |||
1089 | } | 1140 | } |
1090 | spin_unlock(&tree->lock); | 1141 | spin_unlock(&tree->lock); |
1091 | 1142 | ||
1143 | if (!entry->length) { | ||
1144 | dst = kmap_atomic(page); | ||
1145 | zswap_fill_page(dst, entry->value); | ||
1146 | kunmap_atomic(dst); | ||
1147 | goto freeentry; | ||
1148 | } | ||
1149 | |||
1092 | /* decompress */ | 1150 | /* decompress */ |
1093 | dlen = PAGE_SIZE; | 1151 | dlen = PAGE_SIZE; |
1094 | src = (u8 *)zpool_map_handle(entry->pool->zpool, entry->handle, | 1152 | src = (u8 *)zpool_map_handle(entry->pool->zpool, entry->handle, |
@@ -1101,6 +1159,7 @@ static int zswap_frontswap_load(unsigned type, pgoff_t offset, | |||
1101 | zpool_unmap_handle(entry->pool->zpool, entry->handle); | 1159 | zpool_unmap_handle(entry->pool->zpool, entry->handle); |
1102 | BUG_ON(ret); | 1160 | BUG_ON(ret); |
1103 | 1161 | ||
1162 | freeentry: | ||
1104 | spin_lock(&tree->lock); | 1163 | spin_lock(&tree->lock); |
1105 | zswap_entry_put(tree, entry); | 1164 | zswap_entry_put(tree, entry); |
1106 | spin_unlock(&tree->lock); | 1165 | spin_unlock(&tree->lock); |
@@ -1209,6 +1268,8 @@ static int __init zswap_debugfs_init(void) | |||
1209 | zswap_debugfs_root, &zswap_pool_total_size); | 1268 | zswap_debugfs_root, &zswap_pool_total_size); |
1210 | debugfs_create_atomic_t("stored_pages", S_IRUGO, | 1269 | debugfs_create_atomic_t("stored_pages", S_IRUGO, |
1211 | zswap_debugfs_root, &zswap_stored_pages); | 1270 | zswap_debugfs_root, &zswap_stored_pages); |
1271 | debugfs_create_atomic_t("same_filled_pages", 0444, | ||
1272 | zswap_debugfs_root, &zswap_same_filled_pages); | ||
1212 | 1273 | ||
1213 | return 0; | 1274 | return 0; |
1214 | } | 1275 | } |