Blob Blame History Raw
From 973086759974f36987e3c0e9c78bb1e9a41661cc Mon Sep 17 00:00:00 2001
From: Scott Cheloha <cheloha@linux.vnet.ibm.com>
Date: Tue, 17 Dec 2019 13:32:38 -0600
Subject: [PATCH] drivers/base/memory.c: cache blocks in radix tree to
 accelerate lookup

References: bsc#1159955 ltc#182993
Patch-mainline: submitted https://lore.kernel.org/lkml/20191217193238.3098-1-cheloha@linux.vnet.ibm.com/

mhocko@suse.com:
Keep find_memory_block_hinted because there are still users. The
upstream has removed this by dd625285910d ("drivers/base/memory.c:
get rid of find_memory_block_hinted()") which would require more patches
to be backported so go an easier path and simply emulate the
functionality by ignoring the hint. The radix tree O(log(N)) should work
reasonably well.

Searching for a particular memory block by id is slow because each block
device is kept in an unsorted linked list on the subsystem bus.

Lookup is much faster if we cache the blocks in a radix tree.  Memory
subsystem initialization and hotplug/hotunplug is at least a little faster
for any machine with more than ~100 blocks, and the speedup grows with
the block count.

Signed-off-by: Scott Cheloha <cheloha@linux.vnet.ibm.com>
Acked-by: David Hildenbrand <david@redhat.com>
Acked-by: Michal Suchanek <msuchanek@suse.de>
Signed-off-by: Michal Hocko <mhocko@suse.com>
---
 drivers/base/memory.c |   53 +++++++++++++++++++++++++-------------------------
 1 file changed, 27 insertions(+), 26 deletions(-)

--- a/drivers/base/memory.c
+++ b/drivers/base/memory.c
@@ -19,6 +19,7 @@
 #include <linux/memory_hotplug.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
+#include <linux/radix-tree.h>
 #include <linux/stat.h>
 #include <linux/slab.h>
 
@@ -48,6 +49,13 @@ static struct bus_type memory_subsys = {
 	.offline = memory_subsys_offline,
 };
 
+/*
+ * Memory blocks are cached in a local radix tree to avoid
+ * a costly linear search for the corresponding device on
+ * the subsystem bus.
+ */
+static RADIX_TREE(memory_blocks, GFP_KERNEL);
+
 static BLOCKING_NOTIFIER_HEAD(memory_chain);
 
 int register_memory_notifier(struct notifier_block *nb)
@@ -576,36 +584,21 @@ int __weak arch_get_memory_phys_device(u
 	return 0;
 }
 
-/*
- * A reference for the returned object is held and the reference for the
- * hinted object is released.
- */
-struct memory_block *find_memory_block_hinted(struct mem_section *section,
-					      struct memory_block *hint)
+struct memory_block *find_memory_block(struct mem_section *section)
 {
 	int block_id = base_memory_block_id(__section_nr(section));
-	struct device *hintdev = hint ? &hint->dev : NULL;
-	struct device *dev;
+	struct memory_block *mem;
 
-	dev = subsys_find_device_by_id(&memory_subsys, block_id, hintdev);
-	if (hint)
-		put_device(&hint->dev);
-	if (!dev)
-		return NULL;
-	return to_memory_block(dev);
+	mem = radix_tree_lookup(&memory_blocks, block_id);
+	if (mem)
+		get_device(&mem->dev);
+	return mem;
 }
 
-/*
- * For now, we have a linear search to go find the appropriate
- * memory_block corresponding to a particular phys_index. If
- * this gets to be a real problem, we can always use a radix
- * tree or something here.
- *
- * This could be made generic for all device subsystems.
- */
-struct memory_block *find_memory_block(struct mem_section *section)
+struct memory_block *find_memory_block_hinted(struct mem_section *section,
+		struct memory_block *hint)
 {
-	return find_memory_block_hinted(section, NULL);
+	return find_memory_block(section);
 }
 
 static struct attribute *memory_memblk_attrs[] = {
@@ -643,9 +636,15 @@ int register_memory(struct memory_block
 	memory->dev.offline = memory->state == MEM_OFFLINE;
 
 	ret = device_register(&memory->dev);
-	if (ret)
+	if (ret) {
 		put_device(&memory->dev);
-
+		return ret;
+	}
+	ret = radix_tree_insert(&memory_blocks, memory->dev.id, memory);
+	if (ret) {
+		put_device(&memory->dev);
+		device_unregister(&memory->dev);
+	}
 	return ret;
 }
 
@@ -734,6 +733,8 @@ unregister_memory(struct memory_block *m
 {
 	BUG_ON(memory->dev.bus != &memory_subsys);
 
+	WARN_ON(radix_tree_delete(&memory_blocks, memory->dev.id) == NULL);
+
 	/* drop the ref. we got in remove_memory_block() */
 	put_device(&memory->dev);
 	device_unregister(&memory->dev);