[PATCH] Cygwin: proc: Cache system process information

Mark Geisert mark@maxrnd.com
Sat Sep 5 08:00:36 GMT 2026


Hi Takashi,

On 9/3/2026 8:58 PM, Takashi Yano wrote:
> If a process reads /proc/<pid>/stat for multiple PIDs, current code
> every time calls NtQuerySystemInformation(SystemProcessInformation).
> In addition, the buffer size is estimated by calling that function
> with increasing buffer size. This takes 15 - 20 msec. This is looks
> short time, however it is not negligible in some situation. If the
> number of the process is 100, reading /proc/<pid>/stat takes 1.5 -

   ^^^^^^^^^^^^^^^^^^^^^ number of processes

> 2.0 sec. Due to this behaviour, process pane of the btop is very
> heavy.

(I've noticed that 'top' seems to use somewhat excessive cpu for what 
it's doing; maybe this area of Cygwin has something to do with it.)

> With this patch, system process information is cached per process
> for a short time (20 msec) to improve efficiency. Also, the buffer
> size needed by NtQuerySystemInformation() is stored in static area,
> so that the number of function call is reduced (~1/7). As a result,
> the process pane of btop becomes significantly light (takes only
> ~15 msec for 100 processes).

I think you're on the right track with this fix.  I would suggest 
keeping the address of the malloc'd buffer in static storage too.  That 
way the buffer can be extended as needed (but not shrunk), over many 
calls to the function, and doesn't have to be freed.  What do you think?

> Signef-off-by: Takashi Yano <takashi.yano@nift.ne.jp>
   ^^^^^^ Signed

   Also needs a Fixed: line.

> Reviewed-by:
> ---
>   winsup/cygwin/fhandler/process.cc | 27 +++++++++++++++++++++++----
>   1 file changed, 23 insertions(+), 4 deletions(-)
> 
> diff --git a/winsup/cygwin/fhandler/process.cc b/winsup/cygwin/fhandler/process.cc
> index 537a4b370..97f3e0a2f 100644
> --- a/winsup/cygwin/fhandler/process.cc
> +++ b/winsup/cygwin/fhandler/process.cc
> @@ -1524,14 +1524,30 @@ get_process_state (DWORD dwProcessId, DWORD *num_threads)
>   {
>     /* This isn't really heavy magic - just go through the processes' threads
>        one by one and return a value accordingly.  Errors are silently ignored. */
> +  const ULONGLONG cache_time = 20;
> +  static NO_COPY muto cache_guard;
>     NTSTATUS status;
> -  PSYSTEM_PROCESS_INFORMATION p, sp;
> -  ULONG n = 0x4000;
> +  static PSYSTEM_PROCESS_INFORMATION p = NULL;
> +  static ULONGLONG t0 = 0;
> +  PSYSTEM_PROCESS_INFORMATION sp;
> +  static ULONG last_n = 0x4000;
> +  ULONG n;
>     int state =' ';
>   
> +  cache_guard.init ("get_process_state")->acquire ();
> +  n = last_n;
> +
> +  if (GetTickCount64 () - t0 < cache_time && p)
> +    goto reuse_query;
> +  else if (p)
> +    {
> +      free (p);
> +      p = NULL;
> +    }
> +
>     p = (PSYSTEM_PROCESS_INFORMATION) malloc (n);
>     if (!p)
> -    return state;
> +    goto out;
>     while (true)
>       {
>         status = NtQuerySystemInformation (SystemProcessInformation,
> @@ -1550,6 +1566,9 @@ get_process_state (DWORD dwProcessId, DWORD *num_threads)
>   		    status, RtlNtStatusToDosError (status));
>         goto out;
>       }
> +  last_n = n;
> +  t0 = GetTickCount64 ();
> +reuse_query:
>     state = 'Z';
>     sp = p;
>     for (;;)
> @@ -1579,7 +1598,7 @@ get_process_state (DWORD dwProcessId, DWORD *num_threads)
>         sp = (PSYSTEM_PROCESS_INFORMATION) ((char *) sp + sp->NextEntryOffset);
>       }
>   out:
> -  free (p);
> +  cache_guard.release ();
>     return state;
>   }
>   

Thanks for taking this on!

..mark



More information about the Cygwin-patches mailing list