#include #include #include #include "ptypes.h" #include "cache.h" #include "sieve.h" #include "EXTERN.h" #include "perl.h" #include "XSUB.h" /* * These functions are used internally by the .c and .xs files. * They handle a cached primary set of primes, as well as a segment * area for use by all the functions that want to do segmented operation. * * We must be thread-safe, and we want to allow a good deal of concurrency. * It is imperative these be used correctly. After calling the get method, * use the sieve or segment, then release. You MUST call release before you * return or croak. You ought to release as soon as you're done using the * sieve or segment. */ static int mutex_init = 0; #ifdef USE_ITHREADS static perl_mutex segment_mutex; /* See: http://en.wikipedia.org/wiki/Readers-writers_problem */ static perl_mutex primary_mutex_no_waiting; static perl_mutex primary_mutex_no_accessing; static perl_mutex primary_mutex_counter; static int primary_number_of_readers = 0; #endif static unsigned char* prime_cache_sieve = 0; static UV prime_cache_size = 0; /* To avoid thrashing, sieve a little farther than we absolutely need to. */ #define FILL_EXTRA_N (128*30) /* Erase the primary cache and fill up to n. */ static void _erase_and_fill_prime_cache(UV n) { UV padded_n; /* Note: You need to handle mutexes around this. * MUTEX_LOCK(&primary_mutex_no_waiting); * MUTEX_LOCK(&primary_mutex_no_accessing); * MUTEX_UNLOCK(&primary_mutex_no_waiting); * _fill_prime_cache(n); * MUTEX_UNLOCK(&primary_mutex_no_accessing); */ if (n >= (UV_MAX-FILL_EXTRA_N)) padded_n = UV_MAX; else padded_n = ((n + FILL_EXTRA_N)/30)*30; /* If new size isn't larger or smaller, then we're done. */ if (prime_cache_size == padded_n) return; if (prime_cache_sieve != 0) Safefree(prime_cache_sieve); prime_cache_sieve = 0; prime_cache_size = 0; if (n > 0) { prime_cache_sieve = sieve_erat30(padded_n); if (prime_cache_sieve != 0) prime_cache_size = padded_n; } } /* * Get the size and a pointer to the cached prime sieve. * Returns the maximum sieved value available. * Allocates and sieves if needed. * * The sieve holds 30 numbers per byte, using a mod-30 wheel. */ UV get_prime_cache(UV n, const unsigned char** sieve) { #ifdef USE_ITHREADS int prev_readers; int i_hold_access_lock = 0; if (sieve == 0) { if (prime_cache_size < n) { MUTEX_LOCK(&primary_mutex_no_waiting); MUTEX_LOCK(&primary_mutex_no_accessing); MUTEX_UNLOCK(&primary_mutex_no_waiting); _erase_and_fill_prime_cache(n); MUTEX_UNLOCK(&primary_mutex_no_accessing); } return prime_cache_size; } MUTEX_LOCK(&primary_mutex_no_waiting); /* If we need more primes, get the access lock right now */ if (prime_cache_size < n) { MUTEX_LOCK(&primary_mutex_no_accessing); i_hold_access_lock = 1; } MUTEX_LOCK(&primary_mutex_counter); prev_readers = primary_number_of_readers; primary_number_of_readers++; MUTEX_UNLOCK(&primary_mutex_counter); if ( (prev_readers == 0) && (!i_hold_access_lock) ) { MUTEX_LOCK(&primary_mutex_no_accessing); i_hold_access_lock = 1; } if (prime_cache_size < n) { _erase_and_fill_prime_cache(n); } MUTEX_UNLOCK(&primary_mutex_no_waiting); MPUassert(prime_cache_size >= n, "prime cache is too small!"); *sieve = prime_cache_sieve; return prime_cache_size; #else if (prime_cache_size < n) _erase_and_fill_prime_cache(n); if (sieve != 0) *sieve = prime_cache_sieve; return prime_cache_size; #endif } void release_prime_cache(const unsigned char* mem) { #ifdef USE_ITHREADS int current_readers; MUTEX_LOCK(&primary_mutex_counter); primary_number_of_readers--; current_readers = primary_number_of_readers; MUTEX_UNLOCK(&primary_mutex_counter); if (current_readers == 0) { MUTEX_UNLOCK(&primary_mutex_no_accessing); } #endif } /* The segment everyone is trying to share */ #define PRIMARY_SEGMENT_CHUNK_SIZE UVCONST(256*1024-16) static unsigned char* prime_segment = 0; static int prime_segment_is_available = 1; /* If that's in use, malloc a new one of this size */ #define SECONDARY_SEGMENT_CHUNK_SIZE UVCONST( 64*1024-16) unsigned char* get_prime_segment(UV *size) { unsigned char* mem; int use_prime_segment; MPUassert(size != 0, "get_prime_segment given null size pointer"); MPUassert(mutex_init == 1, "segment mutex has not been initialized"); MUTEX_LOCK(&segment_mutex); if (prime_segment_is_available) { prime_segment_is_available = 0; use_prime_segment = 1; } else { use_prime_segment = 0; } MUTEX_UNLOCK(&segment_mutex); if (use_prime_segment) { if (prime_segment == 0) New(0, prime_segment, PRIMARY_SEGMENT_CHUNK_SIZE, unsigned char); *size = PRIMARY_SEGMENT_CHUNK_SIZE; mem = prime_segment; } else { New(0, mem, SECONDARY_SEGMENT_CHUNK_SIZE, unsigned char); *size = SECONDARY_SEGMENT_CHUNK_SIZE; } if (mem == 0) croak("Could not allocate %"UVuf" bytes for segment sieve", *size); return mem; } void release_prime_segment(unsigned char* mem) { MUTEX_LOCK(&segment_mutex); if (mem == prime_segment) { prime_segment_is_available = 1; } else { Safefree(mem); } MUTEX_UNLOCK(&segment_mutex); } #define INITIAL_CACHE_SIZE ((1024-16)*30 - FILL_EXTRA_N) void prime_precalc(UV n) { if (!mutex_init) { MUTEX_INIT(&segment_mutex); MUTEX_INIT(&primary_mutex_no_waiting); MUTEX_INIT(&primary_mutex_no_accessing); MUTEX_INIT(&primary_mutex_counter); mutex_init = 1; } /* On initialization, make a few primes (2-30k using 1k memory) */ if (n == 0) n = INITIAL_CACHE_SIZE; get_prime_cache(n, 0); /* Sieve to n */ /* TODO: should we prealloc the segment here? */ } void prime_memfree(void) { MPUassert(mutex_init == 1, "cache mutexes have not been initialized"); MUTEX_LOCK(&segment_mutex); /* Don't free if another thread is using it */ if ( (prime_segment != 0) && (prime_segment_is_available) ) { Safefree(prime_segment); prime_segment = 0; } MUTEX_UNLOCK(&segment_mutex); MUTEX_LOCK(&primary_mutex_no_waiting); MUTEX_LOCK(&primary_mutex_no_accessing); MUTEX_UNLOCK(&primary_mutex_no_waiting); /* Put primary cache back to initial state */ _erase_and_fill_prime_cache(INITIAL_CACHE_SIZE); MUTEX_UNLOCK(&primary_mutex_no_accessing); } void _prime_memfreeall(void) { /* No locks. We're shutting everything down. */ if (mutex_init) { MUTEX_DESTROY(&segment_mutex); MUTEX_DESTROY(&primary_mutex_no_waiting); MUTEX_DESTROY(&primary_mutex_no_accessing); MUTEX_DESTROY(&primary_mutex_counter); mutex_init = 0; } if (prime_cache_sieve != 0) Safefree(prime_cache_sieve); prime_cache_sieve = 0; prime_cache_size = 0; if (prime_segment != 0) Safefree(prime_segment); prime_segment = 0; }