kernel - VM rework part 20 - Fix vmmeter_neg_slop_cnt
authorMatthew Dillon <dillon@apollo.backplane.com>
Wed, 22 May 2019 07:16:17 +0000 (00:16 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Wed, 22 May 2019 07:22:31 +0000 (00:22 -0700)
* Fix some serious issues with the vmmeter_neg_slop_cnt calculation.
  The main problem is that this calculation was then causing
  vmstats.v_free_min to be recalculated to a much higher value
  than it should beeen calculated to, resulting in systems starting
  to page far earlier than they should.

  For example, the 128G TR started paging tmpfs data with 25GB of
  free memory, which was not intended.  The correct target for that
  amount of memory is more around 3GB.

* Remove vmmeter_neg_slop_cnt entirely and refactor the synchronization
  code to be smarter.  It will now synchronize vmstats fields whos
  adjustments exceed -1024, but only if paging would actually be
  needed in the worst-case scenario.

* This algorithm needs low-memory testing and might require more
  tuning.

sys/sys/vmmeter.h
sys/vm/vm_meter.c
sys/vm/vm_page.c
sys/vm/vm_page.h
sys/vm/vm_page2.h
sys/vm/vm_pageout.c

index dfa9fa2..536ef74 100644 (file)
@@ -143,8 +143,6 @@ struct vmstats {
        long v_unused_variable[9];
 };
 
-#define VMMETER_SLOP_COUNT     128
-
 #ifdef _KERNEL
 
 /* note: vmmeter 'cnt' structure is now per-cpu */
index 1fe96ef..bd0c67c 100644 (file)
@@ -53,7 +53,7 @@
 
 /*
  * WARNING: vmstats represents the final say, but individual cpu's may
- *         accumulative adjustments in gd->gd_vmstats_adj.  These are
+ *         accumualte adjustments in gd->gd_vmstats_adj.  These are
  *         synchronized to the global vmstats in hardclock.
  *
  *         In addition, most individual cpus check vmstats using a local
index c4d9b26..b5f7a90 100644 (file)
@@ -127,7 +127,6 @@ static void vm_numa_add_topology_mem(cpu_node_t *cpup, int physid, long bytes);
  * Array of tailq lists
  */
 struct vpgqueues vm_page_queues[PQ_COUNT];
-__read_mostly long vmmeter_neg_slop_cnt = -VMMETER_SLOP_COUNT;
 
 static volatile int vm_pages_waiting;
 static struct alist vm_contig_alist;
@@ -991,7 +990,8 @@ _vm_page_rem_queue_spinlocked(vm_page_t m)
        struct vpgqueues *pq;
        u_short queue;
        u_short oqueue;
-       long *cnt;
+       long *cnt_adj;
+       long *cnt_gd;
 
        queue = m->queue;
        if (queue != PQ_NONE) {
@@ -999,31 +999,37 @@ _vm_page_rem_queue_spinlocked(vm_page_t m)
                TAILQ_REMOVE(&pq->pl, m, pageq);
 
                /*
-                * Adjust our pcpu stats.  In order for the nominal low-memory
-                * algorithms to work properly we don't let any pcpu stat get
-                * too negative before we force it to be rolled-up into the
-                * global stats.  Otherwise our pageout and vm_wait tests
-                * will fail badly.
+                * Primarily adjust our pcpu stats for rollup, which is
+                * (mycpu->gd_vmstats_adj + offset).  This is normally
+                * synchronized on every hardclock().
                 *
-                * The idea here is to reduce unnecessary SMP cache
-                * mastership changes in the global vmstats, which can be
-                * particularly bad in multi-socket systems.
+                * However, in order for the nominal low-memory algorithms
+                * to work properly if the unsynchronized adjustment gets
+                * too negative and might trigger the pageout daemon, we
+                * immediately synchronize with the global structure.
                 *
-                * NOTE: The double *cnt test tries to avoid a global memory
-                *       read.  vmmeter_neg_slop_cnt is more generous than
-                *       the baseline define, we want to try to avoid atomic
-                *       ops on the global 'vmstats' structure as much as
-                *       possible.
+                * The idea here is to reduce unnecessary SMP cache mastership
+                * changes in the global vmstats, which can be particularly
+                * bad in multi-socket systems.
+                *
+                * WARNING! In systems with low amounts of memory the
+                *          vm_paging_needed(-1024 * ncpus) test could
+                *          wind up testing a value above the paging target,
+                *          meaning it would almost always return TRUE.  In
+                *          that situation we synchronize every time the
+                *          cumulative adjustment falls below -1024.
                 */
-               cnt = (long *)((char *)&mycpu->gd_vmstats_adj + pq->cnt_offset);
-               atomic_add_long(cnt, -1);
-               if (*cnt < -VMMETER_SLOP_COUNT && *cnt < vmmeter_neg_slop_cnt) {
-                       u_long copy = atomic_swap_long(cnt, 0);
-                       cnt = (long *)((char *)&vmstats + pq->cnt_offset);
-                       atomic_add_long(cnt, copy);
-                       cnt = (long *)((char *)&mycpu->gd_vmstats +
-                                     pq->cnt_offset);
-                       atomic_add_long(cnt, copy);
+               cnt_adj = (long *)((char *)&mycpu->gd_vmstats_adj +
+                                  pq->cnt_offset);
+               cnt_gd = (long *)((char *)&mycpu->gd_vmstats +
+                                  pq->cnt_offset);
+               atomic_add_long(cnt_adj, -1);
+               atomic_add_long(cnt_gd, -1);
+
+               if (*cnt_adj < -1024 && vm_paging_needed(-1024 * ncpus)) {
+                       u_long copy = atomic_swap_long(cnt_adj, 0);
+                       cnt_adj = (long *)((char *)&vmstats + pq->cnt_offset);
+                       atomic_add_long(cnt_adj, copy);
                }
                pq->lcnt--;
                m->queue = PQ_NONE;
@@ -1048,7 +1054,8 @@ static __inline void
 _vm_page_add_queue_spinlocked(vm_page_t m, u_short queue, int athead)
 {
        struct vpgqueues *pq;
-       u_long *cnt;
+       u_long *cnt_adj;
+       u_long *cnt_gd;
 
        KKASSERT(m->queue == PQ_NONE &&
                 (m->flags & (PG_FICTITIOUS | PG_UNQUEUED)) == 0);
@@ -1063,8 +1070,12 @@ _vm_page_add_queue_spinlocked(vm_page_t m, u_short queue, int athead)
                 * to incorporate the count it will call vmstats_rollup()
                 * to roll it all up into the global vmstats strufture.
                 */
-               cnt = (long *)((char *)&mycpu->gd_vmstats_adj + pq->cnt_offset);
-               atomic_add_long(cnt, 1);
+               cnt_adj = (long *)((char *)&mycpu->gd_vmstats_adj +
+                                  pq->cnt_offset);
+               cnt_gd = (long *)((char *)&mycpu->gd_vmstats +
+                                  pq->cnt_offset);
+               atomic_add_long(cnt_adj, 1);
+               atomic_add_long(cnt_gd, 1);
 
                /*
                 * PQ_FREE is always handled LIFO style to try to provide
index 3153f19..38507fe 100644 (file)
@@ -282,7 +282,6 @@ struct vpgqueues {
 } __aligned(64);
 
 extern struct vpgqueues vm_page_queues[PQ_COUNT];
-extern long vmmeter_neg_slop_cnt;
 
 /*
  * The m->flags field is generally categorized as follows.  Unless otherwise
index bf99237..570482e 100644 (file)
@@ -153,15 +153,15 @@ vm_paging_target(void)
  */
 static __inline 
 int
-vm_paging_needed(void)
+vm_paging_needed(int adj)
 {
     globaldata_t gd = mycpu;
 
     if (gd->gd_vmstats.v_free_min + gd->gd_vmstats.v_cache_min >
-       gd->gd_vmstats.v_free_count + gd->gd_vmstats.v_cache_count) {
+       gd->gd_vmstats.v_free_count + gd->gd_vmstats.v_cache_count + adj) {
                return 1;
     }
-    if (gd->gd_vmstats.v_free_min > gd->gd_vmstats.v_free_count)
+    if (gd->gd_vmstats.v_free_min > gd->gd_vmstats.v_free_count + adj)
                return 1;
     return 0;
 }
index d501963..74b445d 100644 (file)
@@ -1964,34 +1964,11 @@ vm_pageout_free_page_calc(vm_size_t count)
         * v_free_reserved      system allocations
         * v_pageout_free_min   allocations by pageout daemon
         * v_interrupt_free_min low level allocations (e.g swap structures)
-        */
-       if (vmstats.v_page_count > 1024)
-               vmstats.v_free_min = 64 + (vmstats.v_page_count - 1024) / 200;
-       else
-               vmstats.v_free_min = 64;
-
-       /*
-        * vmmeter_neg_slop_cnt controls when the per-cpu page stats are
-        * synchronized with the global stats (incuring serious cache
-        * contention).
-        */
-       vmmeter_neg_slop_cnt = -vmstats.v_page_count / ncpus / 128;
-       if (vmmeter_neg_slop_cnt > -VMMETER_SLOP_COUNT)
-               vmmeter_neg_slop_cnt = -VMMETER_SLOP_COUNT;
-
-       /*
-        * Make sure the vmmeter slop can't blow out our global minimums.
         *
-        * However, to accomodate weird configurations (vkernels with many
-        * cpus and little memory, or artifically reduced hw.physmem), do
-        * not allow v_free_min to exceed 1/20 of ram or the pageout demon
-        * might go out of control.
+        * v_free_min is used to generate several other baselines, and they
+        * can get pretty silly on systems with a lot of memory.
         */
-       if (vmstats.v_free_min < -vmmeter_neg_slop_cnt * ncpus * 10)
-               vmstats.v_free_min = -vmmeter_neg_slop_cnt * ncpus * 10;
-       if (vmstats.v_free_min > vmstats.v_page_count / 20)
-               vmstats.v_free_min = vmstats.v_page_count / 20;
-
+       vmstats.v_free_min = 64 + vmstats.v_page_count / 200;
        vmstats.v_free_reserved = vmstats.v_free_min * 4 / 8 + 7;
        vmstats.v_free_severe = vmstats.v_free_min * 4 / 8 + 0;
        vmstats.v_pageout_free_min = vmstats.v_free_min * 2 / 8 + 7;
@@ -2045,12 +2022,8 @@ vm_pageout_thread(void)
         * be big enough to handle memory needs while the pageout daemon
         * is signalled and run to free more pages.
         */
-       if (vmstats.v_free_count > 6144)
-               vmstats.v_free_target = 4 * vmstats.v_free_min +
-                                       vmstats.v_free_reserved;
-       else
-               vmstats.v_free_target = 2 * vmstats.v_free_min +
-                                       vmstats.v_free_reserved;
+       vmstats.v_free_target = 4 * vmstats.v_free_min +
+                               vmstats.v_free_reserved;
 
        /*
         * NOTE: With the new buffer cache b_act_count we want the default
@@ -2180,7 +2153,7 @@ skip_setup:
                                               0, "psleep",
                                               vm_pageout_stats_interval * hz);
                                if (error &&
-                                   vm_paging_needed() == 0 &&
+                                   vm_paging_needed(0) == 0 &&
                                    vm_pages_needed == 0) {
                                        for (q = 0; q < PQ_L2_SIZE; ++q)
                                                vm_pageout_page_stats(q);
@@ -2436,7 +2409,7 @@ SYSINIT(emergpager, SI_SUB_KTHREAD_PAGE, SI_ORDER_ANY, kproc_start, &pg2_kp);
 void
 pagedaemon_wakeup(void)
 {
-       if (vm_paging_needed() && curthread != pagethread) {
+       if (vm_paging_needed(0) && curthread != pagethread) {
                if (vm_pages_needed == 0) {
                        vm_pages_needed = 1;    /* SMP race ok */
                        wakeup(&vm_pages_needed);