// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// SearchAlgorithms.cpp
//
// Implementation file containing all scheduling algorithms.
//
// **PLEASE NOTE**:
//
// Any search algorithm in here must be fully reentrant. On UMS schedulers, the UMS primary will invoke these routines
// to perform a search for work.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include "concrtinternal.h"
namespace Concurrency
{
namespace details
{
//***************************************************************************
//
// General:
//
///
/// Constructs a work item from an internal context.
///
WorkItem::WorkItem(InternalContextBase *pContext) :
m_type(WorkItemTypeContext),
m_pSegment(pContext->GetScheduleGroupSegment()),
m_pContext(pContext)
{
}
///
/// Resolves a token to an underlying work item.
///
bool WorkItem::ResolveToken()
{
CONCRT_COREASSERT(IsToken());
switch(m_type)
{
case WorkItemTypeRealizedChoreToken:
{
RealizedChore *pChore = m_pSegment->GetRealizedChore();
if (pChore != NULL)
{
m_pRealizedChore = pChore;
m_type = WorkItemTypeRealizedChore;
}
break;
}
case WorkItemTypeUnrealizedChoreToken:
{
if (m_pWorkQueue == MAILBOX_LOCATION)
{
_UnrealizedChore *pChore;
if (!m_pSegment->m_mailedTasks.Dequeue(&pChore))
pChore = NULL;
if (pChore != NULL)
{
// The chore may not be from a detached workqueue, but since it is dequeued from a mailbox, we set it as detached
// which will add the stealing context to a list in the task collection instead of the owning contexts stealer collection.
pChore->_SetDetached(true);
m_pUnrealizedChore = pChore;
m_type = WorkItemTypeUnrealizedChore;
}
}
else
{
_UnrealizedChore *pChore = m_pWorkQueue->Steal(false);
if (pChore != NULL)
{
m_pUnrealizedChore = pChore;
m_type = WorkItemTypeUnrealizedChore;
}
break;
}
}
}
return !IsToken();
}
///
/// Binds the work item to a context and returns the context. This may or may not allocate a new context. Note that
/// act of binding which performs a context allocation will transfer a single count of work to the counter of the new
/// context.
///
InternalContextBase *WorkItem::Bind()
{
if (IsToken() && !ResolveToken())
return NULL;
switch(m_type)
{
case WorkItemTypeUnrealizedChore:
m_pContext = m_pSegment->GetInternalContext(m_pUnrealizedChore, true);
if (m_pContext != NULL)
{
m_pContext->SaveDequeuedTask();
m_type = WorkItemTypeContext;
}
break;
case WorkItemTypeRealizedChore:
m_pContext = m_pSegment->GetInternalContext(m_pRealizedChore);
if (m_pContext != NULL)
{
m_pContext->SaveDequeuedTask();
m_type = WorkItemTypeContext;
}
break;
case WorkItemTypeContext:
break;
}
return m_pContext;
}
///
/// Binds the work item to the specified context (which is allocated). This will never allocate a new context.
///
void WorkItem::BindTo(InternalContextBase *pContext)
{
switch(m_type)
{
case WorkItemTypeUnrealizedChore:
pContext->PrepareForUse(m_pSegment, m_pUnrealizedChore, true);
break;
case WorkItemTypeRealizedChore:
pContext->PrepareForUse(m_pSegment, m_pRealizedChore, false);
break;
}
m_pContext = pContext;
m_type = WorkItemTypeContext;
}
///
/// Invokes the work item.
///
void WorkItem::Invoke()
{
CONCRT_COREASSERT(m_type == WorkItemTypeRealizedChore || m_type == WorkItemTypeUnrealizedChore);
switch(m_type)
{
case WorkItemTypeUnrealizedChore:
m_pUnrealizedChore->_Invoke();
break;
case WorkItemTypeRealizedChore:
m_pRealizedChore->Invoke();
m_pSegment->GetGroup()->GetScheduler()->ReleaseRealizedChore(m_pRealizedChore);
break;
}
}
///
/// Transfers reference counts as necessary to inline the given work item on the given context. This may
/// only be called on a work item that can be inlined (e.g.: an unbound one).
///
///
/// The context that is attempting to inline the work item.
///
void WorkItem::TransferReferences(InternalContextBase *pContext)
{
ASSERT(m_type == WorkItemTypeRealizedChore || m_type == WorkItemTypeUnrealizedChore);
ScheduleGroupSegmentBase *pSegment = pContext->GetScheduleGroupSegment();
if (m_type == WorkItemTypeRealizedChore)
{
if (pSegment->GetGroup() != m_pSegment->GetGroup())
{
pContext->SwapScheduleGroupSegment(m_pSegment, false);
}
else
{
//
// If newGroup is the same as the existing group, we need to release a reference since both, the context,
// and the realized chore, have a reference on the schedule group, and we only need to hold one reference.
//
OMTRACE(MTRACE_EVT_WORKITEMDEREFERENCE, pSegment->GetGroup(), NULL, NULL, 0);
pSegment->GetGroup()->InternalRelease();
}
}
else if (pSegment->GetGroup() != m_pSegment->GetGroup())
{
pContext->SwapScheduleGroupSegment(m_pSegment, true);
}
}
///
/// Resets the work search context to utilize the specified algorithm at the starting iterator position.
///
///
/// The virtual processor binding the searching.
///
///
/// The algorithm to reset the iterator with.
///
void WorkSearchContext::Reset(VirtualProcessor *pVirtualProcessor, Algorithm algorithm)
{
m_LRCBias = 0;;
m_pVirtualProcessor = pVirtualProcessor;
m_maskId = m_pVirtualProcessor->GetMaskId();
m_pScheduler = pVirtualProcessor->GetOwningNode()->GetScheduler();
m_serviceTick = m_lastPriorityPull = platform::__GetTickCount64();
switch(algorithm)
{
case AlgorithmCacheLocal:
m_pSearchFn = &WorkSearchContext::SearchCacheLocal;
m_pSearchYieldFn = &WorkSearchContext::SearchCacheLocalYield;
break;
case AlgorithmFair:
m_pSearchFn = &WorkSearchContext::SearchFair;
m_pSearchYieldFn = &WorkSearchContext::SearchFairYield;
break;
default:
ASSERT(false);
}
}
///
/// Steals a local runnable from a virtual processor within the specified node. Note that this allows a given virtual processor
/// to be skipped.
///
bool WorkSearchContext::StealLocalRunnable(WorkItem *pWorkItem, SchedulingNode *pNode, VirtualProcessor *pSkipVirtualProcessor)
{
int idx;
VirtualProcessor *pVProc = pNode->GetFirstVirtualProcessor(&idx);
while (pVProc != NULL)
{
if (pVProc != pSkipVirtualProcessor)
{
pVProc->ServiceMark(m_serviceTick);
InternalContextBase *pContext = pVProc->StealLocalRunnableContext();
if (pContext != NULL)
{
*pWorkItem = WorkItem(pContext);
return true;
}
}
pVProc = pNode->GetNextVirtualProcessor(&idx);
}
return false;
}
///
/// Steals a local runnable from a virtual processor of any scheduling node other than the specified local node.
///
bool WorkSearchContext::StealForeignLocalRunnable(WorkItem *pWorkItem, SchedulingNode *pLocalNode)
{
int idx;
SchedulingNode *pNode = m_pScheduler->GetFirstSchedulingNode(&idx);
while (pNode != NULL)
{
if (pNode != pLocalNode)
{
if (StealLocalRunnable(pWorkItem, pNode, NULL))
return true;
}
pNode = m_pScheduler->GetNextSchedulingNode(&idx);
}
return false;
}
///
/// Performs a pre-search for any "special" contexts (e.g.: the UMS SUT)
///
bool WorkSearchContext::PreSearch(WorkItem *pWorkItem)
{
InternalContextBase *pContext = m_pVirtualProcessor->PreRunnableSearch();
if (pContext != NULL)
{
*pWorkItem = WorkItem(pContext);
return true;
}
return false;
}
///
/// Gets a local runnable context from the specified virtual processor.
///
bool WorkSearchContext::GetLocalRunnable(WorkItem *pWorkItem, VirtualProcessor *pVirtualProcessor, bool fYieldingSearch)
{
if (fYieldingSearch)
{
InternalContextBase *pContext = pVirtualProcessor->GetLocalRunnableContext();
if (pContext != NULL)
{
*pWorkItem = WorkItem(pContext);
return true;
}
}
else
{
BiasStageType biasStage = BiasStage();
if (biasStage < BiasStageSkipLRC)
{
InternalContextBase *pContext = (biasStage == BiasStageFlipLRC) ? pVirtualProcessor->StealLocalRunnableContext() : pVirtualProcessor->GetLocalRunnableContext();
if (pContext != NULL)
{
*pWorkItem = WorkItem(pContext);
LRCBias();
return true;
}
}
ResetLRCBias();
}
return false;
}
///
/// Gets a runnable from the specified schedule group segment.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The schedule group segment in which to look for a runnable context.
///
///
/// An indication of whether or not a runnable context was found in the segment.
///
bool WorkSearchContext::GetRunnableContext(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pSegment)
{
InternalContextBase *pContext = pSegment->GetRunnableContext();
if (pContext != NULL)
{
*pWorkItem = WorkItem(pContext);
return true;
}
return false;
}
///
/// Gets a realized chore from the specified schedule group segment.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The schedule group segment in which to look for a realized chore.
///
///
/// If true, the actual work item is returned. If false, a token to the work is returned. The work is not dequeued until the token
/// is resolved.
///
///
/// An indication of whether or not a realized chore was found in the segment.
///
bool WorkSearchContext::GetRealizedChore(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pSegment, bool fRealWork)
{
if (fRealWork)
{
RealizedChore *pRealizedChore = pSegment->GetRealizedChore();
if (pRealizedChore != NULL)
{
*pWorkItem = WorkItem(pRealizedChore, pSegment);
return true;
}
}
else
{
if (pSegment->HasRealizedChores())
{
*pWorkItem = WorkItem(pSegment);
return true;
}
}
return false;
}
///
/// Gets an unrealized chore from the specified schedule group segment.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The schedule group segment in which to look for an unrealized chore.
///
///
/// Whether to steal the task at the bottom end of the work stealing queue even if it is an affinitized to a location
/// that has active searches. This is set to true on the final SFW pass to ensure a vproc does not deactivate while there
/// are chores higher up in the queue that are un-affinitized and therefore inaccessible via a mailbox.
///
///
/// If true, the actual work item is returned. If false, a token to the work is returned. The work is not dequeued until the token
/// is resolved.
///
///
/// An indication of whether or not an unrealized chore was found in the segment.
///
bool WorkSearchContext::GetUnrealizedChore(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pSegment, bool fForceStealLocalized, bool fRealWork)
{
if (fRealWork)
{
_UnrealizedChore *pUnrealizedChore = pSegment->StealUnrealizedChore(fForceStealLocalized);
if (pUnrealizedChore != NULL)
{
*pWorkItem = WorkItem(pUnrealizedChore, pSegment);
return true;
}
}
else
{
// We should never be in the last pass of search for work (which is when fForceStealLocalized is set to true) if we're looking for a token.
ASSERT(!fForceStealLocalized);
WorkQueue *pWorkQueue = pSegment->LocateUnrealizedChores();
if (pWorkQueue != NULL)
{
*pWorkItem = WorkItem(pWorkQueue, pSegment);
return true;
}
}
return false;
}
///
/// Performs a quick search of a particular segment.
///
bool WorkSearchContext::QuickSearch(ScheduleGroupSegmentBase *pQCSegment,
WorkItem *pWorkItem,
bool fLastPass,
ULONG allowableTypes)
{
if (((allowableTypes & WorkItem::WorkItemTypeContext) && GetRunnableContext(pWorkItem, pQCSegment)) ||
((allowableTypes & WorkItem::WorkItemTypeMaskAnyRealizedChore) &&
GetRealizedChore(pWorkItem, pQCSegment, !!(allowableTypes & WorkItem::WorkItemTypeRealizedChore))) ||
((allowableTypes & WorkItem::WorkItemTypeMaskAnyUnrealizedChore) &&
GetUnrealizedChore(pWorkItem, pQCSegment, fLastPass, !!(allowableTypes & WorkItem::WorkItemTypeUnrealizedChore))))
{
return true;
}
return false;
}
///
/// Performs a quick yielding search of a particular segment.
///
bool WorkSearchContext::QuickSearchYield(ScheduleGroupSegmentBase *pQCSegment,
WorkItem *pWorkItem,
bool fLastPass,
ULONG allowableTypes)
{
if (((allowableTypes & WorkItem::WorkItemTypeMaskAnyUnrealizedChore) &&
GetUnrealizedChore(pWorkItem, pQCSegment, fLastPass, !!(allowableTypes & WorkItem::WorkItemTypeUnrealizedChore))) ||
((allowableTypes & WorkItem::WorkItemTypeMaskAnyRealizedChore) &&
GetRealizedChore(pWorkItem, pQCSegment, !!(allowableTypes & WorkItem::WorkItemTypeRealizedChore))) ||
((allowableTypes & WorkItem::WorkItemTypeContext) && GetRunnableContext(pWorkItem, pQCSegment)))
{
return true;
}
return false;
}
///
/// Determines if a segment should be skipped given the search parameters and the segment's affinity.
///
///
/// The segment to query about skipping.
///
///
/// A segment which should be arbitrarily skipped regardless of affinity type. This parameter can be NULL.
///
///
/// The search affinity type to query for.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// An indication as to whether pSegment should be skipped according to the pSkipSegment and affinity parameters.
///
bool WorkSearchContext::SkipSegmentSearch(ScheduleGroupSegmentBase *pSegment, ScheduleGroupSegmentBase *pSkipSegment, SearchAffinity affinity, bool fLastPass)
{
if (pSegment == pSkipSegment)
{
return true;
}
bool fSkip = false;
const location& segmentAffinity = pSegment->GetAffinity();
switch(affinity)
{
case SearchNonAffine:
//
// Skip if it has any affinity and we're looking for non-affine work.
//
fSkip = !segmentAffinity._Is_system();
break;
case SearchAffineLocal:
//
// Skip if it has specific affinity to something that doesn't intersect with us.
//
fSkip = segmentAffinity._Is_system() || !m_pVirtualProcessor->GetLocation()._FastVPIntersects(segmentAffinity);
break;
case SearchAffineNotMe:
//
// Skip if it has specific affinity to us **OR** the virtual processors to which it has affinity are in the middle
// of search-for-work. This is an optimization to prevent us from ripping affine work out from underneath someone who
// will soon find it.
//
// In the last pass of SFW, we ignore this heuristic in order to avoid deadlock in required dependence cases.
//
fSkip = segmentAffinity._Is_system() ||
(m_pVirtualProcessor->GetLocation()._FastVPIntersects(segmentAffinity) ||
(m_pScheduler->HasSearchers(pSegment->GetAffinitySet()) && !fLastPass));
break;
default:
break;
}
return fSkip;
}
///
/// Searches the schedule group to which pSegment belongs to find a runnable. The group is searched for segments according to the specified
/// affinity type.
///
///
/// If an appropriate runnable is found, the resulting work item will be placed here.
///
///
/// A segment within the group to search. This segment has bias within the group if it matches the specified affinity type.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// An indication of whether a work item was found or not.
///
bool WorkSearchContext::GetRunnableContextWithinGroup(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pSegment, SearchAffinity affinity, bool fLastPass)
{
ScheduleGroupBase *pGroup = pSegment->GetGroup();
//
// @TODO_LOC:
//
// We need to slice this differently so that we pick up "local" affine work before "non-local" affine work, etc...
//
if (!SkipSegmentSearch(pSegment, NULL, affinity, fLastPass) && GetRunnableContext(pWorkItem, pSegment))
{
return true;
}
ScheduleGroupSegmentBase *pCurSegment = pGroup->GetFirstScheduleGroupSegment(affinity != SearchNonAffine);
while(pCurSegment != NULL)
{
if (!SkipSegmentSearch(pCurSegment, pSegment, affinity, fLastPass) && GetRunnableContext(pWorkItem, pCurSegment))
{
return true;
}
pCurSegment = pGroup->GetNextScheduleGroupSegment(pCurSegment);
}
return false;
}
///
/// Searches the schedule group to which pSegment belongs to find a realized chore. The group is searched for segments according to the specified
/// affinity type.
///
///
/// If an appropriate realized chore is found, the resulting work item will be placed here.
///
///
/// A segment within the group to search. This segment has bias within the group if it matches the specified affinity type.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// An indication of whether a work item was found or not.
///
bool WorkSearchContext::GetRealizedChoreWithinGroup(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pSegment, bool fRealWork, SearchAffinity affinity, bool fLastPass)
{
ScheduleGroupBase *pGroup = pSegment->GetGroup();
//
// @TODO_LOC:
//
// We need to slice this differently so that we pick up "local" affine work before "non-local" affine work, etc...
//
if (!SkipSegmentSearch(pSegment, NULL, affinity, fLastPass) && GetRealizedChore(pWorkItem, pSegment, fRealWork))
{
return true;
}
ScheduleGroupSegmentBase *pCurSegment = pGroup->GetFirstScheduleGroupSegment(affinity != SearchNonAffine);
while(pCurSegment != NULL)
{
if (!SkipSegmentSearch(pCurSegment, pSegment, affinity, fLastPass) && GetRealizedChore(pWorkItem, pCurSegment, fRealWork))
{
return true;
}
pCurSegment = pGroup->GetNextScheduleGroupSegment(pCurSegment);
}
return false;
}
///
/// Searches the schedule group to which pSegment belongs to find an unrealized chore. The group is searched for segments according to the
/// specified affinity type.
///
///
/// If an appropriate unrealized chore is found, the resulting work item will be placed here.
///
///
/// A segment within the group to search. This segment has bias within the group if it matches the specified affinity type.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// An indication of whether a work item was found or not.
///
bool WorkSearchContext::GetUnrealizedChoreWithinGroup(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pSegment, bool fRealWork, SearchAffinity affinity, bool fLastPass)
{
ScheduleGroupBase *pGroup = pSegment->GetGroup();
//
// @TODO_LOC:
//
// We need to slice this differently so that we pick up "local" affine work before "non-local" affine work, etc... We also might need to
// slice out pSegment first. The second aspect requires more thought.
//
if (!SkipSegmentSearch(pSegment, NULL, affinity, fLastPass) && GetUnrealizedChore(pWorkItem, pSegment, fLastPass, fRealWork))
{
return true;
}
ScheduleGroupSegmentBase *pCurSegment = pGroup->GetFirstScheduleGroupSegment(affinity != SearchNonAffine);
while(pCurSegment != NULL)
{
if (!SkipSegmentSearch(pCurSegment, pSegment, affinity, fLastPass) && GetUnrealizedChore(pWorkItem, pCurSegment, fLastPass, fRealWork))
{
return true;
}
pCurSegment = pGroup->GetNextScheduleGroupSegment(pCurSegment);
}
return false;
}
//***************************************************************************
//
// Fair Searches
//
// NOTE: At present, we completely ignore affine lists because fair schedulers ignore location hints. This means you cannot "switch" scheduling
// to fair on a cache local scheduler should that ever come up.
//
///
/// Performs a fair search for runnables in the specified ring.
///
bool WorkSearchContext::SearchFair_Runnables(WorkItem *pWorkItem, SchedulingRing *pRing)
{
int idx;
ScheduleGroupSegmentBase *pSegment = pRing->GetPseudoRRNonAffineScheduleGroupSegment(&idx);
int idxStart = idx;
while (pSegment != NULL)
{
InternalContextBase *pContext = pSegment->GetRunnableContext();
if (pContext != NULL)
{
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
*pWorkItem = WorkItem(pContext);
return true;
}
pSegment = pRing->GetNextNonAffineScheduleGroupSegment(&idx, idxStart);
}
return false;
}
///
/// Performs a fair search for realized chores in the specified ring.
///
bool WorkSearchContext::SearchFair_Realized(WorkItem *pWorkItem, SchedulingRing *pRing, bool fRealItem)
{
int idx;
ScheduleGroupSegmentBase *pSegment = pRing->GetPseudoRRNonAffineScheduleGroupSegment(&idx);
int idxStart = idx;
while (pSegment != NULL)
{
if (fRealItem)
{
RealizedChore *pRealizedChore = pSegment->GetRealizedChore();
if (pRealizedChore != NULL)
{
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
*pWorkItem = WorkItem(pRealizedChore, pSegment);
return true;
}
}
else
{
if (pSegment->HasRealizedChores())
{
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
*pWorkItem = WorkItem(pSegment);
return true;
}
}
pSegment = pRing->GetNextNonAffineScheduleGroupSegment(&idx, idxStart);
}
return false;
}
///
/// Performs a fair search for unrealized chores in the specified ring.
///
bool WorkSearchContext::SearchFair_Unrealized(WorkItem *pWorkItem, SchedulingRing *pRing, bool fRealItem)
{
int idx;
ScheduleGroupSegmentBase *pSegment = pRing->GetPseudoRRNonAffineScheduleGroupSegment(&idx);
int idxStart = idx;
while (pSegment != NULL)
{
if (fRealItem)
{
_UnrealizedChore *pUnrealizedChore = pSegment->StealUnrealizedChore();
if (pUnrealizedChore != NULL)
{
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
*pWorkItem = WorkItem(pUnrealizedChore, pSegment);
return true;
}
}
else
{
WorkQueue *pWorkQueue = pSegment->LocateUnrealizedChores();
if (pWorkQueue != NULL)
{
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
*pWorkItem = WorkItem(pWorkQueue, pSegment);
return true;
}
}
pSegment = pRing->GetNextNonAffineScheduleGroupSegment(&idx, idxStart);
}
return false;
}
///
/// Performs a fair search for work.
///
bool WorkSearchContext::SearchFair(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool, ULONG allowableTypes)
{
bool fFound = false;
CONCRT_COREASSERT(pOriginSegment != NULL);
//
// Do any up-front searching required for special circumstances (e.g.: UMS schedulers)
//
if (PreSearch(pWorkItem))
return true;
//
// The fair search essentially round robins among scheduling rings and groups within a ring.
// If you consider the search space as follows:
//
// SR SR SR SR
// Contexts ---------------------->
// Realized ---------------------->
// Unrealized ---------------------->
//
// fair scheduling will make horizontal slices through the search space to find work.
//
// Each entry in the above matrix can be viewed as:
//
// SG -> SG -> SG -> SG
//
// However, after finding work in a particular ring, fair will move onto the next ring in round-robin fashion.
//
//
// At the top of each search, reset to the next ring in the round robin index. This is simply the starting point for this search.
//
SchedulingRing *pStartingRing = m_pScheduler->GetNextSchedulingRing();
if (allowableTypes & WorkItem::WorkItemTypeContext)
{
SchedulingRing *pRing = pStartingRing;
while (pRing != NULL)
{
fFound = SearchFair_Runnables(pWorkItem, pRing);
if (fFound)
{
m_pScheduler->SetNextSchedulingRing(pRing);
break;
}
pRing = m_pScheduler->GetNextSchedulingRing(pStartingRing, pRing);
}
if (!fFound)
fFound = StealForeignLocalRunnable(pWorkItem, m_pVirtualProcessor->GetOwningNode());
}
if (!fFound && (allowableTypes & WorkItem::WorkItemTypeMaskAnyRealizedChore))
{
SchedulingRing *pRing = pStartingRing;
while (pRing != NULL)
{
fFound = SearchFair_Realized(pWorkItem, pRing, !!(allowableTypes & WorkItem::WorkItemTypeRealizedChore));
if (fFound)
{
m_pScheduler->SetNextSchedulingRing(pRing);
break;
}
pRing = m_pScheduler->GetNextSchedulingRing(pStartingRing, pRing);
}
}
if (!fFound && (allowableTypes & WorkItem::WorkItemTypeMaskAnyUnrealizedChore))
{
SchedulingRing *pRing = pStartingRing;
while (pRing != NULL)
{
fFound = SearchFair_Unrealized(pWorkItem, pRing, !!(allowableTypes & WorkItem::WorkItemTypeUnrealizedChore));
if (fFound)
{
m_pScheduler->SetNextSchedulingRing(pRing);
break;
}
pRing = m_pScheduler->GetNextSchedulingRing(pStartingRing, pRing);
}
}
return fFound;
}
///
/// Performs a fair search for work in the yielding case.
///
bool WorkSearchContext::SearchFairYield(WorkItem *pWorkItem, ScheduleGroupSegmentBase *, bool, ULONG allowableTypes)
{
//
// The yielding case slices identically to the regular case excepting that the search is done in a pseudo-reverse order
//
bool fFound = false;
//
// Do any up-front searching required for special circumstances (e.g.: UMS schedulers)
//
if (PreSearch(pWorkItem))
return true;
//
// At the top of each search, reset to the next ring in the round robin index. This is simply the starting point for this search.
//
SchedulingRing *pStartingRing = m_pScheduler->GetNextSchedulingRing();
if (allowableTypes & WorkItem::WorkItemTypeMaskAnyUnrealizedChore)
{
SchedulingRing *pRing = pStartingRing;
while (pRing != NULL)
{
fFound = SearchFair_Unrealized(pWorkItem, pRing, !!(allowableTypes & WorkItem::WorkItemTypeUnrealizedChore));
if (fFound)
{
m_pScheduler->SetNextSchedulingRing(pRing);
break;
}
pRing = m_pScheduler->GetNextSchedulingRing(pStartingRing, pRing);
}
}
if (!fFound && (allowableTypes & WorkItem::WorkItemTypeMaskAnyRealizedChore))
{
SchedulingRing *pRing = pStartingRing;
while (pRing != NULL)
{
fFound = SearchFair_Realized(pWorkItem, pRing, !!(allowableTypes & WorkItem::WorkItemTypeRealizedChore));
if (fFound)
{
m_pScheduler->SetNextSchedulingRing(pRing);
break;
}
pRing = m_pScheduler->GetNextSchedulingRing(pStartingRing, pRing);
}
}
if (!fFound && (allowableTypes & WorkItem::WorkItemTypeContext))
{
SchedulingRing *pRing = pStartingRing;
while (pRing != NULL)
{
fFound = SearchFair_Runnables(pWorkItem, pRing);
if (fFound)
{
m_pScheduler->SetNextSchedulingRing(pRing);
break;
}
pRing = m_pScheduler->GetNextSchedulingRing(pStartingRing, pRing);
}
if (!fFound)
fFound = StealForeignLocalRunnable(pWorkItem, m_pVirtualProcessor->GetOwningNode());
}
return fFound;
}
//***************************************************************************
//
// Cache Local Searches
//
///
/// Searches for a runnable within the specified ring. Before searching elsewhere, it searches the segment and group specified by
/// pBiasSegment according to the rules of the search and the requested affinity type.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The scheduling ring to search.
///
///
/// The segment to bias the search to. This segment and its corresponding group are searched first!
///
///
/// Determines whether or not to check other local LRCs in this search.
///
///
/// The search affinity type to query for.
///
///
/// A bitmap of work-types allowed to be returned from the search. This indicates whether or not runnables, realized chores, or unrealized chores
/// can be returned as well as whether the actual work item or only a token should be returned.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// An indication of whether a runnable was found in the bias segment, group, or the specified ring.
///
bool WorkSearchContext::SearchCacheLocal_Runnables(WorkItem *pWorkItem, SchedulingRing *pRing, ScheduleGroupSegmentBase *pBiasSegment,
bool fOtherLocalLRCCheck, SearchAffinity affinity, ULONG allowableTypes, bool fLastPass)
{
if (pBiasSegment != NULL && GetRunnableContextWithinGroup(pWorkItem, pBiasSegment, affinity, fLastPass))
{
return true;
}
//
// As much as I abhor the one off placement of this here, it's the "cleanest" place to put this for its given location within SFW.
// Attempt to steal a local runnable context from the current node.
//
if (fOtherLocalLRCCheck && StealLocalRunnable(pWorkItem, m_pVirtualProcessor->GetOwningNode(), m_pVirtualProcessor))
{
return true;
}
int idx;
ScheduleGroupSegmentBase *pSegment =
(affinity == SearchNonAffine) ? pRing->GetPseudoRRNonAffineScheduleGroupSegment(&idx) : pRing->GetPseudoRRAffineScheduleGroupSegment(&idx);
int idxStart = idx;
while (pSegment != NULL)
{
ScheduleGroupSegmentBase *pQCSegment = m_pScheduler->AcquireQuickCacheSlot(m_maskId);
if (pQCSegment)
{
if (QuickSearch(pQCSegment, pWorkItem, fLastPass, allowableTypes))
return true;
}
//
// Is this a segment we should skip:
//
// - If we are explicitly told to skip it in the search because it was checked elsewhere (pSkipSegment).
// - If we are searching for affine work and it matches or doesn't match the search context affinity as dictated by the affinity parameter.
//
if (!SkipSegmentSearch(pSegment, pBiasSegment, affinity, fLastPass) && GetRunnableContext(pWorkItem, pSegment))
{
if (affinity == SearchNonAffine)
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
else
pRing->SetPseudoRRAffineScheduleGroupSegmentNext(idx);
return true;
}
pSegment = (affinity == SearchNonAffine) ? pRing->GetNextNonAffineScheduleGroupSegment(&idx, idxStart) :
pRing->GetNextAffineScheduleGroupSegment(&idx, idxStart);
}
return false;
}
///
/// Searches for a realized chore within the specified ring. Before searching elsewhere, it searches the segment and group specified by
/// pBiasSegment according to the rules of the search and the requested affinity type.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The scheduling ring to search.
///
///
/// The segment to bias the search to. This segment and its corresponding group are searched first!
///
///
/// If true, the actual work item is returned. If false, a token to the work is returned. The work is not dequeued until the token
/// is resolved.
///
///
/// The search affinity type to query for.
///
///
/// A bitmap of work-types allowed to be returned from the search. This indicates whether or not runnables, realized chores, or unrealized chores
/// can be returned as well as whether the actual work item or only a token should be returned.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// An indication of whether a realized chore was found in the bias segment, group, or the specified ring.
///
bool WorkSearchContext::SearchCacheLocal_Realized(WorkItem *pWorkItem, SchedulingRing *pRing, ScheduleGroupSegmentBase *pBiasSegment,
bool fRealWork, SearchAffinity affinity, ULONG allowableTypes, bool fLastPass)
{
bool fFound = false;
if (pBiasSegment != NULL && GetRealizedChoreWithinGroup(pWorkItem, pBiasSegment, fRealWork, affinity, fLastPass))
{
return true;
}
int idx;
ScheduleGroupSegmentBase *pSegment =
(affinity == SearchNonAffine) ? pRing->GetPseudoRRNonAffineScheduleGroupSegment(&idx) : pRing->GetPseudoRRAffineScheduleGroupSegment(&idx);
int idxStart = idx;
while (pSegment != NULL && !fFound)
{
ScheduleGroupSegmentBase *pQCSegment = m_pScheduler->AcquireQuickCacheSlot(m_maskId);
if (pQCSegment)
{
if (QuickSearch(pQCSegment, pWorkItem, fLastPass, allowableTypes))
return true;
}
//
// Is this a segment we should skip:
//
// - If we are explicitly told to skip it in the search because it was checked elsewhere (pSkipSegment).
// - If we are searching for affine work and it matches or doesn't match the search context affinity as dictated by the affinity parameter.
//
if (!SkipSegmentSearch(pSegment, pBiasSegment, affinity, fLastPass) && GetRealizedChore(pWorkItem, pSegment, fRealWork))
{
if (affinity == SearchNonAffine)
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
else
pRing->SetPseudoRRAffineScheduleGroupSegmentNext(idx);
return true;
}
pSegment = (affinity == SearchNonAffine) ? pRing->GetNextNonAffineScheduleGroupSegment(&idx, idxStart) :
pRing->GetNextAffineScheduleGroupSegment(&idx, idxStart);
}
return false;
}
///
/// Searches for an unrealized chore within the specified ring. Before searching elsewhere, it searches the segment and group specified by
/// pBiasSegment according to the rules of the search and the requested affinity type.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The scheduling ring to search.
///
///
/// The segment to bias the search to. This segment and its corresponding group are searched first!
///
///
/// If true, the actual work item is returned. If false, a token to the work is returned. The work is not dequeued until the token
/// is resolved.
///
///
/// The search affinity type to query for.
///
///
/// A bitmap of work-types allowed to be returned from the search. This indicates whether or not runnables, realized chores, or unrealized chores
/// can be returned as well as whether the actual work item or only a token should be returned.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// An indication of whether an unrealized chore was found in the bias segment, group, or the specified ring.
///
bool WorkSearchContext::SearchCacheLocal_Unrealized(WorkItem *pWorkItem, SchedulingRing *pRing, ScheduleGroupSegmentBase *pBiasSegment,
bool fRealWork, SearchAffinity affinity, ULONG allowableTypes, bool fLastPass)
{
bool fFound = false;
if (pBiasSegment != NULL && GetUnrealizedChoreWithinGroup(pWorkItem, pBiasSegment, fRealWork, affinity, fLastPass))
{
return true;
}
int idx;
ScheduleGroupSegmentBase *pSegment =
(affinity == SearchNonAffine) ? pRing->GetPseudoRRNonAffineScheduleGroupSegment(&idx) : pRing->GetPseudoRRAffineScheduleGroupSegment(&idx);
int idxStart = idx;
while (pSegment != NULL && !fFound)
{
ScheduleGroupSegmentBase *pQCSegment = m_pScheduler->AcquireQuickCacheSlot(m_maskId);
if (pQCSegment)
{
if (QuickSearch(pQCSegment, pWorkItem, fLastPass, allowableTypes))
return true;
}
//
// Is this a segment we should skip:
//
// - If we are explicitly told to skip it in the search because it was checked elsewhere (pSkipSegment).
// - If we are searching for affine work and it matches or doesn't match the search context affinity as dictated by the affinity parameter.
//
if (!SkipSegmentSearch(pSegment, pBiasSegment, affinity, fLastPass) && GetUnrealizedChore(pWorkItem, pSegment, fLastPass, fRealWork))
{
if (affinity == SearchNonAffine)
pRing->SetPseudoRRNonAffineScheduleGroupSegmentNext(idx);
else
pRing->SetPseudoRRAffineScheduleGroupSegmentNext(idx);
return true;
}
pSegment = (affinity == SearchNonAffine) ? pRing->GetNextNonAffineScheduleGroupSegment(&idx, idxStart) :
pRing->GetNextAffineScheduleGroupSegment(&idx, idxStart);
}
return false;
}
///
/// Searches for work within the scheduler according to the cache local (schedule group local) search algorithm.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The segment to bias the search to.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// A bitmap of work-types allowed to be returned from the search. This indicates whether or not runnables, realized chores, or unrealized chores
/// can be returned as well as whether the actual work item or only a token should be returned.
///
///
/// An indication of whether a work item was found or not.
///
bool WorkSearchContext::SearchCacheLocal(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass, ULONG allowableTypes)
{
bool fFound = false;
if (PreSearch(pWorkItem))
return true;
ASSERT(pOriginSegment);
m_serviceTick = platform::__GetTickCount64();
m_pScheduler->PeriodicScan(m_serviceTick);
//
// @TODO: This is a temporary patch until we have priority and boosts in the scheduler. Right now, search for any segment in the locked
// FIFO of "priority" segments to see if we need to service someone to avoid livelock. We also periodically scan to determine whether
// groups need to go into this list.
//
if (CheckPriorityList(m_serviceTick))
{
if (m_pScheduler->HasPriorityObjects())
{
BoostedObject *pObject = m_pScheduler->GetNextPriorityObject();
while (pObject != NULL)
{
if (pObject->IsScheduleGroupSegment())
{
ScheduleGroupSegmentBase *pSegment = ScheduleGroupSegmentBase::FromBoostEntry(pObject);
OMTRACE(MTRACE_EVT_PRIORITYPULL, pSegment, NULL, m_pVirtualProcessor, 0);
if (QuickSearch(pSegment, pWorkItem, fLastPass, allowableTypes))
{
fFound = true;
break;
}
}
else if (allowableTypes & WorkItem::WorkItemTypeContext)
{
VirtualProcessor *pVProc = VirtualProcessor::FromBoostEntry(pObject);
InternalContextBase *pContext = pVProc->StealLocalRunnableContext();
if (pContext != NULL)
{
*pWorkItem = WorkItem(pContext);
fFound = true;
break;
}
}
pObject = m_pScheduler->GetNextPriorityObject();
}
}
//
// If we found something in a priority segment, we need to mark the V-Proc so that it once again looks for affinitized work
// the next time through SFW.
//
m_pVirtualProcessor->MarkGrabbedPriority();
}
SchedulingRing *pOwningRing = m_pVirtualProcessor->GetOwningRing();
if (!fFound)
{
//
// Before describing the cache local search algorithm, some definitions to aid in this are in order:
//
// Work:
// local work -- Work within a node/ring that a given virtual processor belongs to is considered local work
// foreign work -- Work within a node/ring that a given virtual processor does *NOT* belong to is considered foreign work
// affine work -- Work which is placed at a location more specific than the system location is affine work
// non-affine work -- Work which is placed at the system location (or no location) is considered non-affine work
//
// Virtual Processors:
// rambling -- a given virtual processor is *rambling* when it is executing foreign work (regardless whether such work is affine to it or not)
// non-affine -- a given virtual processor is *non-affine* when it is executing non-affine work (regardless whether it is rambling or not)
//
// Virtual processors which are rambling or are executing non-affine work are tracked by the scheduler. When such virtual processors
// exist, notifications will be published whenever affine work or local work becomes available.
//
//
// Look for work in the scheduler. **IN GENERAL**, we want to find work in the following order of precedence:
//
// Phase 1: Affine work
// Affine work in our current group (preferring local affine work)
// Local affine (to us) work (in our starting (home) ring)
// Foreign affine (to us) work (in foreign ring segments that match our affinity mask -- such have a segment in our ring by definition)
//
// - This phase may be skipped if we are executing non-affine work and have not received an affinity notification from the scheduler.
// Doing so prevents SFW from making a full pass per work item if no affinity has been used.
//
// Phase 2: Non-affine work
// Non-Affine work in our current group (preferring local work)
// Local non-affine work (in our starting (home ring))
// Foreign non-affine work (in foreign rings).
//
// - This phase may *NEVER* be skipped.
//
// Phase 3: Affine work (non-local)
// Non-local affine (*NOT* to us) work (in foreign ring segments that do not match our affinity mask
//
// - This phase may be skipped in theory if we know that there is no affine work in the scheduler. For a case with no affinity used within
// any scheduling API, this phase is only hit if the scheduler is completely idle. At idle, performing a pass is likely little additional
// cost to any performance sensitive scenario. At present, this stage is not skipped for this reason.
//
// At a very high level, you can consider a cache local search to be making vertical slices through the search space as follows:
//
// SR SR SR SR
// Contexts | | | |
// Realized | | | |
// Unrealized v v v v
//
// where each entry in this matrix is a piece of a schedule group.
//
// Note that in the last pass of SFW, we cannot skip phases or segments. Doing so can lead us to deadlock in cases with required dependence
// (assumptions on concurrency level).
//
static const SearchAffinity phaseAffinities[] = { SearchAffineLocal, SearchNonAffine, SearchAffineNotMe };
// { 1 , 0 , 2 };
int startPhaseIndex = (!m_pVirtualProcessor->ExecutingAffine() && !m_pVirtualProcessor->CheckAffinityNotification() && !fLastPass) ? 1 : 0;
int maxPhaseIndex = SIZEOF_ARRAY(phaseAffinities) - 1;
// Make sure we only check the vproc's LRC and local node LRCs once. This is done in this manner to ensure the odd placement of this within the
// search algorithm. This variable is set to false at the end of the first pass through the inner loop.
bool fLocalCheck = true;
for(int phaseIndex = startPhaseIndex; !fFound && phaseIndex <= maxPhaseIndex; ++phaseIndex) // This loop goes through the phases in the phaseAffinity array.
{
SearchAffinity srchType = phaseAffinities[phaseIndex];
// At the outset of every pass, we bias to the originating segment/group and the current virtual processors owning ring.
// Note that if we are searching for non-local affinework, we skip the owning ring (by definition, it's local).
ScheduleGroupSegmentBase *pBiasSegment = pOriginSegment;
SchedulingRing *pRing = pOwningRing;
while (pRing != NULL)
{
ScheduleGroupSegmentBase* pQCSegment = m_pScheduler->AcquireQuickCacheSlot(m_maskId);
if (pQCSegment)
{
if (QuickSearch(pQCSegment, pWorkItem, fLastPass, allowableTypes))
{
fFound = true;
break;
}
}
if ((fLocalCheck && (allowableTypes & WorkItem::WorkItemTypeContext) &&
GetLocalRunnable(pWorkItem, m_pVirtualProcessor, false)) ||
((allowableTypes & WorkItem::WorkItemTypeContext) &&
SearchCacheLocal_Runnables(pWorkItem,
pRing,
pBiasSegment,
fLocalCheck,
srchType,
allowableTypes,
fLastPass)) ||
((allowableTypes & WorkItem::WorkItemTypeMaskAnyRealizedChore) &&
SearchCacheLocal_Realized(pWorkItem,
pRing,
pBiasSegment,
!!(allowableTypes & WorkItem::WorkItemTypeRealizedChore),
srchType,
allowableTypes,
fLastPass)) ||
((allowableTypes & WorkItem::WorkItemTypeMaskAnyUnrealizedChore) &&
SearchCacheLocal_Unrealized(pWorkItem,
pRing,
pBiasSegment,
!!(allowableTypes & WorkItem::WorkItemTypeUnrealizedChore),
srchType,
allowableTypes,
fLastPass)) ||
((allowableTypes & WorkItem::WorkItemTypeContext) && phaseIndex == maxPhaseIndex &&
StealLocalRunnable(pWorkItem, pRing->GetOwningNode(), m_pVirtualProcessor)))
{
fFound = true;
break;
}
// Make sure we only steal from vproc LRCs in the local node once. This is done in this manner to ensure the odd placement of this within the
// search algorithm.
fLocalCheck = false;
// The first time through the loop, not only did we bias to pBiasSegment, but to pStartingRing as well. Remove the bias and move to the next ring.
pBiasSegment = NULL;
pRing = m_pScheduler->GetNextSchedulingRing(pOwningRing, pRing);
} // end of while (pRing != NULL) - this loop goes over all rings in the scheduler
} // end of for (!fFound && phase <= maxPhase) - this loop goes through all search affinity types
} // end of if (!fFound)
if (fFound)
{
ScheduleGroupSegmentBase *pSegment = pWorkItem->GetScheduleGroupSegment();
SchedulingRing *pRing = pSegment->GetSchedulingRing();
pSegment->ServiceMark(m_serviceTick);
bool fAffine = false;
if (!pSegment->GetAffinity()._Is_system())
{
location vpLoc = m_pVirtualProcessor->GetLocation();
if (vpLoc._FastVPIntersects(pSegment->GetAffinity()))
fAffine = true;
}
bool fLocal = pRing == pOwningRing;
m_pVirtualProcessor->UpdateWorkState(fAffine, fLocal);
}
return fFound;
}
///
/// Searches for work within the scheduler according to the cache local (schedule group local) search algorithm for yielding.
///
///
/// If a work item is found, the work item will be returned here.
///
///
/// The segment to bias the search to. The need to prefer localized and prioritized work will often trump this bias.
///
///
/// An indication as to whether this is a last pass SFW.
///
///
/// A bitmap of work-types allowed to be returned from the search. This indicates whether or not runnables, realized chores, or unrealized chores
/// can be returned as well as whether the actual work item or only a token should be returned.
///
///
/// An indication of whether a work item was found or not.
///
bool WorkSearchContext::SearchCacheLocalYield(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass, ULONG allowableTypes)
{
bool fFound = false;
if (PreSearch(pWorkItem))
return true;
ASSERT(pOriginSegment);
m_serviceTick = platform::__GetTickCount64();
m_pScheduler->PeriodicScan(m_serviceTick);
//
// @TODO: This is a temporary patch until we have priority and boosts in the scheduler. Right now, search for any segment in the locked
// FIFO of "priority" segments to see if we need to service someone to avoid livelock. We also periodically scan to determine whether
// groups need to go into this list.
//
if (CheckPriorityList(m_serviceTick))
{
if (m_pScheduler->HasPriorityObjects())
{
BoostedObject *pObject = m_pScheduler->GetNextPriorityObject();
while (pObject != NULL)
{
if (pObject->IsScheduleGroupSegment())
{
ScheduleGroupSegmentBase *pSegment = ScheduleGroupSegmentBase::FromBoostEntry(pObject);
OMTRACE(MTRACE_EVT_PRIORITYPULL, pSegment, NULL, m_pVirtualProcessor, 1);
if (QuickSearchYield(pSegment, pWorkItem, fLastPass, allowableTypes))
{
fFound = true;
break;
}
}
else if (allowableTypes & WorkItem::WorkItemTypeContext)
{
VirtualProcessor *pVProc = VirtualProcessor::FromBoostEntry(pObject);
InternalContextBase *pContext = pVProc->StealLocalRunnableContext();
if (pContext != NULL)
{
*pWorkItem = WorkItem(pContext);
fFound = true;
break;
}
}
pObject = m_pScheduler->GetNextPriorityObject();
}
}
//
// If we found something in a priority segment, we need to mark the V-Proc so that it once again looks for affinitized work
// the next time through SFW.
//
m_pVirtualProcessor->MarkGrabbedPriority();
}
SchedulingRing *pOwningRing = m_pVirtualProcessor->GetOwningRing();
if (!fFound)
{
//
// Before describing the cache local search algorithm, some definitions to aid in this are in order:
//
// Work:
// local work -- Work within a node/ring that a given virtual processor belongs to is considered local work
// foreign work -- Work within a node/ring that a given virtual processor does *NOT* belong to is considered foreign work
// affine work -- Work which is placed at a location more specific than the system location is affine work
// non-affine work -- Work which is placed at the system location (or no location) is considered non-affine work
//
// Virtual Processors:
// rambling -- a given virtual processor is *rambling* when it is executing foreign work (regardless whether such work is affine to it or not)
// non-affine -- a given virtual processor is *non-affine* when it is executing non-affine work (regardless whether it is rambling or not)
//
// Virtual processors which are rambling or are executing non-affine work are tracked by the scheduler. When such virtual processors
// exist, notifications will be published whenever affine work or local work
//
//
// **IN GENERAL**, we want to find work in the following order of precedence:
//
// Phase 1: Affine work
// Affine work in our current group (preferring local affine work)
// Local affine (to us) work (in our starting (home) ring)
// Foreign affine (to us) work (in foreign ring segments that match our affinity mask -- such have a segment in our ring by definition)
//
// - This phase may be skipped if we are executing non-affine work and have not received an affinity notification from the scheduler.
// Doing so prevents SFW from making a full pass per work item if no affinity has been used.
//
// Phase 2: Non-affine work
// Non-Affine work in our current group (preferring local work)
// Local non-affine work (in our starting (home ring))
// Foreign non-affine work (in foreign rings).
//
// - This phase may *NEVER* be skipped.
//
// Phase 3: Affine work (non-local)
// Non-local affine (*NOT* to us) work (in foreign ring segments that do not match our affinity mask
//
// - This phase may be skipped in theory if we know that there is no affine work in the scheduler. For a case with no affinity used within
// any scheduling API, this phase is only hit if the scheduler is completely idle. At idle, performing a pass is likely little additional
// cost to any performance sensitive scenario. At present, this stage is not skipped for this reason.
//
// At a very high level, you can consider a cache local search to be making vertical slices through the search space as follows:
//
// SR SR SR SR
// Contexts | | | |
// Realized | | | |
// Unrealized v v v v
//
// where each entry in this matrix is a piece of a schedule group.
//
// Note that in the last pass of SFW, we cannot skip phases or segments. Doing so can lead us to deadlock in cases with required dependence
// (assumptions on concurrency level).
//
static const SearchAffinity phaseAffinities[] = { SearchAffineLocal, SearchNonAffine, SearchAffineNotMe };
// { 1 , 0 , 2 };
int startPhaseIndex = (!m_pVirtualProcessor->ExecutingAffine() && !m_pVirtualProcessor->CheckAffinityNotification() && !fLastPass) ? 1 : 0;
int maxPhaseIndex = SIZEOF_ARRAY(phaseAffinities) - 1;
// @TODO_LOC: We need livelock prevention by doing a priority boost on segments that haven't been serviced in "too long".
bool fLocalCheck = true;
for (int phaseIndex = startPhaseIndex; !fFound && phaseIndex <= maxPhaseIndex; ++phaseIndex) // This loop goes through the phases in the phaseAffinity array.
{
SearchAffinity srchType = phaseAffinities[phaseIndex];
// At the outset of every pass, we bias to the originating segment/group and the current virtual processor's owning ring.
// Note that if we are searching for non-local affine work, we skip the owning ring (by definition, it's local).
ScheduleGroupSegmentBase *pBiasSegment = pOriginSegment;
SchedulingRing *pRing = pOwningRing;
while (pRing != NULL)
{
if (((allowableTypes & WorkItem::WorkItemTypeMaskAnyUnrealizedChore) &&
SearchCacheLocal_Unrealized(pWorkItem,
pRing,
pBiasSegment,
!!(allowableTypes & WorkItem::WorkItemTypeUnrealizedChore),
srchType,
allowableTypes,
fLastPass)) ||
((allowableTypes & WorkItem::WorkItemTypeMaskAnyRealizedChore) &&
SearchCacheLocal_Realized(pWorkItem,
pRing,
pBiasSegment,
!!(allowableTypes & WorkItem::WorkItemTypeRealizedChore),
srchType,
allowableTypes,
fLastPass)) ||
((allowableTypes & WorkItem::WorkItemTypeContext) &&
SearchCacheLocal_Runnables(pWorkItem,
pRing,
pBiasSegment,
fLocalCheck,
srchType,
allowableTypes,
fLastPass)) ||
((allowableTypes & WorkItem::WorkItemTypeContext) && phaseIndex == maxPhaseIndex &&
StealLocalRunnable(pWorkItem, pRing->GetOwningNode(), m_pVirtualProcessor)) ||
(fLocalCheck && (allowableTypes & WorkItem::WorkItemTypeContext) &&
GetLocalRunnable(pWorkItem, m_pVirtualProcessor, true)))
{
fFound = true;
break;
}
// Make sure we only steal from vproc LRCs in the local node once. This is done in this manner to ensure the odd placement of this within the
// search algorithm.
fLocalCheck = false;
// The first time through the loop, not only did we bias to pBiasSegment, but to pStartingRing as well. Remove the bias and move to the next ring.
pBiasSegment = NULL;
pRing = m_pScheduler->GetNextSchedulingRing(pOwningRing, pRing);
} // end of while (pRing != NULL) - this loop goes over all rings in the scheduler
} // end of for (!fFound && phase <= maxPhase) - this loop goes through all search affinity types
} // end of if (!fFound)
if (fFound)
{
ScheduleGroupSegmentBase *pSegment = pWorkItem->GetScheduleGroupSegment();
SchedulingRing *pRing = pSegment->GetSchedulingRing();
pSegment->ServiceMark(m_serviceTick);
bool fAffine = pWorkItem->GetScheduleGroupSegment()->GetAffinity()._Is_system();
bool fLocal = pRing == pOwningRing;
m_pVirtualProcessor->UpdateWorkState(fAffine, fLocal);
}
return fFound;
}
}
}