| /* |
| * 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; |
| } |
| |
| |