1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmCTestBinPacker.h"
#include <algorithm>
#include <utility>
bool cmCTestBinPackerAllocation::operator==(
cmCTestBinPackerAllocation const& other) const
{
return this->ProcessIndex == other.ProcessIndex &&
this->SlotsNeeded == other.SlotsNeeded && this->Id == other.Id;
}
bool cmCTestBinPackerAllocation::operator!=(
cmCTestBinPackerAllocation const& other) const
{
return !(*this == other);
}
namespace {
/*
* The following algorithm is used to do two things:
*
* 1) Determine if a test's resource requirements can fit within the resources
* 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>
bool AllocateCTestResources(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string> const& resourcesSorted, std::size_t currentIndex,
std::vector<cmCTestBinPackerAllocation*>& allocations)
{
// Iterate through all large enough resources until we find a solution
std::size_t resourceIndex = 0;
while (resourceIndex < resourcesSorted.size()) {
auto const& resource = resources.at(resourcesSorted[resourceIndex]);
if (resource.Free() >=
static_cast<unsigned int>(allocations[currentIndex]->SlotsNeeded)) {
// Preemptively allocate the resource
allocations[currentIndex]->Id = resourcesSorted[resourceIndex];
if (currentIndex + 1 >= allocations.size()) {
// We have a solution
return true;
}
// Move the resource up the list until it is sorted again
auto resources2 = resources;
auto resourcesSorted2 = resourcesSorted;
resources2[resourcesSorted2[resourceIndex]].Locked +=
allocations[currentIndex]->SlotsNeeded;
AllocationStrategy::IncrementalSort(resources2, resourcesSorted2,
resourceIndex);
// Recurse one level deeper
if (AllocateCTestResources<AllocationStrategy>(
resources2, resourcesSorted2, currentIndex + 1, allocations)) {
return true;
}
}
// No solution found here, deallocate the resource and try the next one
allocations[currentIndex]->Id.clear();
auto freeSlots = resources.at(resourcesSorted.at(resourceIndex)).Free();
do {
++resourceIndex;
} while (resourceIndex < resourcesSorted.size() &&
resources.at(resourcesSorted.at(resourceIndex)).Free() ==
freeSlots);
}
// No solution was found
return false;
}
template <typename AllocationStrategy>
bool AllocateCTestResources(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
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> resourcesSorted;
resourcesSorted.reserve(resources.size());
for (auto const& res : resources) {
resourcesSorted.push_back(res.first);
}
AllocationStrategy::InitialSort(resources, resourcesSorted);
// Do the actual allocation
return AllocateCTestResources<AllocationStrategy>(
resources, resourcesSorted, static_cast<std::size_t>(0), allocationsPtr);
}
class RoundRobinAllocationStrategy
{
public:
static void InitialSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string>& resourcesSorted);
static void IncrementalSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string>& resourcesSorted, std::size_t lastAllocatedIndex);
};
void RoundRobinAllocationStrategy::InitialSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string>& resourcesSorted)
{
std::stable_sort(
resourcesSorted.rbegin(), resourcesSorted.rend(),
[&resources](std::string const& id1, std::string const& id2) {
return resources.at(id1).Free() < resources.at(id2).Free();
});
}
void RoundRobinAllocationStrategy::IncrementalSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string>& resourcesSorted, std::size_t lastAllocatedIndex)
{
auto tmp = resourcesSorted[lastAllocatedIndex];
std::size_t i = lastAllocatedIndex;
while (i < resourcesSorted.size() - 1 &&
resources.at(resourcesSorted[i + 1]).Free() >
resources.at(tmp).Free()) {
resourcesSorted[i] = resourcesSorted[i + 1];
++i;
}
resourcesSorted[i] = tmp;
}
class BlockAllocationStrategy
{
public:
static void InitialSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string>& resourcesSorted);
static void IncrementalSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string>& resourcesSorted, std::size_t lastAllocatedIndex);
};
void BlockAllocationStrategy::InitialSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<std::string>& resourcesSorted)
{
std::stable_sort(
resourcesSorted.rbegin(), resourcesSorted.rend(),
[&resources](std::string const& id1, std::string const& id2) {
return resources.at(id1).Free() < resources.at(id2).Free();
});
}
void BlockAllocationStrategy::IncrementalSort(
std::map<std::string, cmCTestResourceAllocator::Resource> const&,
std::vector<std::string>& resourcesSorted, std::size_t lastAllocatedIndex)
{
auto tmp = resourcesSorted[lastAllocatedIndex];
std::size_t i = lastAllocatedIndex;
while (i > 0) {
resourcesSorted[i] = resourcesSorted[i - 1];
--i;
}
resourcesSorted[i] = tmp;
}
}
bool cmAllocateCTestResourcesRoundRobin(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<cmCTestBinPackerAllocation>& allocations)
{
return AllocateCTestResources<RoundRobinAllocationStrategy>(resources,
allocations);
}
bool cmAllocateCTestResourcesBlock(
std::map<std::string, cmCTestResourceAllocator::Resource> const& resources,
std::vector<cmCTestBinPackerAllocation>& allocations)
{
return AllocateCTestResources<BlockAllocationStrategy>(resources,
allocations);
}
|