sysvipc: make get_maxid O(1) again
authorDavidlohr Bueso <dave@stgolabs.net>
Fri, 17 Nov 2017 23:31:18 +0000 (15:31 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 18 Nov 2017 00:10:04 +0000 (16:10 -0800)
For a custom microbenchmark on a 3.30GHz Xeon SandyBridge, which calls
IPC_STAT over and over, it was calculated that, on avg the cost of
ipc_get_maxid() for increasing amounts of keys was:

 10 keys: ~900 cycles
 100 keys: ~15000 cycles
 1000 keys: ~150000 cycles
 10000 keys: ~2100000 cycles

This is unsurprising as maxid is currently O(n).

By having the max_id available in O(1) we save all those cycles for each
semctl(_STAT) command, the idr_find can be expensive -- which some real
(customer) workloads actually poll on.

Note that this used to be the case, until commit 7ca7e564e04 ("ipc:
store ipcs into IDRs").  The cost is the extra idr_find when doing
RMIDs, but we simply go backwards, and should not take too many
iterations to find the new value.

[akpm@linux-foundation.org: coding-style fixes]
Link: http://lkml.kernel.org/r/20170831172049.14576-5-dave@stgolabs.net
Signed-off-by: Davidlohr Bueso <dbueso@suse.de>
Cc: Manfred Spraul <manfred@colorfullife.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
include/linux/ipc_namespace.h
ipc/util.c
ipc/util.h

index d7cf3a850853963fd8e0efcc0c504aa61c38d711..b5630c8eb2f3a910b922eeb08153635e88faadbb 100644 (file)
@@ -19,6 +19,7 @@ struct ipc_ids {
        bool tables_initialized;
        struct rw_semaphore rwsem;
        struct idr ipcs_idr;
+       int max_id;
 #ifdef CONFIG_CHECKPOINT_RESTORE
        int next_id;
 #endif
index e09bf76610efb347afc36f91d636250ee0b7b2ea..ff045fec8d835549d35faf54742be3f15f1b356a 100644 (file)
@@ -122,6 +122,7 @@ int ipc_init_ids(struct ipc_ids *ids)
                return err;
        idr_init(&ids->ipcs_idr);
        ids->tables_initialized = true;
+       ids->max_id = -1;
 #ifdef CONFIG_CHECKPOINT_RESTORE
        ids->next_id = -1;
 #endif
@@ -188,36 +189,6 @@ static struct kern_ipc_perm *ipc_findkey(struct ipc_ids *ids, key_t key)
        return NULL;
 }
 
-/**
- * ipc_get_maxid - get the last assigned id
- * @ids: ipc identifier set
- *
- * Called with ipc_ids.rwsem held.
- */
-int ipc_get_maxid(struct ipc_ids *ids)
-{
-       struct kern_ipc_perm *ipc;
-       int max_id = -1;
-       int total, id;
-
-       if (ids->in_use == 0)
-               return -1;
-
-       if (ids->in_use == IPCMNI)
-               return IPCMNI - 1;
-
-       /* Look for the last assigned id */
-       total = 0;
-       for (id = 0; id < IPCMNI && total < ids->in_use; id++) {
-               ipc = idr_find(&ids->ipcs_idr, id);
-               if (ipc != NULL) {
-                       max_id = id;
-                       total++;
-               }
-       }
-       return max_id;
-}
-
 #ifdef CONFIG_CHECKPOINT_RESTORE
 /*
  * Specify desired id for next allocated IPC object.
@@ -313,6 +284,9 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit)
        }
 
        ids->in_use++;
+       if (id > ids->max_id)
+               ids->max_id = id;
+
        new->id = ipc_buildid(id, ids, new);
 
        return id;
@@ -459,6 +433,15 @@ void ipc_rmid(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
        ipc_kht_remove(ids, ipcp);
        ids->in_use--;
        ipcp->deleted = true;
+
+       if (unlikely(lid == ids->max_id)) {
+               do {
+                       lid--;
+                       if (lid == -1)
+                               break;
+               } while (!idr_find(&ids->ipcs_idr, lid));
+               ids->max_id = lid;
+       }
 }
 
 /**
index 0cd6201fe63a4a0cb73e841fdf910c38467ec52d..89b8ec176fc4c41aae00360f08a833532903577a 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <linux/unistd.h>
 #include <linux/err.h>
+#include <linux/ipc_namespace.h>
 
 #define SEQ_MULTIPLIER (IPCMNI)
 
@@ -99,9 +100,6 @@ void __init ipc_init_proc_interface(const char *path, const char *header,
 /* must be called with ids->rwsem acquired for writing */
 int ipc_addid(struct ipc_ids *, struct kern_ipc_perm *, int);
 
-/* must be called with ids->rwsem acquired for reading */
-int ipc_get_maxid(struct ipc_ids *);
-
 /* must be called with both locks acquired. */
 void ipc_rmid(struct ipc_ids *, struct kern_ipc_perm *);
 
@@ -111,6 +109,23 @@ void ipc_set_key_private(struct ipc_ids *, struct kern_ipc_perm *);
 /* must be called with ipcp locked */
 int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flg);
 
+/**
+ * ipc_get_maxid - get the last assigned id
+ * @ids: ipc identifier set
+ *
+ * Called with ipc_ids.rwsem held for reading.
+ */
+static inline int ipc_get_maxid(struct ipc_ids *ids)
+{
+       if (ids->in_use == 0)
+               return -1;
+
+       if (ids->in_use == IPCMNI)
+               return IPCMNI - 1;
+
+       return ids->max_id;
+}
+
 /*
  * For allocation that need to be freed by RCU.
  * Objects are reference counted, they start with reference count 1.