205 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
| 			Client Scheduling in X
 | |
| 			    Keith Packard
 | |
| 			       SuSE
 | |
| 			     10/28/99
 | |
| 
 | |
| History:
 | |
| 
 | |
| Since the original X server was written at Digital in 1987, the OS and DIX
 | |
| layers shared responsibility for scheduling the order to service
 | |
| client requests.  The original design was simplistic; under the maximum
 | |
| first make it work, then make it work well, this was a good idea.  Now 
 | |
| that we have a bit more experience with X applications, it's time to
 | |
| rethink the design.
 | |
| 
 | |
| The basic dispatch loop in DIX looks like:
 | |
| 
 | |
| 	for (;;)
 | |
| 	{
 | |
| 		nready = WaitForSomething (...);
 | |
| 		while (nready--)
 | |
| 		{
 | |
| 			isItTimeToYield = FALSE;
 | |
| 			while (!isItTimeToYield)
 | |
| 			{
 | |
| 				if (!ReadRequestFromClient (...))
 | |
| 					break;
 | |
| 				(execute request);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| WaitForSomething looks like:
 | |
| 
 | |
| 	for (;;)
 | |
| 		if (ANYSET (ClientsWithInput))
 | |
| 			return popcount (ClientsWithInput);
 | |
| 		select (...)
 | |
| 		compute clientsReadable from select result;
 | |
| 		return popcount (clientsReadable)
 | |
| 	}
 | |
| 
 | |
| ReadRequestFromClient looks like:
 | |
| 
 | |
| 	if (!fullRequestQueued)
 | |
| 	{
 | |
| 		read ();
 | |
| 		if (!fullRequestQueued)
 | |
| 		{
 | |
| 			remove from ClientsWithInput;
 | |
| 			timesThisConnection = 0;
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 	if (twoFullRequestsQueued)
 | |
| 		add to ClientsWithInput;
 | |
| 
 | |
| 	if (++timesThisConnection >= 10)
 | |
| 	{
 | |
| 		isItTimeToYield = TRUE;
 | |
| 		timesThisConnection = 0;
 | |
| 	}
 | |
| 	return 1;
 | |
| 
 | |
| Here's what happens in this code:
 | |
| 
 | |
| With a single client executing a stream of requests:
 | |
| 
 | |
| 	A client sends a packet of requests to the server.
 | |
| 
 | |
| 	WaitForSomething wakes up from select and returns that client
 | |
| 	to Dispatch
 | |
| 
 | |
| 	Dispatch calls ReadRequestFromClient which reads a buffer (4K)
 | |
| 	full of requests from the client
 | |
| 
 | |
| 	The server executes requests from this buffer until it emptys,
 | |
| 	in two stages -- 10 requests at a time are executed in the
 | |
| 	inner Dispatch loop, a buffer full of requests are executed
 | |
| 	because WaitForSomething immediately returns if any clients
 | |
| 	have complete requests pending in their input queues.
 | |
| 
 | |
| 	When the buffer finally emptys, the next call to ReadRequest
 | |
| 	FromClient will return zero and Dispatch will go back to
 | |
| 	WaitForSomething; now that the client has no requests pending,
 | |
| 	WaitForSomething will block in select again.  If the client
 | |
| 	is active, this select will immediately return that client
 | |
| 	as ready to read.
 | |
| 
 | |
| With multiple clients sending streams of requests, the sequence
 | |
| of operations is similar, except that ReadRequestFromClient will
 | |
| set isItTimeToYield after each 10 requests executed causing the
 | |
| server to round-robin among the clients with available requests.
 | |
| 
 | |
| It's important to realize here that any complete requests which have been
 | |
| read from clients will be executed before the server will use select again
 | |
| to discover input from other clients.  A single busy client can easily
 | |
| monopolize the X server.
 | |
| 
 | |
| So, the X server doesn't share well with clients which are more interactive
 | |
| in nature.
 | |
| 
 | |
| The X server executes at most a buffer full of requests before again heading
 | |
| into select; ReadRequestFromClient causes the server to yield when the
 | |
| client request buffer doesn't contain a complete request.  When
 | |
| that buffer is executed quickly, the server spends a lot of time
 | |
| in select discovering that the same client again has input ready.  Thus
 | |
| the server also runs busy clients less efficiently than is would be
 | |
| possible.
 | |
| 
 | |
| What to do.
 | |
| 
 | |
| There are several things evident from the above discussion:
 | |
| 
 | |
|  1	The server has a poor metric for deciding how much work it
 | |
| 	should do at one time on behalf of a particular client.
 | |
| 
 | |
|  2	The server doesn't call select often enough to detect less
 | |
|  	aggressive clients in the face of busy clients, especially
 | |
| 	when those clients are executing slow requests.
 | |
| 
 | |
|  3	The server calls select too often when executing fast requests.
 | |
| 
 | |
|  4	Some priority scheme is needed to keep interactive clients
 | |
|  	responding to the user.
 | |
| 
 | |
| And, there are some assumptions about how X applications work:
 | |
| 
 | |
|  1	Each X request is executed relatively quickly; a request-granularity
 | |
|  	is good enough for interactive response almost all of the time.
 | |
| 
 | |
|  2	X applications receiving mouse/keyboard events are likely to
 | |
|  	warrant additional attention from the X server.
 | |
| 
 | |
| Instead of a request-count metric for work, a time-based metric should be
 | |
| used.  The server should select a reasonable time slice for each client
 | |
| and execute requests for the entire timeslice before yielding to
 | |
| another client.
 | |
| 
 | |
| Instead of returning immediately from WaitForSomething if clients have
 | |
| complete requests queued, the server should go through select each
 | |
| time and gather as many ready clients as possible.  This involves
 | |
| polling instead of blocking and adding the ClientsWithInput to
 | |
| clientsReadable after the select returns.
 | |
| 
 | |
| Instead of yielding when the request buffer is empty for a particular
 | |
| client, leave the yielding to the upper level scheduling and allow
 | |
| the server to try and read again from the socket.  If the client
 | |
| is busy, another buffer full of requests will already be waiting
 | |
| to be delivered thus avoiding the call through select and the
 | |
| additional overhead in WaitForSomething.
 | |
| 
 | |
| Finally, the dispatch loop should not simply execute requests from the
 | |
| first available client, instead each client should be prioritized with
 | |
| busy clients penalized and clients receiving user events praised.
 | |
| 
 | |
| How it's done:
 | |
| 
 | |
| Polling the current time of day from the OS is too expensive to
 | |
| be done at each request boundary, so instead an interval timer is
 | |
| set allowing the server to track time changes by counting invocations
 | |
| of the related signal handler.  Instead of using the wall time for
 | |
| this purpose, the process CPU time is used instead.  This serves
 | |
| two purposes -- first, it allows the server to consume no CPU cycles
 | |
| when idle, second it avoids conflicts with SIGALRM usage in other
 | |
| parts of the server code.  It's not without problems though; other
 | |
| CPU intensive processes on the same machine can reduce interactive
 | |
| response time within the X server.  The dispatch loop can now
 | |
| calculate an approximate time value using the number of signals
 | |
| received.  The granularity of the timer sets the scheduling jitter,
 | |
| at 20ms it's only occasionally noticeable.
 | |
| 
 | |
| The changes to WaitForSomething and ReadRequestFromClient are
 | |
| straightforward, adjusting when select is called and avoiding
 | |
| setting isItTimeToYield too often.
 | |
| 
 | |
| The dispatch loop changes are more extensive, now instead of
 | |
| executing requests from all available clients, a single client
 | |
| is chosen after each call to WaitForSomething, requests are
 | |
| executed for that client and WaitForSomething is called again.
 | |
| 
 | |
| Each client is assigned a priority, the dispatch loop chooses the
 | |
| client with the highest priority to execute.  Priorities are
 | |
| updated in three ways:
 | |
| 
 | |
|  1.	Clients which consume their entire slice are penalized
 | |
|  	by having their priority reduced by one until they
 | |
| 	reach some minimum value.
 | |
| 
 | |
|  2.	Clients which have executed no requests for some time
 | |
|  	are praised by having their priority raised until they
 | |
| 	return to normal priority.
 | |
| 
 | |
|  3.	Clients which receive user input are praised by having
 | |
|  	their priority rased until they reach some maximal
 | |
| 	value, above normal priority.
 | |
| 
 | |
| The effect of these changes is to both improve interactive application
 | |
| response and benchmark numbers at the same time.
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| $XFree86: xc/programs/Xserver/hw/xfree86/doc/smartsched,v 1.2 1999/11/19 14:59:16 hohndel Exp $
 |