diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a02de7a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y valgrind make gcc + + - name: Build project + run: make + + - name: Run valgrind tests + run: valgrind ./myalloc diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3c415cd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "myalloc.h": "c" + } +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..34c091d --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +TARGET = myalloc +OBJS = main.o myalloc.o + +CXXFLAGS = -Wall -g -std=c++11 +CXX = g++ + +all: clean $(TARGET) + +%.o: %.cpp + $(CXX) -c $(CXXFLAGS) $< + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) $(OBJS) -o $@ + +clean: + rm -f $(TARGET) + rm -f $(OBJS) \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..cee764c --- /dev/null +++ b/main.cpp @@ -0,0 +1,41 @@ +#include +#include +#include "myalloc.hpp" + +int main(int argc, char* argv[]) { + MyAllocator allocator(100, FIRST_FIT); + std::cout << "Using first fit algorithm on memory size 100" << std::endl; + + std::vector p(50, nullptr); + for(int i = 0; i < 10; ++i) { + p[i] = static_cast(allocator.allocate(sizeof(int))); + if(p[i] == nullptr) { + std::cout << "Allocation failed" << std::endl; + continue; + } + *(p[i]) = i; + std::cout << "p[" << i << "] = " << p[i] << " ; *p[" << i << "] = " << *(p[i]) << std::endl; + } + + allocator.printStatistics(); + + for(int i = 0; i < 10; ++i) { + if(i % 2 == 0) { + continue; + } + std::cout << "Freeing p[" << i << "]" << std::endl; + allocator.deallocate(p[i]); + p[i] = nullptr; + } + + std::cout << "available_memory " << allocator.availableMemory() << std::endl; + + std::vector before(100, nullptr); + std::vector after(100, nullptr); + allocator.compactAllocation(before, after); + + allocator.printStatistics(); + + // allocator's destructor will automatically free allocated memory + return 0; +} \ No newline at end of file diff --git a/main.o b/main.o new file mode 100644 index 0000000..a2566e7 Binary files /dev/null and b/main.o differ diff --git a/myalloc b/myalloc new file mode 100755 index 0000000..3a3a1dc Binary files /dev/null and b/myalloc differ diff --git a/myalloc.cpp b/myalloc.cpp new file mode 100644 index 0000000..038b6a2 --- /dev/null +++ b/myalloc.cpp @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include "myalloc.hpp" + +MyAllocator::MyAllocator(int size, AllocationAlgorithm algorithm) { + initialize(size, algorithm); +} + +MyAllocator::~MyAllocator() { + destroy(); +} + +void MyAllocator::initialize(int size, AllocationAlgorithm algorithm) { + assert(size > 0); + + // Align size to 64 bytes + size = (size + 63) & ~63; + + algorithm_ = algorithm; + size_ = size; + memory_ = malloc(static_cast(size_)); + + if (!memory_) { + fprintf(stderr, "Failed to allocate memory\n"); + exit(EXIT_FAILURE); + } + + memset(memory_, 0, static_cast(size_)); + + Block* initialBlock = static_cast(memory_); + initialBlock->size = size_; + + freeList_ = new Node{ initialBlock, nullptr }; + allocatedList_ = nullptr; + + pthread_mutex_init(&lock_, nullptr); +} + +void MyAllocator::destroy() { + pthread_mutex_lock(&lock_); + + free(memory_); + memory_ = nullptr; + + Node* current = freeList_; + while (current) { + Node* next = current->next; + delete current; + current = next; + } + + freeList_ = nullptr; + + current = allocatedList_; + while (current) { + Node* next = current->next; + delete current; + current = next; + } + + allocatedList_ = nullptr; + + pthread_mutex_unlock(&lock_); + pthread_mutex_destroy(&lock_); +} + +void* MyAllocator::allocate(int size) { + pthread_mutex_lock(&lock_); + + Node* prev = nullptr; + Node* curr = freeList_; + Node* bestPrev = nullptr; + Node* bestFit = nullptr; + size_t totalSize = size + sizeof(size_t); + + if (algorithm_ == BEST_FIT) { + while (curr) { + if (curr->block->size >= totalSize && (!bestFit || curr->block->size < bestFit->block->size)) { + bestFit = curr; + bestPrev = prev; + } + prev = curr; + curr = curr->next; + } + curr = bestFit; + prev = bestPrev; + } else { + while (curr && curr->block->size < totalSize) { + prev = curr; + curr = curr->next; + } + } + + if (curr) { + Block* block = curr->block; + + if (block->size >= totalSize + sizeof(Block)) { + Block* newBlock = reinterpret_cast(reinterpret_cast(block) + totalSize); + newBlock->size = block->size - totalSize; + + Node* newNode = new Node{ newBlock, nullptr }; + + curr->block->size = totalSize; + + if (prev) { + prev->next = newNode; + } else { + freeList_ = newNode; + } + } else { + if (prev) { + prev->next = curr->next; + } else { + freeList_ = curr->next; + } + curr->block->size = totalSize; + } + + Node* temp = allocatedList_; + if (!temp) { + allocatedList_ = curr; + } else { + while (temp->next) { + temp = temp->next; + } + temp->next = curr; + } + curr->next = nullptr; + + *reinterpret_cast(block) = totalSize; + + pthread_mutex_unlock(&lock_); + return reinterpret_cast(block) + sizeof(size_t); + } + + pthread_mutex_unlock(&lock_); + return nullptr; +} + +void MyAllocator::deallocate(void* ptr) { + assert(ptr != nullptr); + + pthread_mutex_lock(&lock_); + + Block* block = reinterpret_cast(reinterpret_cast(ptr) - sizeof(size_t)); + + Node* newNode = new Node{ block, nullptr }; + + if (!freeList_) { + freeList_ = newNode; + } else { + Node* curr = freeList_; + while (curr->next) { + curr = curr->next; + } + curr->next = newNode; + } + + Node* curr = allocatedList_; + Node* prev = nullptr; + + while (curr && curr->block != block) { + prev = curr; + curr = curr->next; + } + + if (curr) { + if (prev) { + prev->next = curr->next; + } else { + allocatedList_ = curr->next; + } + delete curr; + } + + bool merged; + do { + merged = false; + Node* current = freeList_; + while (current) { + Node* checker = current; + while (checker->next) { + if (reinterpret_cast(current->block) + current->block->size == reinterpret_cast(checker->next->block)) { + current->block->size += checker->next->block->size; + Node* temp = checker->next; + checker->next = checker->next->next; + delete temp; + merged = true; + } else { + checker = checker->next; + } + } + current = current->next; + } + } while (merged); + + pthread_mutex_unlock(&lock_); +} + +int MyAllocator::compactAllocation(std::vector& before, std::vector& after) { + pthread_mutex_lock(&lock_); + + int compactedSize = 0; + size_t offset = 0; + Node* current = allocatedList_; + + Node* temp; + while (freeList_) { + temp = freeList_; + freeList_ = freeList_->next; + delete temp; + } + + while (current) { + Node* next = current->next; + Block* block = current->block; + if (reinterpret_cast(block) != reinterpret_cast(memory_) + offset) { + std::memmove(reinterpret_cast(memory_) + offset, block, block->size); + before.push_back(reinterpret_cast(block) + sizeof(size_t)); + after.push_back(reinterpret_cast(memory_) + offset + sizeof(size_t)); + compactedSize++; + } + block = reinterpret_cast(reinterpret_cast(memory_) + offset); + current->block = block; + offset += block->size; + current = next; + } + + if (offset < static_cast(size_)) { + freeList_ = new Node{ reinterpret_cast(reinterpret_cast(memory_) + offset), nullptr }; + freeList_->block->size = size_ - offset; + } else { + freeList_ = nullptr; + } + + pthread_mutex_unlock(&lock_); + return compactedSize; +} + +int MyAllocator::availableMemory() { + pthread_mutex_lock(&lock_); + + int availableMemorySize = 0; + Node* current = freeList_; + while (current) { + availableMemorySize += current->block->size; + current = current->next; + } + + pthread_mutex_unlock(&lock_); + return availableMemorySize; +} + +void MyAllocator::printStatistics() { + pthread_mutex_lock(&lock_); + + int allocatedSize = 0; + int allocatedChunks = 0; + int freeSize = 0; + int freeChunks = 0; + int smallestFreeChunkSize = size_; + int largestFreeChunkSize = 0; + + Node* current = allocatedList_; + while (current) { + allocatedSize += current->block->size; + allocatedChunks++; + current = current->next; + } + + current = freeList_; + while (current) { + freeSize += current->block->size; + if (current->block->size > largestFreeChunkSize) { + largestFreeChunkSize = current->block->size; + } + if (current->block->size < smallestFreeChunkSize) { + smallestFreeChunkSize = current->block->size; + } + freeChunks++; + current = current->next; + } + + printf("Allocated size = %d\n", allocatedSize); + printf("Allocated chunks = %d\n", allocatedChunks); + printf("Free size = %d\n", freeSize); + printf("Free chunks = %d\n", freeChunks); + printf("Largest free chunk size = %d\n", largestFreeChunkSize); + printf("Smallest free chunk size = %d\n", smallestFreeChunkSize); + + pthread_mutex_unlock(&lock_); +} \ No newline at end of file diff --git a/myalloc.hpp b/myalloc.hpp new file mode 100644 index 0000000..a6f2a26 --- /dev/null +++ b/myalloc.hpp @@ -0,0 +1,42 @@ +#ifndef __MYALLOCATOR_H__ +#define __MYALLOCATOR_H__ + +#include +#include +#include + +enum AllocationAlgorithm { FIRST_FIT, BEST_FIT }; + +class MyAllocator { +public: + MyAllocator(int size, AllocationAlgorithm algorithm); + ~MyAllocator(); + + void* allocate(int size); + void deallocate(void* ptr); + int availableMemory(); + void printStatistics(); + int compactAllocation(std::vector& before, std::vector& after); + +private: + struct Block { + size_t size; + }; + + struct Node { + Block* block; + Node* next; + }; + + AllocationAlgorithm algorithm_; + int size_; + void* memory_; + Node* freeList_; + Node* allocatedList_; + pthread_mutex_t lock_; + + void initialize(int size, AllocationAlgorithm algorithm); + void destroy(); +}; + +#endif // __MYALLOCATOR_H__ \ No newline at end of file diff --git a/myalloc.o b/myalloc.o new file mode 100644 index 0000000..7a8cdfe Binary files /dev/null and b/myalloc.o differ