aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHugh Dickins <hughd@google.com>2011-08-03 19:21:27 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2011-08-03 20:25:24 -0400
commite504f3fdd63d486d45b18009e5a65f2e329acb0a (patch)
tree2d02a5c29a922fae626a69cd0fc92cae37d7918e
parent31475dd611209413bace21651a400afb91d0bd9d (diff)
tmpfs radix_tree: locate_item to speed up swapoff
We have already acknowledged that swapoff of a tmpfs file is slower than it was before conversion to the generic radix_tree: a little slower there will be acceptable, if the hotter paths are faster. But it was a shock to find swapoff of a 500MB file 20 times slower on my laptop, taking 10 minutes; and at that rate it significantly slows down my testing. Now, most of that turned out to be overhead from PROVE_LOCKING and PROVE_RCU: without those it was only 4 times slower than before; and more realistic tests on other machines don't fare as badly. I've tried a number of things to improve it, including tagging the swap entries, then doing lookup by tag: I'd expected that to halve the time, but in practice it's erratic, and often counter-productive. The only change I've so far found to make a consistent improvement, is to short-circuit the way we go back and forth, gang lookup packing entries into the array supplied, then shmem scanning that array for the target entry. Scanning in place doubles the speed, so it's now only twice as slow as before (or three times slower when the PROVEs are on). So, add radix_tree_locate_item() as an expedient, once-off, single-caller hack to do the lookup directly in place. #ifdef it on CONFIG_SHMEM and CONFIG_SWAP, as much to document its limited applicability as save space in other configurations. And, sadly, #include sched.h for cond_resched(). Signed-off-by: Hugh Dickins <hughd@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--include/linux/radix-tree.h1
-rw-r--r--lib/radix-tree.c92
-rw-r--r--mm/shmem.c38
3 files changed, 94 insertions, 37 deletions
diff --git a/include/linux/radix-tree.h b/include/linux/radix-tree.h
index b7edf8251455..9d4539c52e53 100644
--- a/include/linux/radix-tree.h
+++ b/include/linux/radix-tree.h
@@ -252,6 +252,7 @@ unsigned long radix_tree_range_tag_if_tagged(struct radix_tree_root *root,
252 unsigned long nr_to_tag, 252 unsigned long nr_to_tag,
253 unsigned int fromtag, unsigned int totag); 253 unsigned int fromtag, unsigned int totag);
254int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag); 254int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);
255unsigned long radix_tree_locate_item(struct radix_tree_root *root, void *item);
255 256
256static inline void radix_tree_preload_end(void) 257static inline void radix_tree_preload_end(void)
257{ 258{
diff --git a/lib/radix-tree.c b/lib/radix-tree.c
index 348eaefbed74..a2f9da59c197 100644
--- a/lib/radix-tree.c
+++ b/lib/radix-tree.c
@@ -1203,6 +1203,98 @@ radix_tree_gang_lookup_tag_slot(struct radix_tree_root *root, void ***results,
1203} 1203}
1204EXPORT_SYMBOL(radix_tree_gang_lookup_tag_slot); 1204EXPORT_SYMBOL(radix_tree_gang_lookup_tag_slot);
1205 1205
1206#if defined(CONFIG_SHMEM) && defined(CONFIG_SWAP)
1207#include <linux/sched.h> /* for cond_resched() */
1208
1209/*
1210 * This linear search is at present only useful to shmem_unuse_inode().
1211 */
1212static unsigned long __locate(struct radix_tree_node *slot, void *item,
1213 unsigned long index, unsigned long *found_index)
1214{
1215 unsigned int shift, height;
1216 unsigned long i;
1217
1218 height = slot->height;
1219 shift = (height-1) * RADIX_TREE_MAP_SHIFT;
1220
1221 for ( ; height > 1; height--) {
1222 i = (index >> shift) & RADIX_TREE_MAP_MASK;
1223 for (;;) {
1224 if (slot->slots[i] != NULL)
1225 break;
1226 index &= ~((1UL << shift) - 1);
1227 index += 1UL << shift;
1228 if (index == 0)
1229 goto out; /* 32-bit wraparound */
1230 i++;
1231 if (i == RADIX_TREE_MAP_SIZE)
1232 goto out;
1233 }
1234
1235 shift -= RADIX_TREE_MAP_SHIFT;
1236 slot = rcu_dereference_raw(slot->slots[i]);
1237 if (slot == NULL)
1238 goto out;
1239 }
1240
1241 /* Bottom level: check items */
1242 for (i = 0; i < RADIX_TREE_MAP_SIZE; i++) {
1243 if (slot->slots[i] == item) {
1244 *found_index = index + i;
1245 index = 0;
1246 goto out;
1247 }
1248 }
1249 index += RADIX_TREE_MAP_SIZE;
1250out:
1251 return index;
1252}
1253
1254/**
1255 * radix_tree_locate_item - search through radix tree for item
1256 * @root: radix tree root
1257 * @item: item to be found
1258 *
1259 * Returns index where item was found, or -1 if not found.
1260 * Caller must hold no lock (since this time-consuming function needs
1261 * to be preemptible), and must check afterwards if item is still there.
1262 */
1263unsigned long radix_tree_locate_item(struct radix_tree_root *root, void *item)
1264{
1265 struct radix_tree_node *node;
1266 unsigned long max_index;
1267 unsigned long cur_index = 0;
1268 unsigned long found_index = -1;
1269
1270 do {
1271 rcu_read_lock();
1272 node = rcu_dereference_raw(root->rnode);
1273 if (!radix_tree_is_indirect_ptr(node)) {
1274 rcu_read_unlock();
1275 if (node == item)
1276 found_index = 0;
1277 break;
1278 }
1279
1280 node = indirect_to_ptr(node);
1281 max_index = radix_tree_maxindex(node->height);
1282 if (cur_index > max_index)
1283 break;
1284
1285 cur_index = __locate(node, item, cur_index, &found_index);
1286 rcu_read_unlock();
1287 cond_resched();
1288 } while (cur_index != 0 && cur_index <= max_index);
1289
1290 return found_index;
1291}
1292#else
1293unsigned long radix_tree_locate_item(struct radix_tree_root *root, void *item)
1294{
1295 return -1;
1296}
1297#endif /* CONFIG_SHMEM && CONFIG_SWAP */
1206 1298
1207/** 1299/**
1208 * radix_tree_shrink - shrink height of a radix tree to minimal 1300 * radix_tree_shrink - shrink height of a radix tree to minimal
diff --git a/mm/shmem.c b/mm/shmem.c
index 3a5be0feb6af..1c702f6f1241 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -357,42 +357,6 @@ export:
357} 357}
358 358
359/* 359/*
360 * Lockless lookup of swap entry in radix tree, avoiding refcount on pages.
361 */
362static pgoff_t shmem_find_swap(struct address_space *mapping, void *radswap)
363{
364 void **slots[PAGEVEC_SIZE];
365 pgoff_t indices[PAGEVEC_SIZE];
366 unsigned int nr_found;
367
368restart:
369 nr_found = 1;
370 indices[0] = -1;
371 while (nr_found) {
372 pgoff_t index = indices[nr_found - 1] + 1;
373 unsigned int i;
374
375 rcu_read_lock();
376 nr_found = radix_tree_gang_lookup_slot(&mapping->page_tree,
377 slots, indices, index, PAGEVEC_SIZE);
378 for (i = 0; i < nr_found; i++) {
379 void *item = radix_tree_deref_slot(slots[i]);
380 if (radix_tree_deref_retry(item)) {
381 rcu_read_unlock();
382 goto restart;
383 }
384 if (item == radswap) {
385 rcu_read_unlock();
386 return indices[i];
387 }
388 }
389 rcu_read_unlock();
390 cond_resched();
391 }
392 return -1;
393}
394
395/*
396 * Remove swap entry from radix tree, free the swap and its page cache. 360 * Remove swap entry from radix tree, free the swap and its page cache.
397 */ 361 */
398static int shmem_free_swap(struct address_space *mapping, 362static int shmem_free_swap(struct address_space *mapping,
@@ -612,7 +576,7 @@ static int shmem_unuse_inode(struct shmem_inode_info *info,
612 int error; 576 int error;
613 577
614 radswap = swp_to_radix_entry(swap); 578 radswap = swp_to_radix_entry(swap);
615 index = shmem_find_swap(mapping, radswap); 579 index = radix_tree_locate_item(&mapping->page_tree, radswap);
616 if (index == -1) 580 if (index == -1)
617 return 0; 581 return 0;
618 582