Skip to content

Commit e504f3f

Browse files
Hugh Dickinstorvalds
Hugh Dickins
authored andcommitted
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 <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 31475dd commit e504f3f

File tree

3 files changed

+94
-37
lines changed

3 files changed

+94
-37
lines changed

include/linux/radix-tree.h

+1
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ unsigned long radix_tree_range_tag_if_tagged(struct radix_tree_root *root,
252252
unsigned long nr_to_tag,
253253
unsigned int fromtag, unsigned int totag);
254254
int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);
255+
unsigned long radix_tree_locate_item(struct radix_tree_root *root, void *item);
255256

256257
static inline void radix_tree_preload_end(void)
257258
{

lib/radix-tree.c

+92
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,98 @@ radix_tree_gang_lookup_tag_slot(struct radix_tree_root *root, void ***results,
12031203
}
12041204
EXPORT_SYMBOL(radix_tree_gang_lookup_tag_slot);
12051205

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+
*/
1212+
static 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;
1250+
out:
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+
*/
1263+
unsigned 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
1293+
unsigned long radix_tree_locate_item(struct radix_tree_root *root, void *item)
1294+
{
1295+
return -1;
1296+
}
1297+
#endif /* CONFIG_SHMEM && CONFIG_SWAP */
12061298

12071299
/**
12081300
* radix_tree_shrink - shrink height of a radix tree to minimal

mm/shmem.c

+1-37
Original file line numberDiff line numberDiff line change
@@ -356,42 +356,6 @@ static unsigned shmem_find_get_pages_and_swap(struct address_space *mapping,
356356
return ret;
357357
}
358358

359-
/*
360-
* Lockless lookup of swap entry in radix tree, avoiding refcount on pages.
361-
*/
362-
static 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-
368-
restart:
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-
395359
/*
396360
* Remove swap entry from radix tree, free the swap and its page cache.
397361
*/
@@ -612,7 +576,7 @@ static int shmem_unuse_inode(struct shmem_inode_info *info,
612576
int error;
613577

614578
radswap = swp_to_radix_entry(swap);
615-
index = shmem_find_swap(mapping, radswap);
579+
index = radix_tree_locate_item(&mapping->page_tree, radswap);
616580
if (index == -1)
617581
return 0;
618582

0 commit comments

Comments
 (0)