commit 412ca967602d953cbf8138f43e293efc3b4d0b92 Author: Lixfel Date: Thu Mar 16 12:28:35 2023 +0100 Initial bugged version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f88b18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/cmake-build-debug +/.idea diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8ec4c2e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.25) +project(ReferenceLinkingC C) + +set(CMAKE_C_STANDARD 11) + +add_executable(ReferenceLinkingC rl.c RLBench.c) diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/RLBench.c b/RLBench.c new file mode 100644 index 0000000..aa8f788 --- /dev/null +++ b/RLBench.c @@ -0,0 +1,301 @@ +// This is adapted from a benchmark written by John Ellis and Pete Kovac +// of Post Communications. +// It was modified by Hans Boehm of Silicon Graphics. +// Translated to C++ 30 May 1997 by William D Clinger of Northeastern Univ. +// Translated to C 15 March 2000 by Hans Boehm, now at HP Labs. +// +// This is no substitute for real applications. No actual application +// is likely to behave in exactly this way. However, this benchmark was +// designed to be more representative of real applications than other +// Java GC benchmarks of which we are aware. +// It attempts to model those properties of allocation requests that +// are important to current GC techniques. +// It is designed to be used either to obtain a single overall performance +// number, or to give a more detailed estimate of how collector +// performance varies with object lifetimes. It prints the time +// required to allocate and collect balanced binary trees of various +// sizes. Smaller trees result in shorter object lifetimes. Each cycle +// allocates roughly the same amount of memory. +// Two data structures are kept around during the entire process, so +// that the measured performance is representative of applications +// that maintain some live in-memory data. One of these is a tree +// containing many pointers. The other is a large array containing +// double precision floating point numbers. Both should be of comparable +// size. +// +// The results are only really meaningful together with a specification +// of how much memory was used. It is possible to trade memory for +// better time performance. This benchmark should be run in a 32 MB +// heap, though we don't currently know how to enforce that uniformly. +// +// Unlike the original Ellis and Kovac benchmark, we do not attempt +// measure pause times. This facility should eventually be added back +// in. There are several reasons for omitting it for now. The original +// implementation depended on assumptions about the thread scheduler +// that don't hold uniformly. The results really measure both the +// scheduler and GC. Pause time measurements tend to not fit well with +// current benchmark suites. As far as we know, none of the current +// commercial Java implementations seriously attempt to minimize GC pause +// times. + +#include +#include +#include +#include "rl.h" + +#ifdef GC +# include "gc.h" +#endif + +#ifdef PROFIL +extern void init_profiling(); + extern dump_profile(); +#endif + +// These macros were a quick hack for the Macintosh. +// +// #define currentTime() clock() +// #define elapsedTime(x) ((1000*(x))/CLOCKS_PER_SEC) + +#define currentTime() stats_rtclock() +#define elapsedTime(x) (x) + +/* Get the current time in milliseconds */ + +unsigned +stats_rtclock( void ) +{ + struct timeval t; + struct timezone tz; + + if (gettimeofday( &t, &tz ) == -1) + return 0; + return (t.tv_sec * 1000 + t.tv_usec / 1000); +} + +static const int kStretchTreeDepth = 18; // about 16Mb +static const int kLongLivedTreeDepth = 16; // about 4Mb +static const int kArraySize = 500000; // about 4Mb +static const int kMinTreeDepth = 4; +static const int kMaxTreeDepth = 16; + +typedef struct Node0_struct { + Rl left; + Rl right; + int i, j; +} Node0; + +#ifdef HOLES +# define HOLE() GC_NEW(Node0); +#else +# define HOLE() +#endif + +typedef Rl* Node; +#define TO_NODE(rl) ((Node0*) (rl)->ref) + +void init_Node(Node me, Node l, Node r) { + Node0* node = TO_NODE(me); + rl_set(&node->left, me->ref, l); + rl_set(&node->right, me->ref, r); +} + +#ifndef GC +void destroy_Node(void* me) { + Node0* node = (Node0*)me; + if(node->left.ref) + rl_free(&node->left); + if(node->right.ref) + rl_free(&node->right); +} +#endif + +// Nodes used by a tree of a given size +static int TreeSize(int i) { + return ((1 << (i + 1)) - 1); +} + +// Number of iterations to use for a given tree depth +static int NumIters(int i) { + return 2 * TreeSize(kStretchTreeDepth) / TreeSize(i); +} + +// Build tree top down, assigning to older objects. +static void Populate(int iDepth, Node thisNode) { + Node0* node = TO_NODE(thisNode); + if (iDepth<=0) { + node->left.ref = NULL; + node->right.ref = NULL; + return; + } else { + iDepth--; +# ifdef GC + thisNode->left = GC_NEW(Node0); HOLE(); + thisNode->right = GC_NEW(Node0); HOLE(); +# else + rl_alloc(&node->left, node, sizeof(Node0), destroy_Node); + rl_alloc(&node->right, node, sizeof(Node0), destroy_Node); +# endif + Populate (iDepth, &node->left); + Populate (iDepth, &node->right); + } +} + +// Build tree bottom-up +static void MakeTree(Node result, int iDepth) { + if (iDepth<=0) { +# ifndef GC + rl_alloc(result, NULL, sizeof(Node0), destroy_Node); + Node0* node = TO_NODE(result); + node->left.ref = NULL; + node->right.ref = NULL; +# else + result = GC_NEW(Node0); HOLE(); +# endif + /* result is implicitly initialized in both cases. */ + } else { + Rl left, right; + MakeTree(&left, iDepth-1); + MakeTree(&right, iDepth-1); +# ifndef GC + rl_alloc(result, NULL, sizeof(Node0), destroy_Node); +# else + result = GC_NEW(Node0); HOLE(); +# endif + init_Node(result, &left, &right); + printf("Set works\n"); + rl_free(&left); + rl_free(&right); + } +} + +static void PrintDiagnostics() { +#if 0 + long lFreeMemory = Runtime.getRuntime().freeMemory(); + long lTotalMemory = Runtime.getRuntime().totalMemory(); + + System.out.print(" Total memory available=" + + lTotalMemory + " bytes"); + System.out.println(" Free memory=" + lFreeMemory + " bytes"); +#endif +} + +static void TimeConstruction(int depth) { + long tStart, tFinish; + int iNumIters = NumIters(depth); + Rl tempTree; + int i; + + printf("Creating %d trees of depth %d\n", iNumIters, depth); + + tStart = currentTime(); + for (i = 0; i < iNumIters; ++i) { +# ifndef GC + rl_alloc(&tempTree, NULL, sizeof(Node0), destroy_Node); +# else + tempTree = GC_NEW(Node0); +# endif + Populate(depth, &tempTree); +# ifndef GC + rl_free(&tempTree); +# endif + } + tFinish = currentTime(); + printf("\tTop down construction took %d msec\n", + elapsedTime(tFinish - tStart)); + + tStart = currentTime(); + for (i = 0; i < iNumIters; ++i) { + MakeTree(&tempTree, depth); +# ifndef GC + rl_free(&tempTree); +# endif + } + tFinish = currentTime(); + printf("\tBottom up construction took %d msec\n", + elapsedTime(tFinish - tStart)); + +} + +int main() { + Node root; + Node longLivedTree; + Rl tempTree; + long tStart, tFinish; + long tElapsed; + int i, d; + double *array; + +#ifdef GC + // GC_full_freq = 30; + // GC_free_space_divisor = 16; + // GC_enable_incremental(); +#endif + printf("Garbage Collector Test\n"); + printf(" Live storage will peak at %d bytes.\n\n", + 2 * sizeof(Node0) * TreeSize(kLongLivedTreeDepth) + + sizeof(double) * kArraySize); + printf(" Stretching memory with a binary tree of depth %d\n", + kStretchTreeDepth); + PrintDiagnostics(); +# ifdef PROFIL + init_profiling(); +# endif + + tStart = currentTime(); + + // Stretch the memory space quickly + MakeTree(&tempTree, kStretchTreeDepth); + printf("Made tree\n"); +# ifndef GC + rl_free(&tempTree); +# endif + + // Create a long lived object + printf(" Creating a long-lived binary tree of depth %d\n", + kLongLivedTreeDepth); +# ifndef GC + rl_alloc(longLivedTree, NULL, sizeof(Node0), destroy_Node); +# else + longLivedTree = GC_NEW(Node0); +# endif + Populate(kLongLivedTreeDepth, longLivedTree); + + // Create long-lived array, filling half of it + printf(" Creating a long-lived array of %d doubles\n", kArraySize); +# ifndef GC + array = malloc(kArraySize * sizeof(double)); +# else + # ifndef NO_PTRFREE + array = GC_MALLOC_ATOMIC(sizeof(double) * kArraySize); +# else + array = GC_MALLOC(sizeof(double) * kArraySize); +# endif +# endif + for (i = 0; i < kArraySize/2; ++i) { + array[i] = 1.0/i; + } + PrintDiagnostics(); + + for (d = kMinTreeDepth; d <= kMaxTreeDepth; d += 2) { + TimeConstruction(d); + } + + if (longLivedTree == 0 || array[1000] != 1.0/1000) + fprintf(stderr, "Failed\n"); + // fake reference to LongLivedTree + // and array + // to keep them from being optimized away + + tFinish = currentTime(); + tElapsed = elapsedTime(tFinish-tStart); + PrintDiagnostics(); + printf("Completed in %d msec\n", tElapsed); +# ifdef GC + printf("Completed %d collections\n", GC_gc_no); + printf("Heap size is %d\n", GC_get_heap_size()); +# endif +# ifdef PROFIL + dump_profile(); +# endif +} diff --git a/rl.c b/rl.c new file mode 100644 index 0000000..9ec4145 --- /dev/null +++ b/rl.c @@ -0,0 +1,104 @@ +#include "rl.h" +#include +#include + +struct RlBox { + bool marked; + Rl* first; + Rl* last; + Destructor destructor; +}; + +#define RLBOX_SIZE sizeof(struct RlBox) +#define PTR_TO_RLBOX(ptr) ((struct RlBox*)((ptr) - RLBOX_SIZE)) + +inline void rl_alloc(Rl* reference, const void* owner, const size_t size, const Destructor destructor) { + struct RlBox* box = (struct RlBox*) malloc(RLBOX_SIZE + size); + box->marked = false; + box->first = NULL; + box->last = NULL; + box->destructor = destructor; + reference->prev = NULL; + reference->next = NULL; + reference->owner = PTR_TO_RLBOX(owner); + reference->ref = box + RLBOX_SIZE; +} + +// reference may not point to a valid object +inline void rl_set(Rl* reference, const void* owner, const Rl* copy) { + reference->prev = PTR_TO_RLBOX(copy->ref)->last; + reference->next = NULL; + reference->owner = PTR_TO_RLBOX(owner); + reference->ref = copy->ref; + reference->prev->next = reference; +} + +static void unmark(struct RlBox* obj) { + if(!obj->marked) + return; + obj->marked = false; + + Rl* ref = obj->first; + while(ref) { + unmark(ref->owner); + ref = ref->next; + } +} + +static bool search_root(struct RlBox* obj) { + if(obj->marked) + return false; + obj->marked = true; + + Rl* ref = obj->first; + while(ref) { + if(!ref->owner || search_root(ref->owner)) { + // mark reversal + Rl* prev = ref->prev; + while(prev) { + unmark(prev->owner); + prev = ref->prev; + } + obj->marked = false; + + // reordering + if(ref != obj->first) { + if(ref->next) + ref->next->prev = ref->prev; + else + obj->last = ref->prev; + ref->prev->next = ref->next; + + obj->first->prev = ref; + obj->first = ref; + } + + return true; + } + + ref = ref->next; + } + + return false; +} + +inline void rl_free(Rl* reference) { + struct RlBox* obj = PTR_TO_RLBOX(reference->ref); + if(obj->first != reference) { + if(reference->next) + reference->next->prev = reference->prev; + else + obj->last = reference->prev; + + reference->prev->next = reference->next; + return; + } + + if(search_root(obj)) + return; + + obj->destructor(reference->ref); + free(obj); +} + +//TODO foolproofed versions (with NULL, prev set, etc.) \ No newline at end of file diff --git a/rl.h b/rl.h new file mode 100644 index 0000000..1aff91a --- /dev/null +++ b/rl.h @@ -0,0 +1,34 @@ +#pragma once +#include + +typedef struct Rl Rl; +typedef void (*Destructor)(void*); + +struct Rl { + Rl* prev; + Rl* next; + struct RlBox* owner; + void* ref; +}; + +/* + * rl_alloc has to be used for new object allocation of the size size. + * reference will point to the new function afterwards. + * owner should be the struct containing reference and may be NULL if Rl is on the stack or in a global variable. + * The destructor has to rl_free all references inside the newly allocated object. + * + * This function disregards potential malloc() failures. + */ +void rl_alloc(Rl* reference, const void* owner, size_t size, Destructor destructor); + +/* + * rl_set has to be used to set the reference Rl to point to the same object as copy. + * owner should be the struct containing reference and may be NULL if Rl is on the stack or in a global variable. + */ +void rl_set(Rl* reference, const void* owner, const Rl* copy); + +/* + * rl_free has to be called prior to discarding a Rl to prevent resource leaks. + * Rl* has to point to a valid object (not NULL) + */ +void rl_free(Rl* reference);