blob: 2a10466c8e9b9a3e581b837d27cf781a3d808686 [file] [log] [blame]
/*
* Copyright (c) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "pas_lock.h"
#include "pas_status_reporter.h"
#include "pas_utils.h"
#include "iso_heap.h"
#include "iso_heap_config.h"
#include <cstdint>
#include <iostream>
#include <mutex>
#include <random>
#include <sstream>
#include <thread>
#include <utility>
#include <vector>
using namespace std;
namespace {
// Each of the following arguments can be set using -argumentName. For example, you can do:
//
// ./Chaos -seed 42 -maxSmallObjectSize=20 -numThreads=1000
//
// Or somesuch. See HANDLE_ARG in main().
#define FOR_EACH_ARG(macro) \
macro(uintptr_t, seed, 666, "%lu") \
macro(uintptr_t, seedAdvance, 1, "%lu") \
macro(uintptr_t, maxSmallObjectSize, 1000, "%lu") \
macro(uintptr_t, maxObjectSize, 65536, "%lu") \
macro(double, smallObjectProbability, .95, "%lf") \
macro(double, memalignProbability, .05, "%lf") \
macro(uintptr_t, maxMemalignShift, 14, "%lu") \
macro(uintptr_t, numThreads, 10, "%lu") \
macro(uintptr_t, targetBytesPerPacket, 100000, "%lu") \
macro(uintptr_t, targetBytesInGlobalPool, 100000000, "%lu") \
macro(uintptr_t, maxPacketsPerThread, 10, "%lu") \
macro(uintptr_t, reportPeriod, 1000000, "%lu") \
macro(double, deleteInActiveProbability, .5, "%lf") \
macro(double, massacreProbability, .5, "%lf") \
macro(double, massacreSurvivalRateTarget, .5, "%lf") \
macro(double, suicideProbability, .001, "%lf") \
macro(unsigned, enableBitFit, 1, "%u") \
macro(unsigned, enableSegregated, 1, "%u") \
macro(unsigned, enableStatusReporter, 0, "%u")
#define DECLARE_ARG(type, name, initialValue, format) \
type name = initialValue;
FOR_EACH_ARG(DECLARE_ARG)
#undef DECLARE_ARG
pas_lock startLock;
pas_lock globalPoolLock;
pas_lock printLock;
struct Object {
Object() = default;
Object(void* ptr, uintptr_t size)
: ptr(ptr)
, size(size)
{
}
void* ptr { nullptr };
uintptr_t size { 0 };
};
struct Packet {
Packet() = default;
~Packet()
{
PAS_ASSERT(objects.empty());
PAS_ASSERT(!numBytes);
}
vector<Object> objects;
uintptr_t numBytes { 0 };
};
vector<unique_ptr<Packet>> globalPool;
uintptr_t globalPoolNumBytes;
class Locker {
public:
Locker(pas_lock& lock)
: m_lock(lock)
{
pas_lock_lock(&lock);
}
~Locker()
{
pas_lock_unlock(&m_lock);
}
private:
pas_lock& m_lock;
};
void threadMain(uintptr_t threadIndex)
{
{
Locker locker(startLock);
// Just flash this lock. This trick helps threads start more in unison than they otherwise
// would.
}
uintptr_t mySeed = seed + threadIndex * seedAdvance;
{
Locker locker(printLock);
cout << "Thread " << threadIndex << " starting with seed " << mySeed << "\n";
}
mt19937 randomGenerator(static_cast<unsigned>(mySeed));
vector<unique_ptr<Packet>> localPool;
uintptr_t numAllocationActions = 0;
uintptr_t numDeallocationActions = 0;
uintptr_t numAllocations = 0;
uintptr_t numDeallocations = 0;
auto givePacketToGlobalPool =
[&] (unique_ptr<Packet> packet) {
Locker locker(globalPoolLock);
globalPoolNumBytes += packet->numBytes;
globalPool.push_back(move(packet));
};
auto takePacketFromGlobalPool =
[&] () {
Locker locker(globalPoolLock);
if (globalPool.empty())
return unique_ptr<Packet>(nullptr);
uintptr_t index =
uniform_int_distribution<uintptr_t>(0, globalPool.size() - 1)(randomGenerator);
unique_ptr<Packet> result = move(globalPool[index]);
globalPool[index] = move(globalPool.back());
PAS_ASSERT(result->numBytes <= globalPoolNumBytes);
globalPoolNumBytes -= result->numBytes;
globalPool.pop_back();
return result;
};
auto allocate =
[&] () {
uintptr_t targetSize;
uintptr_t targetMaxSize;
if (bernoulli_distribution(smallObjectProbability)(randomGenerator))
targetMaxSize = maxSmallObjectSize;
else
targetMaxSize = maxObjectSize;
targetSize = uniform_int_distribution<uintptr_t>(0, targetMaxSize)(randomGenerator);
numAllocations++;
char* ptr;
if (bernoulli_distribution(memalignProbability)(randomGenerator)) {
ptr = static_cast<char*>(
iso_allocate_common_primitive_with_alignment(
targetSize,
static_cast<uintptr_t>(1) << uniform_int_distribution<uintptr_t>(
0, maxMemalignShift)(randomGenerator)));
} else
ptr = static_cast<char*>(iso_allocate_common_primitive(targetSize));
for (uintptr_t i = targetSize; i--;)
ptr[i] = static_cast<char>(i + 42);
if (localPool.empty())
localPool.push_back(make_unique<Packet>());
localPool.back()->objects.push_back(Object(ptr, targetSize));
localPool.back()->numBytes += targetSize;
if (localPool.back()->numBytes > targetBytesPerPacket)
localPool.push_back(make_unique<Packet>());
};
auto deleteAll =
[&] (const auto& packetPtr) {
for (Object object : packetPtr->objects) {
iso_deallocate(object.ptr);
numDeallocations++;
}
packetPtr->objects.clear();
packetPtr->numBytes = 0;
};
auto allocationAction =
[&] () {
numAllocationActions++;
if (globalPoolNumBytes > targetBytesInGlobalPool) {
if (bernoulli_distribution(massacreProbability)(randomGenerator)) {
uintptr_t newTargetBytesInGlobalPool = static_cast<uintptr_t>(
targetBytesInGlobalPool * massacreSurvivalRateTarget);
PAS_ASSERT(newTargetBytesInGlobalPool >= 0);
PAS_ASSERT(newTargetBytesInGlobalPool <= targetBytesInGlobalPool);
while (globalPoolNumBytes > newTargetBytesInGlobalPool)
deleteAll(takePacketFromGlobalPool());
} else {
unique_ptr<Packet> packet = takePacketFromGlobalPool();
if (packet)
localPool.push_back(move(packet));
}
} else
allocate();
if (localPool.size() > maxPacketsPerThread) {
uintptr_t index =
uniform_int_distribution<uintptr_t>(0, localPool.size() - 1)(randomGenerator);
unique_ptr<Packet> result = move(localPool[index]);
PAS_ASSERT(result);
localPool.erase(localPool.begin() + index);
if (!result->objects.empty())
givePacketToGlobalPool(move(result));
}
};
auto deallocationAction =
[&] () {
numDeallocationActions++;
if (bernoulli_distribution(suicideProbability)(randomGenerator)) {
for (auto& entry : localPool)
deleteAll(entry);
localPool.clear();
return;
}
Packet* packet;
if (localPool.empty())
return;
if (localPool.size() == 1
|| bernoulli_distribution(deleteInActiveProbability)(randomGenerator))
packet = localPool.back().get();
else {
packet = localPool[
uniform_int_distribution<uintptr_t>(
0, localPool.size() - 2)(
randomGenerator)].get();
}
if (packet->objects.empty())
return;
uintptr_t index =
uniform_int_distribution<uintptr_t>(0, packet->objects.size() - 1)(randomGenerator);
Object object = packet->objects[index];
packet->objects[index] = packet->objects.back();
packet->objects.pop_back();
numDeallocations++;
for (uintptr_t i = object.size; i--;)
PAS_ASSERT(static_cast<char*>(object.ptr)[i] == static_cast<char>(i + 42));
iso_deallocate(object.ptr);
PAS_ASSERT(object.size <= packet->numBytes);
packet->numBytes -= object.size;
};
for (uintptr_t count = 1; ; ++count) {
if (bernoulli_distribution(0.5)(randomGenerator))
allocationAction();
else
deallocationAction();
if (!(count % reportPeriod)) {
uintptr_t localPoolNumBytes = 0;
for (auto& entry : localPool)
localPoolNumBytes += entry->numBytes;
Locker locker(printLock);
cout << "Thread " << threadIndex << ": "
<< count << " iterations, "
<< localPool.size() << " local packets, "
<< localPoolNumBytes << " local bytes, "
<< (localPool.empty() ? 0 : localPool.back()->numBytes) << " bytes in active packet, "
<< globalPool.size() << " global packets, "
<< globalPoolNumBytes << " global bytes, "
<< numAllocationActions << " allocation actions, "
<< numDeallocationActions << " deallocation actions, "
<< numAllocations << " allocations, "
<< numDeallocations << " deallocations.\n";
}
}
PAS_ASSERT(!"Should not be reached");
}
} // anonymous namespace
int main(int argc, char** argv)
{
setvbuf(stdout, NULL, _IOLBF, 0);
pas_lock_construct(&startLock);
pas_lock_construct(&globalPoolLock);
pas_lock_construct(&printLock);
for (int argIndex = 1; argIndex < argc;) {
char* argName = argv[argIndex++];
#define HANDLE_ARG(type, name, initialValue, format) \
if (!strcmp(argName, "-" #name)) { \
if (argIndex >= argc) { \
cerr << "Need argument for -" #name "\n"; \
return 1; \
} \
char* argValue = argv[argIndex++]; \
if (sscanf(argValue, format, &name) != 1) { \
cerr << "Badly formatted argument for -" #name ": " << argValue << "\n"; \
return 1; \
} \
continue; \
}
FOR_EACH_ARG(HANDLE_ARG)
#undef HANDLE_ARG
if (!strcmp(argName, "-help")) {
cout << "Usage: chaos [options]\n";
cout << "\n";
cout << "Options:\n";
#define REPORT_ARG(type, name, initialValue, format) \
printf("-%s <arg> (type %s and initial value " format ")\n", #name, #type, type(initialValue));
FOR_EACH_ARG(REPORT_ARG)
#undef REPORT_ARG
return 2;
}
cerr << "Bad argument: " << argName << " (try -help)\n";
return 1;
}
if (!enableBitFit)
iso_intrinsic_runtime_config.base.max_bitfit_object_size = 0;
if (!enableSegregated)
iso_intrinsic_runtime_config.base.max_segregated_object_size = 0;
pas_status_reporter_enabled = enableStatusReporter;
#define REPORT_ARG(type, name, initialValue, format) \
printf("%s = " format "\n", #name, name);
FOR_EACH_ARG(REPORT_ARG);
#undef REPORT_ARG
vector<thread> threads;
{
Locker locker(startLock);
for (uintptr_t i = numThreads; i--;)
threads.push_back(thread(threadMain, i));
}
for (thread& someThread : threads) {
someThread.join();
if ((true)) {
cerr << "ERROR: Thread exited.\n";
return 1;
}
}
cerr << "ERROR: Threads should not have exited.\n";
return 1;
}