mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Merge pull request #104806 from MikeSpreitzer/set-g-to-3ms
Change execution duration guess from 1 minute to 3 milliseconds
This commit is contained in:
commit
559808670a
@ -45,6 +45,9 @@ type fifo interface {
|
|||||||
// Dequeue pulls out the oldest request from the list.
|
// Dequeue pulls out the oldest request from the list.
|
||||||
Dequeue() (*request, bool)
|
Dequeue() (*request, bool)
|
||||||
|
|
||||||
|
// Peek returns the oldest request without removing it.
|
||||||
|
Peek() (*request, bool)
|
||||||
|
|
||||||
// Length returns the number of requests in the list.
|
// Length returns the number of requests in the list.
|
||||||
Length() int
|
Length() int
|
||||||
|
|
||||||
@ -97,18 +100,28 @@ func (l *requestFIFO) Enqueue(req *request) removeFromFIFOFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *requestFIFO) Dequeue() (*request, bool) {
|
func (l *requestFIFO) Dequeue() (*request, bool) {
|
||||||
|
return l.getFirst(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *requestFIFO) Peek() (*request, bool) {
|
||||||
|
return l.getFirst(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *requestFIFO) getFirst(remove bool) (*request, bool) {
|
||||||
e := l.Front()
|
e := l.Front()
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
defer func() {
|
defer func() {
|
||||||
l.Remove(e)
|
l.Remove(e)
|
||||||
e.Value = nil
|
e.Value = nil
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
request, ok := e.Value.(*request)
|
request, ok := e.Value.(*request)
|
||||||
if ok {
|
if remove && ok {
|
||||||
l.seatsSum -= request.Seats()
|
l.seatsSum -= request.Seats()
|
||||||
}
|
}
|
||||||
return request, ok
|
return request, ok
|
||||||
|
@ -77,7 +77,7 @@ type queueSetCompleter struct {
|
|||||||
// locking.
|
// locking.
|
||||||
type queueSet struct {
|
type queueSet struct {
|
||||||
clock eventclock.Interface
|
clock eventclock.Interface
|
||||||
estimatedServiceTime float64
|
estimatedServiceSeconds float64
|
||||||
obsPair metrics.TimedObserverPair
|
obsPair metrics.TimedObserverPair
|
||||||
|
|
||||||
promiseFactory promiseFactory
|
promiseFactory promiseFactory
|
||||||
@ -171,7 +171,7 @@ func (qsc *queueSetCompleter) Complete(dCfg fq.DispatchingConfig) fq.QueueSet {
|
|||||||
if qs == nil {
|
if qs == nil {
|
||||||
qs = &queueSet{
|
qs = &queueSet{
|
||||||
clock: qsc.factory.clock,
|
clock: qsc.factory.clock,
|
||||||
estimatedServiceTime: 60,
|
estimatedServiceSeconds: 0.003,
|
||||||
obsPair: qsc.obsPair,
|
obsPair: qsc.obsPair,
|
||||||
qCfg: qsc.qCfg,
|
qCfg: qsc.qCfg,
|
||||||
virtualTime: 0,
|
virtualTime: 0,
|
||||||
@ -642,8 +642,8 @@ func (qs *queueSet) dispatchLocked() bool {
|
|||||||
qs.qCfg.Name, request.startTime.Format(nsTimeFmt), qs.virtualTime, request.descr1, request.descr2,
|
qs.qCfg.Name, request.startTime.Format(nsTimeFmt), qs.virtualTime, request.descr1, request.descr2,
|
||||||
request.workEstimate, queue.index, queue.virtualStart, queue.requests.Length(), queue.requestsExecuting, queue.seatsInUse, qs.totSeatsInUse)
|
request.workEstimate, queue.index, queue.virtualStart, queue.requests.Length(), queue.requestsExecuting, queue.seatsInUse, qs.totSeatsInUse)
|
||||||
}
|
}
|
||||||
// When a request is dequeued for service -> qs.virtualStart += G
|
// When a request is dequeued for service -> qs.virtualStart += G * width
|
||||||
queue.virtualStart += qs.estimatedServiceTime * float64(request.Seats())
|
queue.virtualStart += qs.estimatedServiceSeconds * float64(request.Seats())
|
||||||
request.decision.Set(decisionExecute)
|
request.decision.Set(decisionExecute)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
@ -686,29 +686,14 @@ func (qs *queueSet) selectQueueLocked() *queue {
|
|||||||
for range qs.queues {
|
for range qs.queues {
|
||||||
qs.robinIndex = (qs.robinIndex + 1) % nq
|
qs.robinIndex = (qs.robinIndex + 1) % nq
|
||||||
queue := qs.queues[qs.robinIndex]
|
queue := qs.queues[qs.robinIndex]
|
||||||
if queue.requests.Length() != 0 {
|
oldestWaiting, _ := queue.requests.Peek()
|
||||||
|
if oldestWaiting != nil {
|
||||||
sMin = math.Min(sMin, queue.virtualStart)
|
sMin = math.Min(sMin, queue.virtualStart)
|
||||||
sMax = math.Max(sMax, queue.virtualStart)
|
sMax = math.Max(sMax, queue.virtualStart)
|
||||||
estimatedWorkInProgress := qs.estimatedServiceTime * float64(queue.seatsInUse)
|
estimatedWorkInProgress := qs.estimatedServiceSeconds * float64(queue.seatsInUse)
|
||||||
dsMin = math.Min(dsMin, queue.virtualStart-estimatedWorkInProgress)
|
dsMin = math.Min(dsMin, queue.virtualStart-estimatedWorkInProgress)
|
||||||
dsMax = math.Max(dsMax, queue.virtualStart-estimatedWorkInProgress)
|
dsMax = math.Max(dsMax, queue.virtualStart-estimatedWorkInProgress)
|
||||||
// the finish R of the oldest request is:
|
currentVirtualFinish := queue.virtualStart + qs.estimatedServiceSeconds*float64(oldestWaiting.Seats())
|
||||||
// start R + G
|
|
||||||
// we are not taking the width of the request into account when
|
|
||||||
// we calculate the virtual finish time of the request because
|
|
||||||
// it can starve requests with smaller wdith in other queues.
|
|
||||||
//
|
|
||||||
// so let's draw an example of the starving scenario:
|
|
||||||
// - G=60 (estimated service time in seconds)
|
|
||||||
// - concurrency limit=2
|
|
||||||
// - we have two queues, q1 and q2
|
|
||||||
// - q1 has an infinite supply of requests with width W=1
|
|
||||||
// - q2 has one request waiting in the queue with width W=2
|
|
||||||
// - start R for both q1 and q2 are at t0
|
|
||||||
// - requests complete really fast, S=1ms on q1
|
|
||||||
// in this scenario we will execute roughly 60,000 requests
|
|
||||||
// from q1 before we pick the request from q2.
|
|
||||||
currentVirtualFinish := queue.virtualStart + qs.estimatedServiceTime
|
|
||||||
klog.V(11).InfoS("Considering queue to dispatch", "queueSet", qs.qCfg.Name, "queue", qs.robinIndex, "finishR", currentVirtualFinish)
|
klog.V(11).InfoS("Considering queue to dispatch", "queueSet", qs.qCfg.Name, "queue", qs.robinIndex, "finishR", currentVirtualFinish)
|
||||||
if currentVirtualFinish < minVirtualFinish {
|
if currentVirtualFinish < minVirtualFinish {
|
||||||
minVirtualFinish = currentVirtualFinish
|
minVirtualFinish = currentVirtualFinish
|
||||||
@ -718,12 +703,7 @@ func (qs *queueSet) selectQueueLocked() *queue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add a method to fifo that lets us peek at the oldest request
|
oldestReqFromMinQueue, _ := minQueue.requests.Peek()
|
||||||
var oldestReqFromMinQueue *request
|
|
||||||
minQueue.requests.Walk(func(r *request) bool {
|
|
||||||
oldestReqFromMinQueue = r
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if oldestReqFromMinQueue == nil {
|
if oldestReqFromMinQueue == nil {
|
||||||
// This cannot happen
|
// This cannot happen
|
||||||
klog.ErrorS(errors.New("selected queue is empty"), "Impossible", "queueSet", qs.qCfg.Name)
|
klog.ErrorS(errors.New("selected queue is empty"), "Impossible", "queueSet", qs.qCfg.Name)
|
||||||
@ -755,7 +735,7 @@ func (qs *queueSet) selectQueueLocked() *queue {
|
|||||||
// queue here. if the last start R (excluded estimated cost)
|
// queue here. if the last start R (excluded estimated cost)
|
||||||
// falls behind the global virtual time, we update the latest virtual
|
// falls behind the global virtual time, we update the latest virtual
|
||||||
// start by: <latest global virtual time> + <previously estimated cost>
|
// start by: <latest global virtual time> + <previously estimated cost>
|
||||||
previouslyEstimatedServiceTime := float64(minQueue.seatsInUse) * qs.estimatedServiceTime
|
previouslyEstimatedServiceTime := float64(minQueue.seatsInUse) * qs.estimatedServiceSeconds
|
||||||
if qs.virtualTime > minQueue.virtualStart-previouslyEstimatedServiceTime {
|
if qs.virtualTime > minQueue.virtualStart-previouslyEstimatedServiceTime {
|
||||||
// per-queue virtual time should not fall behind the global
|
// per-queue virtual time should not fall behind the global
|
||||||
minQueue.virtualStart = qs.virtualTime + previouslyEstimatedServiceTime
|
minQueue.virtualStart = qs.virtualTime + previouslyEstimatedServiceTime
|
||||||
@ -853,7 +833,7 @@ func (qs *queueSet) finishRequestLocked(r *request) {
|
|||||||
|
|
||||||
// When a request finishes being served, and the actual service time was S,
|
// When a request finishes being served, and the actual service time was S,
|
||||||
// the queue’s start R is decremented by (G - S)*width.
|
// the queue’s start R is decremented by (G - S)*width.
|
||||||
r.queue.virtualStart -= (qs.estimatedServiceTime - S) * float64(r.Seats())
|
r.queue.virtualStart -= (qs.estimatedServiceSeconds - S) * float64(r.Seats())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1034,7 +1034,7 @@ func TestTotalRequestsExecutingWithPanic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectQueueLocked(t *testing.T) {
|
func TestSelectQueueLocked(t *testing.T) {
|
||||||
var G float64 = 60
|
var G float64 = 0.003
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
robinIndex int
|
robinIndex int
|
||||||
@ -1087,7 +1087,7 @@ func TestSelectQueueLocked(t *testing.T) {
|
|||||||
robinIndexExpected: []int{0},
|
robinIndexExpected: []int{0},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "width > 1, seats are available for request with the least finish time, queue is picked",
|
name: "width > 1, seats are available for request with the least finish R, queue is picked",
|
||||||
concurrencyLimit: 50,
|
concurrencyLimit: 50,
|
||||||
totSeatsInUse: 25,
|
totSeatsInUse: 25,
|
||||||
robinIndex: -1,
|
robinIndex: -1,
|
||||||
@ -1110,7 +1110,7 @@ func TestSelectQueueLocked(t *testing.T) {
|
|||||||
robinIndexExpected: []int{1},
|
robinIndexExpected: []int{1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "width > 1, seats are not available for request with the least finish time, queue is not picked",
|
name: "width > 1, seats are not available for request with the least finish R, queue is not picked",
|
||||||
concurrencyLimit: 50,
|
concurrencyLimit: 50,
|
||||||
totSeatsInUse: 26,
|
totSeatsInUse: 26,
|
||||||
robinIndex: -1,
|
robinIndex: -1,
|
||||||
@ -1165,9 +1165,10 @@ func TestSelectQueueLocked(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
qs := &queueSet{
|
qs := &queueSet{
|
||||||
estimatedServiceTime: G,
|
estimatedServiceSeconds: G,
|
||||||
robinIndex: test.robinIndex,
|
robinIndex: test.robinIndex,
|
||||||
totSeatsInUse: test.totSeatsInUse,
|
totSeatsInUse: test.totSeatsInUse,
|
||||||
|
qCfg: fq.QueuingConfig{Name: "TestSelectQueueLocked/" + test.name},
|
||||||
dCfg: fq.DispatchingConfig{
|
dCfg: fq.DispatchingConfig{
|
||||||
ConcurrencyLimit: test.concurrencyLimit,
|
ConcurrencyLimit: test.concurrencyLimit,
|
||||||
},
|
},
|
||||||
|
@ -76,13 +76,14 @@ type request struct {
|
|||||||
// queue is an array of requests with additional metadata required for
|
// queue is an array of requests with additional metadata required for
|
||||||
// the FQScheduler
|
// the FQScheduler
|
||||||
type queue struct {
|
type queue struct {
|
||||||
// The requests are stored in a FIFO list.
|
// The requests not yet executing in the real world are stored in a FIFO list.
|
||||||
requests fifo
|
requests fifo
|
||||||
|
|
||||||
// virtualStart is the "virtual time" (R progress meter reading) at
|
// virtualStart is the "virtual time" (R progress meter reading) at
|
||||||
// which the next request will be dispatched in the virtual world.
|
// which the next request will be dispatched in the virtual world.
|
||||||
virtualStart float64
|
virtualStart float64
|
||||||
|
|
||||||
|
// requestsExecuting is the count in the real world
|
||||||
requestsExecuting int
|
requestsExecuting int
|
||||||
index int
|
index int
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user