Abel Wu writes: > On 9/30/23 8:09 AM, Benjamin Segall Wrote: >> + /* >> + * Now best_left and all of its children are eligible, and we are just >> + * looking for deadline == min_deadline >> + */ >> + node = &best_left->run_node; >> + while (node) { >> + struct sched_entity *se = __node_2_se(node); >> + >> + /* min_deadline is the current node */ >> + if (se->deadline == se->min_deadline) >> + return se; > > IMHO it would be better tiebreak on vruntime by moving this hunk to .. > >> + >> + /* min_deadline is in the left branch */ >> if (node->rb_left && >> __node_2_se(node->rb_left)->min_deadline == se->min_deadline) { >> node = node->rb_left; >> continue; >> } > > .. here, thoughts? Yeah, that should work and be better on the tiebreak (and my test code agrees). There's an argument that the tiebreak will never really come up and it's better to avoid the potential one extra cache line from "__node_2_se(node->rb_left)->min_deadline" though. > >> + /* else min_deadline is in the right branch */ >> node = node->rb_right; >> } >> + return NULL; > > Why not 'best'? Since .. The only time this can happen is if the tree is corrupt. We only reach this case if best_left is set at all (and best_left's min_deadline is better than "best"'s, which includes curr). In that case getting an error message is good, and in general I wasn't worrying about it much. > >> +} >> - if (!best || (curr && deadline_gt(deadline, best, curr))) >> - best = curr; >> +static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq) >> +{ >> + struct sched_entity *se = __pick_eevdf(cfs_rq); >> - if (unlikely(!best)) { >> + if (!se) { >> struct sched_entity *left = __pick_first_entity(cfs_rq); > > .. cfs_rq->curr isn't considered here. That said, we should probably consider curr here in the error-case fallback, if just as a "if (!left) left = cfs_rq->curr;" > >> if (left) { >> pr_err("EEVDF scheduling fail, picking leftmost\n"); >> return left; >> } >> } >> - return best; >> + return se; >> } >> #ifdef CONFIG_SCHED_DEBUG >> struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) >> { > > The implementation of __pick_eevdf() now is quite complicated which > makes it really hard to maintain. I'm trying my best to refactor this > part, hopefully can do some help. Below is only for illustration, I > need to test more. > A passing version of that single-loop code minus the cfs_rq->curr handling is here (just pasting the curr handling back on the start/end should do): static struct sched_entity *pick_eevdf_abel(struct cfs_rq *cfs_rq) { struct rb_node *node = cfs_rq->tasks_timeline.rb_root.rb_node; struct sched_entity *best = NULL; struct sched_entity *cand = NULL; bool all_eligible = false; while (node || cand) { struct sched_entity *se = __node_2_se(node); if (!node) { BUG_ON(!cand); node = &cand->run_node; se = __node_2_se(node); all_eligible = true; cand = NULL; /* * Our initial pass ran into an eligible node which is * itself the best */ if (best && (s64)(se->min_deadline - best->deadline) > 0) break; } /* * If this entity is not eligible, try the left subtree. */ if (!all_eligible && !entity_eligible(cfs_rq, se)) { node = node->rb_left; continue; } if (node->rb_left) { struct sched_entity *left = __node_2_se(node->rb_left); BUG_ON(left->min_deadline < se->min_deadline); /* Tiebreak on vruntime */ if (left->min_deadline == se->min_deadline) { node = node->rb_left; all_eligible = true; continue; } if (!all_eligible) { /* * We're going to search right subtree and the one * with min_deadline can be non-eligible, so record * the left subtree as a candidate. */ if (!cand || deadline_gt(min_deadline, cand, left)) cand = left; } } if (!all_eligible && (!best || deadline_gt(deadline, best, se))) best = se; /* min_deadline is at this node, no need to look right */ if (se->deadline == se->min_deadline) { if (all_eligible && (!best || deadline_gte(deadline, best, se))) best = se; if (!all_eligible && (!best || deadline_gt(deadline, best, se))) best = se; node = NULL; continue; } node = node->rb_right; } return best; } This does exactly as well on the tiebreak condition of vruntime as the improved two-loop version you suggested. If we don't care about the tiebreak we can replace the ugly if(all_eligible) if(!all_eligible) in the last section with a single version that just uses deadline_gt (or gte) throughout. Personally with all the extra bits I added for correctness this winds up definitely uglier than the two-loop version, but it might be possible to clean it up further. (And a bunch of it is just personal style preference in the first place) I've also attached my ugly userspace EEVDF tester as an attachment, hopefully I attached it in a correct mode to go through lkml.