mirror of
https://github.com/Kitware/CMake.git
synced 2026-02-09 08:40:11 -06:00
This algorithm is used to determine whether or not a test can execute with the available resources. It uses a recursive largest- first algorithm to try to place the tests into their respective slots.
202 lines
6.9 KiB
C++
202 lines
6.9 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmCTestBinPacker.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
bool cmCTestBinPackerAllocation::operator==(
|
|
const cmCTestBinPackerAllocation& other) const
|
|
{
|
|
return this->ProcessIndex == other.ProcessIndex &&
|
|
this->SlotsNeeded == other.SlotsNeeded && this->Id == other.Id;
|
|
}
|
|
|
|
bool cmCTestBinPackerAllocation::operator!=(
|
|
const cmCTestBinPackerAllocation& other) const
|
|
{
|
|
return !(*this == other);
|
|
}
|
|
|
|
namespace {
|
|
|
|
/*
|
|
* The following algorithm is used to do two things:
|
|
*
|
|
* 1) Determine if a test's hardware requirements can fit within the hardware
|
|
* present on the system, and
|
|
* 2) Do the actual allocation
|
|
*
|
|
* This algorithm performs a recursive search, looking for a bin pack that will
|
|
* fit the specified requirements. It has a template to specify different
|
|
* optimization strategies. If it ever runs out of room, it backtracks as far
|
|
* down the stack as it needs to and tries a different combination until no
|
|
* more combinations can be tried.
|
|
*/
|
|
template <typename AllocationStrategy>
|
|
static bool AllocateCTestHardware(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
const std::vector<std::string>& hardwareSorted, std::size_t currentIndex,
|
|
std::vector<cmCTestBinPackerAllocation*>& allocations)
|
|
{
|
|
// Iterate through all large enough resources until we find a solution
|
|
std::size_t hardwareIndex = 0;
|
|
while (hardwareIndex < hardwareSorted.size()) {
|
|
auto const& resource = hardware.at(hardwareSorted[hardwareIndex]);
|
|
if (resource.Free() >=
|
|
static_cast<unsigned int>(allocations[currentIndex]->SlotsNeeded)) {
|
|
// Preemptively allocate the resource
|
|
allocations[currentIndex]->Id = hardwareSorted[hardwareIndex];
|
|
if (currentIndex + 1 >= allocations.size()) {
|
|
// We have a solution
|
|
return true;
|
|
}
|
|
|
|
// Move the resource up the list until it is sorted again
|
|
auto hardware2 = hardware;
|
|
auto hardwareSorted2 = hardwareSorted;
|
|
hardware2[hardwareSorted2[hardwareIndex]].Locked +=
|
|
allocations[currentIndex]->SlotsNeeded;
|
|
AllocationStrategy::IncrementalSort(hardware2, hardwareSorted2,
|
|
hardwareIndex);
|
|
|
|
// Recurse one level deeper
|
|
if (AllocateCTestHardware<AllocationStrategy>(
|
|
hardware2, hardwareSorted2, currentIndex + 1, allocations)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// No solution found here, deallocate the resource and try the next one
|
|
allocations[currentIndex]->Id.clear();
|
|
auto freeSlots = hardware.at(hardwareSorted.at(hardwareIndex)).Free();
|
|
do {
|
|
++hardwareIndex;
|
|
} while (hardwareIndex < hardwareSorted.size() &&
|
|
hardware.at(hardwareSorted.at(hardwareIndex)).Free() ==
|
|
freeSlots);
|
|
}
|
|
|
|
// No solution was found
|
|
return false;
|
|
}
|
|
|
|
template <typename AllocationStrategy>
|
|
static bool AllocateCTestHardware(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<cmCTestBinPackerAllocation>& allocations)
|
|
{
|
|
// Sort the resource requirements in descending order by slots needed
|
|
std::vector<cmCTestBinPackerAllocation*> allocationsPtr;
|
|
allocationsPtr.reserve(allocations.size());
|
|
for (auto& allocation : allocations) {
|
|
allocationsPtr.push_back(&allocation);
|
|
}
|
|
std::stable_sort(
|
|
allocationsPtr.rbegin(), allocationsPtr.rend(),
|
|
[](cmCTestBinPackerAllocation* a1, cmCTestBinPackerAllocation* a2) {
|
|
return a1->SlotsNeeded < a2->SlotsNeeded;
|
|
});
|
|
|
|
// Sort the resources according to sort strategy
|
|
std::vector<std::string> hardwareSorted;
|
|
hardwareSorted.reserve(hardware.size());
|
|
for (auto const& hw : hardware) {
|
|
hardwareSorted.push_back(hw.first);
|
|
}
|
|
AllocationStrategy::InitialSort(hardware, hardwareSorted);
|
|
|
|
// Do the actual allocation
|
|
return AllocateCTestHardware<AllocationStrategy>(
|
|
hardware, hardwareSorted, std::size_t(0), allocationsPtr);
|
|
}
|
|
|
|
class RoundRobinAllocationStrategy
|
|
{
|
|
public:
|
|
static void InitialSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<std::string>& hardwareSorted);
|
|
|
|
static void IncrementalSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex);
|
|
};
|
|
|
|
void RoundRobinAllocationStrategy::InitialSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<std::string>& hardwareSorted)
|
|
{
|
|
std::stable_sort(
|
|
hardwareSorted.rbegin(), hardwareSorted.rend(),
|
|
[&hardware](const std::string& id1, const std::string& id2) {
|
|
return hardware.at(id1).Free() < hardware.at(id2).Free();
|
|
});
|
|
}
|
|
|
|
void RoundRobinAllocationStrategy::IncrementalSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex)
|
|
{
|
|
auto tmp = hardwareSorted[lastAllocatedIndex];
|
|
std::size_t i = lastAllocatedIndex;
|
|
while (i < hardwareSorted.size() - 1 &&
|
|
hardware.at(hardwareSorted[i + 1]).Free() > hardware.at(tmp).Free()) {
|
|
hardwareSorted[i] = hardwareSorted[i + 1];
|
|
++i;
|
|
}
|
|
hardwareSorted[i] = tmp;
|
|
}
|
|
|
|
class BlockAllocationStrategy
|
|
{
|
|
public:
|
|
static void InitialSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<std::string>& hardwareSorted);
|
|
|
|
static void IncrementalSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex);
|
|
};
|
|
|
|
void BlockAllocationStrategy::InitialSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<std::string>& hardwareSorted)
|
|
{
|
|
std::stable_sort(
|
|
hardwareSorted.rbegin(), hardwareSorted.rend(),
|
|
[&hardware](const std::string& id1, const std::string& id2) {
|
|
return hardware.at(id1).Free() < hardware.at(id2).Free();
|
|
});
|
|
}
|
|
|
|
void BlockAllocationStrategy::IncrementalSort(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>&,
|
|
std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex)
|
|
{
|
|
auto tmp = hardwareSorted[lastAllocatedIndex];
|
|
std::size_t i = lastAllocatedIndex;
|
|
while (i > 0) {
|
|
hardwareSorted[i] = hardwareSorted[i - 1];
|
|
--i;
|
|
}
|
|
hardwareSorted[i] = tmp;
|
|
}
|
|
}
|
|
|
|
bool cmAllocateCTestHardwareRoundRobin(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<cmCTestBinPackerAllocation>& allocations)
|
|
{
|
|
return AllocateCTestHardware<RoundRobinAllocationStrategy>(hardware,
|
|
allocations);
|
|
}
|
|
|
|
bool cmAllocateCTestHardwareBlock(
|
|
const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
|
|
std::vector<cmCTestBinPackerAllocation>& allocations)
|
|
{
|
|
return AllocateCTestHardware<BlockAllocationStrategy>(hardware, allocations);
|
|
}
|