Subject: halving context switch time
To: None <port-i386@netbsd.org>
From: David Laight <david@l8s.co.uk>
List: tech-perform
Date: 08/15/2002 12:22:47
By far the largest contribution to the context switch time
for netbsd i386 comes from the calls to microtime().

Now if the kernel doesn't actually enter the idle loop, there
is no reason not to use the same time for the end of one
process and the start of another.  This cuts the context
switch time by about 5us (6us with my faster microtime).

Ideally the return value of cpu_switch() [1] would need to be
changed to indicate whether it went into the idle loop or not.
Hovever I added a global 64-bit counter to the idle loop.
The following patch probably works (it isn't quite the one
I'm running):

kern/kern_synch.c

@@ -775,8 +786,10 @@
 {
        struct schedstate_percpu *spc;
        struct rlimit *rlim;
        long s, u;
        struct timeval tv;
+       static u_quad_t sv_idle_count;  /* XXX MP - per cpu */
+       extern u_quad_t idle_count;
 #if defined(MULTIPROCESSOR)
        int hold_count;
 #endif
@@ -810,17 +823,11 @@
         * process was running, and add that to its total so far.
         */
        microtime(&tv);
        u = p->p_rtime.tv_usec + (tv.tv_usec - spc->spc_runtime.tv_usec);
        s = p->p_rtime.tv_sec + (tv.tv_sec - spc->spc_runtime.tv_sec);
        if (u < 0) {
                u += 1000000;
                s--;
        } else if (u >= 1000000) {
                u -= 1000000;
                s++;
        }
        p->p_rtime.tv_usec = u;
        p->p_rtime.tv_sec = s;
+       /* save time in case we reschedule something immediately */
+       spc->spc_runtime = tv;
 
        /*
         * Check if the process exceeds its cpu resource allocation.
@@ -870,6 +879,7 @@
         * run again, we'll return back here.
         */
        uvmexp.swtch++;
+       sv_idle_count = idle_count;
        cpu_switch(p);
 
        /*
@@ -893,7 +903,9 @@
         */
        KDASSERT(p->p_cpu != NULL);
        KDASSERT(p->p_cpu == curcpu());
-       microtime(&p->p_cpu->ci_schedstate.spc_runtime);
+       /* Microtime is SLOW, don't ask again if we didn't actually idle. */
+       if (sv_idle_count != idle_count)
+               microtime(&p->p_cpu->ci_schedstate.spc_runtime);
 
 #if defined(MULTIPROCESSOR)
        /*

arch/i386/i386/locore.s

@@ -215,6 +215,7 @@
        .globl  _C_LABEL(cpu_brand_id)
        .globl  _C_LABEL(esym),_C_LABEL(boothowto)
        .globl  _C_LABEL(bootinfo),_C_LABEL(atdevbase)
+       .globl  _C_LABEL(idle_count)
 #ifdef COMPAT_OLDBOOT
        .globl  _C_LABEL(bootdev)
 #endif
@@ -236,6 +237,7 @@
 _C_LABEL(cpu_brand_id):        .long   0       # brand ID from 'cpuid' instruction
 _C_LABEL(esym):                .long   0       # ptr to end of syms
 _C_LABEL(atdevbase):   .long   0       # location of start of iomem in virtual
+_C_LABEL(idle_count):  .long   0,0     # laps of idle loop
 _C_LABEL(proc0paddr):  .long   0
 _C_LABEL(PTDpaddr):    .long   0       # paddr of PTD, for libkvm
 #ifndef REALBASEMEM
@@ -1755,6 +1757,9 @@
        call    _C_LABEL(sched_unlock_idle)
 #endif
        sti
+
+       addw    $1,_C_LABEL(idle_count)
+       adcw    $0,_C_LABEL(idle_count)+4
 
        /* Try to zero some pages. */
        movl    _C_LABEL(uvm)+UVM_PAGE_IDLE_ZERO,%ecx


	David

[1] a badly named function since it changes everything except the cpu.

-- 
David Laight: david@l8s.co.uk