1 /****************************************************************************
2 * Copyright (C) 2014-2015 Intel Corporation. All Rights Reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 ****************************************************************************/
33 #if defined(__linux__) || defined(__gnu_linux__)
39 #include "common/os.h"
43 #include "rasterizer.h"
44 #include "rdtsc_core.h"
53 uint32_t procGroup
= 0;
54 std::vector
<uint32_t> threadIds
;
59 std::vector
<Core
> cores
;
62 typedef std::vector
<NumaNode
> CPUNumaNodes
;
64 void CalculateProcessorTopology(CPUNumaNodes
& out_nodes
, uint32_t& out_numThreadsPerProcGroup
)
67 out_numThreadsPerProcGroup
= 0;
72 std::lock_guard
<std::mutex
> l(m
);
74 static SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer
[KNOB_MAX_NUM_THREADS
];
75 DWORD bufSize
= sizeof(buffer
);
77 BOOL ret
= GetLogicalProcessorInformationEx(RelationProcessorCore
, buffer
, &bufSize
);
78 SWR_ASSERT(ret
!= FALSE
, "Failed to get Processor Topology Information");
80 uint32_t count
= bufSize
/ buffer
->Size
;
81 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pBuffer
= buffer
;
83 for (uint32_t i
= 0; i
< count
; ++i
)
85 SWR_ASSERT(pBuffer
->Relationship
== RelationProcessorCore
);
86 for (uint32_t g
= 0; g
< pBuffer
->Processor
.GroupCount
; ++g
)
88 auto& gmask
= pBuffer
->Processor
.GroupMask
[g
];
89 uint32_t threadId
= 0;
90 uint32_t procGroup
= gmask
.Group
;
92 Core
* pCore
= nullptr;
94 uint32_t numThreads
= (uint32_t)_mm_popcount_sizeT(gmask
.Mask
);
96 while (BitScanForwardSizeT((unsigned long*)&threadId
, gmask
.Mask
))
99 gmask
.Mask
&= ~(KAFFINITY(1) << threadId
);
102 PROCESSOR_NUMBER procNum
= {};
103 procNum
.Group
= WORD(procGroup
);
104 procNum
.Number
= UCHAR(threadId
);
107 ret
= GetNumaProcessorNodeEx(&procNum
, (PUSHORT
)&numaId
);
111 if (out_nodes
.size() <= numaId
) out_nodes
.resize(numaId
+ 1);
112 auto& numaNode
= out_nodes
[numaId
];
116 if (nullptr == pCore
)
118 numaNode
.cores
.push_back(Core());
119 pCore
= &numaNode
.cores
.back();
120 pCore
->procGroup
= procGroup
;
122 coreId
= (uint32_t)numaNode
.cores
.size();
123 if ((coreId
* numThreads
) >= 32)
125 // Windows doesn't return threadIds >= 32 for a processor group correctly
126 // when running a 32-bit application.
127 // Just save -1 as the threadId
128 threadId
= uint32_t(-1);
132 pCore
->threadIds
.push_back(threadId
);
135 out_numThreadsPerProcGroup
++;
139 pBuffer
= PtrAdd(pBuffer
, pBuffer
->Size
);
143 #elif defined(__linux__) || defined (__gnu_linux__)
145 // Parse /proc/cpuinfo to get full topology
146 std::ifstream
input("/proc/cpuinfo");
149 uint32_t threadId
= uint32_t(-1);
150 uint32_t coreId
= uint32_t(-1);
151 uint32_t numaId
= uint32_t(-1);
153 while (std::getline(input
, line
))
155 if (line
.find("processor") != std::string::npos
)
157 if (threadId
!= uint32_t(-1))
160 if (out_nodes
.size() <= numaId
) out_nodes
.resize(numaId
+ 1);
161 auto& numaNode
= out_nodes
[numaId
];
162 if (numaNode
.cores
.size() <= coreId
) numaNode
.cores
.resize(coreId
+ 1);
163 auto& core
= numaNode
.cores
[coreId
];
165 core
.procGroup
= coreId
;
166 core
.threadIds
.push_back(threadId
);
168 out_numThreadsPerProcGroup
++;
171 auto data_start
= line
.find(": ") + 2;
172 threadId
= std::strtoul(&line
.c_str()[data_start
], &c
, 10);
175 if (line
.find("core id") != std::string::npos
)
177 auto data_start
= line
.find(": ") + 2;
178 coreId
= std::strtoul(&line
.c_str()[data_start
], &c
, 10);
181 if (line
.find("physical id") != std::string::npos
)
183 auto data_start
= line
.find(": ") + 2;
184 numaId
= std::strtoul(&line
.c_str()[data_start
], &c
, 10);
189 if (threadId
!= uint32_t(-1))
192 if (out_nodes
.size() <= numaId
) out_nodes
.resize(numaId
+ 1);
193 auto& numaNode
= out_nodes
[numaId
];
194 if (numaNode
.cores
.size() <= coreId
) numaNode
.cores
.resize(coreId
+ 1);
195 auto& core
= numaNode
.cores
[coreId
];
197 core
.procGroup
= coreId
;
198 core
.threadIds
.push_back(threadId
);
199 out_numThreadsPerProcGroup
++;
202 for (uint32_t node
= 0; node
< out_nodes
.size(); node
++) {
203 auto& numaNode
= out_nodes
[node
];
204 auto it
= numaNode
.cores
.begin();
205 for ( ; it
!= numaNode
.cores
.end(); ) {
206 if (it
->threadIds
.size() == 0)
207 numaNode
.cores
.erase(it
);
215 #error Unsupported platform
221 void bindThread(uint32_t threadId
, uint32_t procGroupId
= 0, bool bindProcGroup
=false)
223 // Only bind threads when MAX_WORKER_THREADS isn't set.
224 if (KNOB_MAX_WORKER_THREADS
&& bindProcGroup
== false)
231 GROUP_AFFINITY affinity
= {};
232 affinity
.Group
= procGroupId
;
237 // In a 32-bit process on Windows it is impossible to bind
238 // to logical processors 32-63 within a processor group.
239 // In this case set the mask to 0 and let the system assign
240 // the processor. Hopefully it will make smart choices.
246 // If KNOB_MAX_WORKER_THREADS is set, only bind to the proc group,
247 // Not the individual HW thread.
248 if (!KNOB_MAX_WORKER_THREADS
)
250 affinity
.Mask
= KAFFINITY(1) << threadId
;
254 SetThreadGroupAffinity(GetCurrentThread(), &affinity
, nullptr);
258 pthread_t thread
= pthread_self();
260 CPU_SET(threadId
, &cpuset
);
262 pthread_setaffinity_np(thread
, sizeof(cpu_set_t
), &cpuset
);
267 uint64_t GetEnqueuedDraw(SWR_CONTEXT
*pContext
)
269 return pContext
->dcRing
.GetHead();
273 DRAW_CONTEXT
*GetDC(SWR_CONTEXT
*pContext
, uint64_t drawId
)
275 return &pContext
->dcRing
[(drawId
-1) % KNOB_MAX_DRAWS_IN_FLIGHT
];
278 // returns true if dependency not met
280 bool CheckDependency(SWR_CONTEXT
*pContext
, DRAW_CONTEXT
*pDC
, uint64_t lastRetiredDraw
)
282 return (pDC
->dependency
> lastRetiredDraw
);
285 INLINE
int64_t CompleteDrawContext(SWR_CONTEXT
* pContext
, DRAW_CONTEXT
* pDC
)
287 int64_t result
= InterlockedDecrement64(&pDC
->threadsDone
);
288 SWR_ASSERT(result
>= 0);
292 // Cleanup memory allocations
293 pDC
->pArena
->Reset(true);
296 pDC
->pTileMgr
->initialize();
298 if (pDC
->cleanupState
)
300 pDC
->pState
->pArena
->Reset(true);
305 pContext
->dcRing
.Dequeue(); // Remove from tail
311 INLINE
bool FindFirstIncompleteDraw(SWR_CONTEXT
* pContext
, uint64_t& curDrawBE
, uint64_t& drawEnqueued
)
313 // increment our current draw id to the first incomplete draw
314 drawEnqueued
= GetEnqueuedDraw(pContext
);
315 while (curDrawBE
< drawEnqueued
)
317 DRAW_CONTEXT
*pDC
= &pContext
->dcRing
[curDrawBE
% KNOB_MAX_DRAWS_IN_FLIGHT
];
319 // If its not compute and FE is not done then break out of loop.
320 if (!pDC
->doneFE
&& !pDC
->isCompute
) break;
322 bool isWorkComplete
= pDC
->isCompute
?
323 pDC
->pDispatch
->isWorkComplete() :
324 pDC
->pTileMgr
->isWorkComplete();
329 CompleteDrawContext(pContext
, pDC
);
337 // If there are no more incomplete draws then return false.
338 return (curDrawBE
>= drawEnqueued
) ? false : true;
341 //////////////////////////////////////////////////////////////////////////
342 /// @brief If there is any BE work then go work on it.
343 /// @param pContext - pointer to SWR context.
344 /// @param workerId - The unique worker ID that is assigned to this thread.
345 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker thread
346 /// has its own curDrawBE counter and this ensures that each worker processes all the
348 /// @param lockedTiles - This is the set of tiles locked by other threads. Each thread maintains its
349 /// own set and each time it fails to lock a macrotile, because its already locked,
350 /// then it will add that tile to the lockedTiles set. As a worker begins to work
351 /// on future draws the lockedTiles ensure that it doesn't work on tiles that may
352 /// still have work pending in a previous draw. Additionally, the lockedTiles is
353 /// hueristic that can steer a worker back to the same macrotile that it had been
354 /// working on in a previous draw.
356 SWR_CONTEXT
*pContext
,
359 TileSet
& lockedTiles
,
363 // Find the first incomplete draw that has pending work. If no such draw is found then
364 // return. FindFirstIncompleteDraw is responsible for incrementing the curDrawBE.
365 uint64_t drawEnqueued
= 0;
366 if (FindFirstIncompleteDraw(pContext
, curDrawBE
, drawEnqueued
) == false)
371 uint64_t lastRetiredDraw
= pContext
->dcRing
[curDrawBE
% KNOB_MAX_DRAWS_IN_FLIGHT
].drawId
- 1;
373 // Reset our history for locked tiles. We'll have to re-learn which tiles are locked.
376 // Try to work on each draw in order of the available draws in flight.
377 // 1. If we're on curDrawBE, we can work on any macrotile that is available.
378 // 2. If we're trying to work on draws after curDrawBE, we are restricted to
379 // working on those macrotiles that are known to be complete in the prior draw to
380 // maintain order. The locked tiles provides the history to ensures this.
381 for (uint64_t i
= curDrawBE
; i
< drawEnqueued
; ++i
)
383 DRAW_CONTEXT
*pDC
= &pContext
->dcRing
[i
% KNOB_MAX_DRAWS_IN_FLIGHT
];
385 if (pDC
->isCompute
) return; // We don't look at compute work.
387 // First wait for FE to be finished with this draw. This keeps threading model simple
388 // but if there are lots of bubbles between draws then serializing FE and BE may
389 // need to be revisited.
390 if (!pDC
->doneFE
) return;
392 // If this draw is dependent on a previous draw then we need to bail.
393 if (CheckDependency(pContext
, pDC
, lastRetiredDraw
))
398 // Grab the list of all dirty macrotiles. A tile is dirty if it has work queued to it.
399 std::vector
<uint32_t> ¯oTiles
= pDC
->pTileMgr
->getDirtyTiles();
401 for (uint32_t tileID
: macroTiles
)
403 // Only work on tiles for for this numa node
405 pDC
->pTileMgr
->getTileIndices(tileID
, x
, y
);
406 if (((x
^ y
) & numaMask
) != numaNode
)
411 MacroTileQueue
&tile
= pDC
->pTileMgr
->getMacroTileQueue(tileID
);
413 if (!tile
.getNumQueued())
418 // can only work on this draw if it's not in use by other threads
419 if (lockedTiles
.find(tileID
) != lockedTiles
.end())
428 RDTSC_START(WorkerFoundWork
);
430 uint32_t numWorkItems
= tile
.getNumQueued();
431 SWR_ASSERT(numWorkItems
);
435 if (pWork
->type
== DRAW
)
437 pContext
->pHotTileMgr
->InitializeHotTiles(pContext
, pDC
, tileID
);
440 while ((pWork
= tile
.peek()) != nullptr)
442 pWork
->pfnWork(pDC
, workerId
, tileID
, &pWork
->desc
);
445 RDTSC_STOP(WorkerFoundWork
, numWorkItems
, pDC
->drawId
);
449 pDC
->pTileMgr
->markTileComplete(tileID
);
451 // Optimization: If the draw is complete and we're the last one to have worked on it then
452 // we can reset the locked list as we know that all previous draws before the next are guaranteed to be complete.
453 if ((curDrawBE
== i
) && pDC
->pTileMgr
->isWorkComplete())
455 // We can increment the current BE and safely move to next draw since we know this draw is complete.
457 CompleteDrawContext(pContext
, pDC
);
467 // This tile is already locked. So let's add it to our locked tiles set. This way we don't try locking this one again.
468 lockedTiles
.insert(tileID
);
474 void WorkOnFifoFE(SWR_CONTEXT
*pContext
, uint32_t workerId
, uint64_t &curDrawFE
)
476 // Try to grab the next DC from the ring
477 uint64_t drawEnqueued
= GetEnqueuedDraw(pContext
);
478 while (curDrawFE
< drawEnqueued
)
480 uint32_t dcSlot
= curDrawFE
% KNOB_MAX_DRAWS_IN_FLIGHT
;
481 DRAW_CONTEXT
*pDC
= &pContext
->dcRing
[dcSlot
];
482 if (pDC
->isCompute
|| pDC
->doneFE
|| pDC
->FeLock
)
484 CompleteDrawContext(pContext
, pDC
);
493 uint64_t curDraw
= curDrawFE
;
494 while (curDraw
< drawEnqueued
)
496 uint32_t dcSlot
= curDraw
% KNOB_MAX_DRAWS_IN_FLIGHT
;
497 DRAW_CONTEXT
*pDC
= &pContext
->dcRing
[dcSlot
];
499 if (!pDC
->isCompute
&& !pDC
->FeLock
)
501 uint32_t initial
= InterlockedCompareExchange((volatile uint32_t*)&pDC
->FeLock
, 1, 0);
504 // successfully grabbed the DC, now run the FE
505 pDC
->FeWork
.pfnWork(pContext
, pDC
, workerId
, &pDC
->FeWork
.desc
);
515 //////////////////////////////////////////////////////////////////////////
516 /// @brief If there is any compute work then go work on it.
517 /// @param pContext - pointer to SWR context.
518 /// @param workerId - The unique worker ID that is assigned to this thread.
519 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker thread
520 /// has its own curDrawBE counter and this ensures that each worker processes all the
523 SWR_CONTEXT
*pContext
,
527 uint64_t drawEnqueued
= 0;
528 if (FindFirstIncompleteDraw(pContext
, curDrawBE
, drawEnqueued
) == false)
533 uint64_t lastRetiredDraw
= pContext
->dcRing
[curDrawBE
% KNOB_MAX_DRAWS_IN_FLIGHT
].drawId
- 1;
535 for (uint64_t i
= curDrawBE
; curDrawBE
< drawEnqueued
; ++i
)
537 DRAW_CONTEXT
*pDC
= &pContext
->dcRing
[i
% KNOB_MAX_DRAWS_IN_FLIGHT
];
538 if (pDC
->isCompute
== false) return;
540 // check dependencies
541 if (CheckDependency(pContext
, pDC
, lastRetiredDraw
))
546 SWR_ASSERT(pDC
->pDispatch
!= nullptr);
547 DispatchQueue
& queue
= *pDC
->pDispatch
;
549 // Is there any work remaining?
550 if (queue
.getNumQueued() > 0)
552 void* pSpillFillBuffer
= nullptr;
553 uint32_t threadGroupId
= 0;
554 while (queue
.getWork(threadGroupId
))
556 ProcessComputeBE(pDC
, workerId
, threadGroupId
, pSpillFillBuffer
);
558 queue
.finishedWork();
564 template<bool IsFEThread
, bool IsBEThread
>
565 DWORD
workerThreadMain(LPVOID pData
)
567 THREAD_DATA
*pThreadData
= (THREAD_DATA
*)pData
;
568 SWR_CONTEXT
*pContext
= pThreadData
->pContext
;
569 uint32_t threadId
= pThreadData
->threadId
;
570 uint32_t workerId
= pThreadData
->workerId
;
572 bindThread(threadId
, pThreadData
->procGroupId
, pThreadData
->forceBindProcGroup
);
574 RDTSC_INIT(threadId
);
576 uint32_t numaNode
= pThreadData
->numaId
;
577 uint32_t numaMask
= pContext
->threadPool
.numaMask
;
579 // flush denormals to 0
580 _mm_setcsr(_mm_getcsr() | _MM_FLUSH_ZERO_ON
| _MM_DENORMALS_ZERO_ON
);
582 // Track tiles locked by other threads. If we try to lock a macrotile and find its already
583 // locked then we'll add it to this list so that we don't try and lock it again.
586 // each worker has the ability to work on any of the queued draws as long as certain
587 // conditions are met. the data associated
588 // with a draw is guaranteed to be active as long as a worker hasn't signaled that he
589 // has moved on to the next draw when he determines there is no more work to do. The api
590 // thread will not increment the head of the dc ring until all workers have moved past the
592 // the logic to determine what to work on is:
593 // 1- try to work on the FE any draw that is queued. For now there are no dependencies
594 // on the FE work, so any worker can grab any FE and process in parallel. Eventually
595 // we'll need dependency tracking to force serialization on FEs. The worker will try
596 // to pick an FE by atomically incrementing a counter in the swr context. he'll keep
597 // trying until he reaches the tail.
598 // 2- BE work must be done in strict order. we accomplish this today by pulling work off
599 // the oldest draw (ie the head) of the dcRing. the worker can determine if there is
600 // any work left by comparing the total # of binned work items and the total # of completed
601 // work items. If they are equal, then there is no more work to do for this draw, and
602 // the worker can safely increment its oldestDraw counter and move on to the next draw.
603 std::unique_lock
<std::mutex
> lock(pContext
->WaitLock
, std::defer_lock
);
605 auto threadHasWork
= [&](uint64_t curDraw
) { return curDraw
!= pContext
->dcRing
.GetHead(); };
607 uint64_t curDrawBE
= 0;
608 uint64_t curDrawFE
= 0;
610 while (pContext
->threadPool
.inThreadShutdown
== false)
613 while (loop
++ < KNOB_WORKER_SPIN_LOOP_COUNT
&& !threadHasWork(curDrawBE
))
618 if (!threadHasWork(curDrawBE
))
622 // check for thread idle condition again under lock
623 if (threadHasWork(curDrawBE
))
629 if (pContext
->threadPool
.inThreadShutdown
)
635 RDTSC_START(WorkerWaitForThreadEvent
);
637 pContext
->FifosNotEmpty
.wait(lock
);
640 RDTSC_STOP(WorkerWaitForThreadEvent
, 0, 0);
642 if (pContext
->threadPool
.inThreadShutdown
)
650 RDTSC_START(WorkerWorkOnFifoBE
);
651 WorkOnFifoBE(pContext
, workerId
, curDrawBE
, lockedTiles
, numaNode
, numaMask
);
652 RDTSC_STOP(WorkerWorkOnFifoBE
, 0, 0);
654 WorkOnCompute(pContext
, workerId
, curDrawBE
);
659 WorkOnFifoFE(pContext
, workerId
, curDrawFE
);
663 curDrawBE
= curDrawFE
;
670 template<> DWORD workerThreadMain
<false, false>(LPVOID
) = delete;
672 template <bool IsFEThread
, bool IsBEThread
>
673 DWORD
workerThreadInit(LPVOID pData
)
679 return workerThreadMain
<IsFEThread
, IsBEThread
>(pData
);
683 __except(EXCEPTION_CONTINUE_SEARCH
)
691 template<> DWORD workerThreadInit
<false, false>(LPVOID pData
) = delete;
693 void CreateThreadPool(SWR_CONTEXT
*pContext
, THREAD_POOL
*pPool
)
698 uint32_t numThreadsPerProcGroup
= 0;
699 CalculateProcessorTopology(nodes
, numThreadsPerProcGroup
);
701 uint32_t numHWNodes
= (uint32_t)nodes
.size();
702 uint32_t numHWCoresPerNode
= (uint32_t)nodes
[0].cores
.size();
703 uint32_t numHWHyperThreads
= (uint32_t)nodes
[0].cores
[0].threadIds
.size();
705 uint32_t numNodes
= numHWNodes
;
706 uint32_t numCoresPerNode
= numHWCoresPerNode
;
707 uint32_t numHyperThreads
= numHWHyperThreads
;
709 if (KNOB_MAX_WORKER_THREADS
)
711 SET_KNOB(HYPERTHREADED_FE
, false);
714 if (KNOB_HYPERTHREADED_FE
)
716 SET_KNOB(MAX_THREADS_PER_CORE
, 0);
719 if (KNOB_MAX_NUMA_NODES
)
721 numNodes
= std::min(numNodes
, KNOB_MAX_NUMA_NODES
);
724 if (KNOB_MAX_CORES_PER_NUMA_NODE
)
726 numCoresPerNode
= std::min(numCoresPerNode
, KNOB_MAX_CORES_PER_NUMA_NODE
);
729 if (KNOB_MAX_THREADS_PER_CORE
)
731 numHyperThreads
= std::min(numHyperThreads
, KNOB_MAX_THREADS_PER_CORE
);
734 if (numHyperThreads
< 2)
736 SET_KNOB(HYPERTHREADED_FE
, false);
739 // Calculate numThreads
740 uint32_t numThreads
= numNodes
* numCoresPerNode
* numHyperThreads
;
742 if (KNOB_MAX_WORKER_THREADS
)
744 uint32_t maxHWThreads
= numHWNodes
* numHWCoresPerNode
* numHWHyperThreads
;
745 numThreads
= std::min(KNOB_MAX_WORKER_THREADS
, maxHWThreads
);
748 if (numThreads
> KNOB_MAX_NUM_THREADS
)
750 printf("WARNING: system thread count %u exceeds max %u, "
751 "performance will be degraded\n",
752 numThreads
, KNOB_MAX_NUM_THREADS
);
755 uint32_t numAPIReservedThreads
= 1;
760 // If only 1 worker threads, try to move it to an available
761 // HW thread. If that fails, use the API thread.
762 if (numCoresPerNode
< numHWCoresPerNode
)
766 else if (numHyperThreads
< numHWHyperThreads
)
770 else if (numNodes
< numHWNodes
)
776 pPool
->numThreads
= 0;
777 SET_KNOB(SINGLE_THREADED
, true);
783 // Save HW threads for the API if we can
784 if (numThreads
> numAPIReservedThreads
)
786 numThreads
-= numAPIReservedThreads
;
790 numAPIReservedThreads
= 0;
794 pPool
->numThreads
= numThreads
;
795 pContext
->NumWorkerThreads
= pPool
->numThreads
;
797 pPool
->inThreadShutdown
= false;
798 pPool
->pThreadData
= (THREAD_DATA
*)malloc(pPool
->numThreads
* sizeof(THREAD_DATA
));
801 if (KNOB_MAX_WORKER_THREADS
)
803 bool bForceBindProcGroup
= (numThreads
> numThreadsPerProcGroup
);
804 uint32_t numProcGroups
= (numThreads
+ numThreadsPerProcGroup
- 1) / numThreadsPerProcGroup
;
805 // When MAX_WORKER_THREADS is set we don't bother to bind to specific HW threads
806 // But Windows will still require binding to specific process groups
807 for (uint32_t workerId
= 0; workerId
< numThreads
; ++workerId
)
809 pPool
->pThreadData
[workerId
].workerId
= workerId
;
810 pPool
->pThreadData
[workerId
].procGroupId
= workerId
% numProcGroups
;
811 pPool
->pThreadData
[workerId
].threadId
= 0;
812 pPool
->pThreadData
[workerId
].numaId
= 0;
813 pPool
->pThreadData
[workerId
].coreId
= 0;
814 pPool
->pThreadData
[workerId
].htId
= 0;
815 pPool
->pThreadData
[workerId
].pContext
= pContext
;
816 pPool
->pThreadData
[workerId
].forceBindProcGroup
= bForceBindProcGroup
;
817 pPool
->threads
[workerId
] = new std::thread(workerThreadInit
<true, true>, &pPool
->pThreadData
[workerId
]);
819 pContext
->NumBEThreads
++;
820 pContext
->NumFEThreads
++;
825 pPool
->numaMask
= numNodes
- 1; // Only works for 2**n numa nodes (1, 2, 4, etc.)
827 uint32_t workerId
= 0;
828 for (uint32_t n
= 0; n
< numNodes
; ++n
)
830 auto& node
= nodes
[n
];
831 if (node
.cores
.size() == 0)
836 uint32_t numCores
= numCoresPerNode
;
837 for (uint32_t c
= 0; c
< numCores
; ++c
)
839 auto& core
= node
.cores
[c
];
840 for (uint32_t t
= 0; t
< numHyperThreads
; ++t
)
842 if (numAPIReservedThreads
)
844 --numAPIReservedThreads
;
848 pPool
->pThreadData
[workerId
].workerId
= workerId
;
849 pPool
->pThreadData
[workerId
].procGroupId
= core
.procGroup
;
850 pPool
->pThreadData
[workerId
].threadId
= core
.threadIds
[t
];
851 pPool
->pThreadData
[workerId
].numaId
= n
;
852 pPool
->pThreadData
[workerId
].coreId
= c
;
853 pPool
->pThreadData
[workerId
].htId
= t
;
854 pPool
->pThreadData
[workerId
].pContext
= pContext
;
856 if (KNOB_HYPERTHREADED_FE
)
860 pContext
->NumBEThreads
++;
861 pPool
->threads
[workerId
] = new std::thread(workerThreadInit
<false, true>, &pPool
->pThreadData
[workerId
]);
865 pContext
->NumFEThreads
++;
866 pPool
->threads
[workerId
] = new std::thread(workerThreadInit
<true, false>, &pPool
->pThreadData
[workerId
]);
871 pPool
->threads
[workerId
] = new std::thread(workerThreadInit
<true, true>, &pPool
->pThreadData
[workerId
]);
872 pContext
->NumBEThreads
++;
873 pContext
->NumFEThreads
++;
883 void DestroyThreadPool(SWR_CONTEXT
*pContext
, THREAD_POOL
*pPool
)
885 if (!KNOB_SINGLE_THREADED
)
887 // Inform threads to finish up
888 std::unique_lock
<std::mutex
> lock(pContext
->WaitLock
);
889 pPool
->inThreadShutdown
= true;
891 pContext
->FifosNotEmpty
.notify_all();
894 // Wait for threads to finish and destroy them
895 for (uint32_t t
= 0; t
< pPool
->numThreads
; ++t
)
897 pPool
->threads
[t
]->join();
898 delete(pPool
->threads
[t
]);
901 // Clean up data used by threads
902 free(pPool
->pThreadData
);