Repository: numpy/numpy
File: numpy/_core/src/multiarray/alloc.cpp (C++)

Code before:
#    endif
#endif
#ifndef USE_ALLOC_CACHE
#    define USE_ALLOC_CACHE 1
#endif


# define NBUCKETS 1024 /* number of buckets for data*/
# define NBUCKETS_DIM 16 /* number of buckets for dimensions/strides */
# define NCACHE 7 /* number of cache entries per bucket */
/* this structure fits neatly into a cacheline */
typedef struct {
    npy_uintp available; /* number of cached pointers */
    void * ptrs[NCACHE];
} cache_bucket;

// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61991
// gcc has a bug where if the thread_local variable is unused
// then its destructor may not get called at thread exit.
// to work around this we define consolidate both datacache
// and dimcache into a single thread_local variable so
// that there is no issue of unused thread_local variables.
typedef struct thread_local_cache {
    cache_bucket datacache[NBUCKETS];
    cache_bucket dimcache[NBUCKETS_DIM];
    ~thread_local_cache() {
        for (npy_uint i = 0; i < NBUCKETS; ++i) {
            while (datacache[i].available > 0) {
                free(datacache[i].ptrs[--datacache[i].available]);
            }
        }
        for (npy_uint i = 0; i < NBUCKETS_DIM; ++i) {
            while (dimcache[i].available > 0) {
                PyArray_free(dimcache[i].ptrs[--dimcache[i].available]);
            }
        }
    }
} thread_local_cache;

static NPY_TLS thread_local_cache tls_cache;

#define datacache (tls_cache.datacache)
#define dimcache (tls_cache.dimcache)

/*
 * This function tells whether NumPy attempts to call `madvise` with
 * `MADV_HUGEPAGE`.  `madvise` is only ever used on linux, so the value
 * of `madvise_hugepage` may be ignored.
 *
 * It is exposed to Python as `np._core.multiarray._get_madvise_hugepage`.
 */

Review comment (performance):
```suggestion

    thread_local_cache() {
        for (npy_uint i = 0; i < NBUCKETS; ++i) {
            datacache[i].available = 0;
        }
        for (npy_uint i = 0; i < NBUCKETS_DIM; ++i) {
            dimcache[i].available = 0;
        }
    }

    ~thread_local_cache() {
```
Let's just see if that works, even if it is a random try maybe.

FWIW, I also have 13.3.0 locally and everything is good (also in valgrind `test_multithreaded.py` is fine with this).

Code after:
#    endif
#endif
#ifndef USE_ALLOC_CACHE
#    define USE_ALLOC_CACHE 1
#endif


# define NBUCKETS 1024 /* number of buckets for data*/
# define NBUCKETS_DIM 16 /* number of buckets for dimensions/strides */
# define NCACHE 7 /* number of cache entries per bucket */
/* this structure fits neatly into a cacheline */
typedef struct {
    npy_uintp available; /* number of cached pointers */
    void * ptrs[NCACHE];
} cache_bucket;

static NPY_TLS cache_bucket _datacache[NBUCKETS];
static NPY_TLS cache_bucket _dimcache[NBUCKETS_DIM];

// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61991
// gcc has a bug where if the thread local variable
// is unused then in some cases it's destructor may not get
// called at thread exit. So to workaround this, we access the
// datacache and dimcache through this struct so that
// cache_destructor gets initialized and used, ensuring that
// the destructor gets called properly at thread exit.
// The datacache and dimcache are not embedded in this struct
// because that would make this struct very large and certain
// platforms like armhf can crash while allocating that large
// TLS block.
typedef struct cache_destructor {
    cache_bucket *dimcache;
    cache_bucket *datacache;
    cache_destructor() {
        dimcache = &_dimcache[0];
        datacache = &_datacache[0];
    }
    ~cache_destructor() {
        for (npy_uint i = 0; i < NBUCKETS; ++i) {
            while (datacache[i].available > 0) {
                free(datacache[i].ptrs[--datacache[i].available]);
            }
        }
        for (npy_uint i = 0; i < NBUCKETS_DIM; ++i) {
            while (dimcache[i].available > 0) {
                PyArray_free(dimcache[i].ptrs[--dimcache[i].available]);
            }
        }
    }
} cache_destructor;