| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * pixalloc.c |
| * |
| * Custom memory storage with allocator and deallocator |
| * |
| * l_int32 pmsCreate() |
| * void pmsDestroy() |
| * void *pmsCustomAlloc() |
| * void pmsCustomDealloc() |
| * void *pmsGetAlloc() |
| * l_int32 pmsGetLevelForAlloc() |
| * l_int32 pmsGetLevelForDealloc() |
| * void pmsLogInfo() |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "allheaders.h" |
| |
| |
| /*-------------------------------------------------------------------------* |
| * Pix Memory Storage * |
| * * |
| * This is a simple utility for handling pix memory storage. It is * |
| * enabled by setting the PixMemoryManager allocators to the functions * |
| * that are defined here * |
| * pmsCustomAlloc() * |
| * pmsCustomDealloc() * |
| * Use pmsCreate() at the beginning to do the pre-allocation, and * |
| * pmsDestroy() at the end to clean it up. * |
| *-------------------------------------------------------------------------*/ |
| /* |
| * In the following, the "memory" refers to the image data |
| * field that is used within the pix. The memory store is a |
| * continuous block of memory, that is logically divided into |
| * smaller "chunks" starting with a set at a minimum size, and |
| * followed by sets of increasing size that are a power of 2 larger |
| * than the minimum size. You must specify the number of chunks |
| * of each size. |
| * |
| * A requested data chunk, if it exists, is borrowed from the memory |
| * storage, and returned after use. If the chunk is too small, or |
| * too large, or if all chunks in the appropriate size range are |
| * in use, the memory is allocated dynamically and freed after use. |
| * |
| * There are four parameters that determine the use of pre-allocated memory: |
| * |
| * minsize: any requested chunk smaller than this is allocated |
| * dynamically and destroyed after use. No preallocated |
| * memory is used. |
| * smallest: the size of the smallest pre-allocated memory chunk. |
| * nlevels: the number of different sizes of data chunks, each a |
| * power of 2 larger than 'smallest'. |
| * numalloc: a Numa of size 'nlevels' containing the number of data |
| * chunks for each size that are in the memory store. |
| * |
| * As an example, suppose: |
| * minsize = 0.5MB |
| * smallest = 1.0MB |
| * nlevels = 4 |
| * numalloc = {10, 5, 5, 5} |
| * Then the total amount of allocated memory (in MB) is |
| * 10 * 1 + 5 * 2 + 5 * 4 + 5 * 8 = 80 MB |
| * Any pix requiring less than 0.5 MB or more than 8 MB of memory will |
| * not come from the memory store. Instead, it will be dynamically |
| * allocated and freed after use. |
| * |
| * How is this implemented? |
| * |
| * At setup, the full data block size is computed and allocated. |
| * The addresses of the individual chunks are found, and the pointers |
| * are stored in a set of Ptra (generic pointer arrays), using one Ptra |
| * for each of the sizes of the chunks. When returning a chunk after |
| * use, it is necessary to determine from the address which size level |
| * (ptra) the chunk belongs to. This is done by comparing the address |
| * of the associated chunk. |
| * |
| * In the event that memory chunks need to be dynamically allocated, |
| * either (1) because they are too small or too large for the memory |
| * store or (2) because all the pix of that size (i.e., in the |
| * appropriate level) in the memory store are in use, the |
| * addresses generated will be outside the pre-allocated block. |
| * After use they won't be returned to a ptra; instead the deallocator |
| * will free them. |
| */ |
| |
| |
| struct PixMemoryStore |
| { |
| struct L_Ptraa *paa; /* Holds ptrs to allocated memory */ |
| size_t minsize; /* Pix smaller than this (in bytes) */ |
| /* are allocated dynamically */ |
| size_t smallest; /* Smallest mem (in bytes) alloc'd */ |
| size_t largest; /* Larest mem (in bytes) alloc'd */ |
| size_t nbytes; /* Size of allocated block w/ all chunks */ |
| l_int32 nlevels; /* Num of power-of-2 sizes pre-alloc'd */ |
| size_t *sizes; /* Mem sizes at each power-of-2 level */ |
| l_int32 *allocarray; /* Number of mem alloc'd at each size */ |
| l_uint32 *baseptr; /* ptr to allocated array */ |
| l_uint32 *maxptr; /* ptr just beyond allocated memory */ |
| l_uint32 **firstptr; /* array of ptrs to first chunk in size */ |
| l_int32 *memused; /* log: total # of pix used (by level) */ |
| l_int32 *meminuse; /* log: # of pix in use (by level) */ |
| l_int32 *memmax; /* log: max # of pix in use (by level) */ |
| l_int32 *memempty; /* log: # of pix alloc'd because */ |
| /* the store was empty (by level) */ |
| char *logfile; /* log: set to null if no logging */ |
| }; |
| typedef struct PixMemoryStore L_PIX_MEM_STORE; |
| |
| static L_PIX_MEM_STORE *CustomPMS = NULL; |
| |
| |
| /*! |
| * pmsCreate() |
| * |
| * Input: minsize (of data chunk that can be supplied by pms) |
| * smallest (bytes of the smallest pre-allocated data chunk. |
| * numalloc (array with the number of data chunks for each |
| * size that are in the memory store) |
| * logfile (use for debugging; null otherwise) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This computes the size of the block of memory required |
| * and allocates it. Each chunk starts on a 32-bit word boundary. |
| * The chunk sizes are in powers of 2, starting at @smallest, |
| * and the number of levels and chunks at each level is |
| * specified by @numalloc. |
| * (2) This is intended to manage the image data for a small number |
| * of relatively large pix. The system malloc is expected to |
| * handle very large numbers of small chunks efficiently. |
| * (3) Important: set the allocators and call this function |
| * before any pix have been allocated. Destroy all the pix |
| * in the normal way before calling pmsDestroy(). |
| * (4) The pms struct is stored in a static global, so this function |
| * is not thread-safe. When used, there must be only one thread |
| * per process. |
| */ |
| l_int32 |
| pmsCreate(size_t minsize, |
| size_t smallest, |
| NUMA *numalloc, |
| const char *logfile) |
| { |
| l_int32 nlevels, i, j, nbytes; |
| l_int32 *alloca; |
| l_float32 nchunks; |
| l_uint32 *baseptr, *data; |
| l_uint32 **firstptr; |
| size_t *sizes; |
| L_PIX_MEM_STORE *pms; |
| L_PTRA *pa; |
| L_PTRAA *paa; |
| |
| PROCNAME("createPMS"); |
| |
| if (!numalloc) |
| return ERROR_INT("numalloc not defined", procName, 1); |
| numaGetSum(numalloc, &nchunks); |
| if (nchunks > 1000.0) |
| L_WARNING_FLOAT("There are %.0f chunks", procName, nchunks); |
| |
| if ((pms = (L_PIX_MEM_STORE *)CALLOC(1, sizeof(L_PIX_MEM_STORE))) == NULL) |
| return ERROR_INT("pms not made", procName, 1); |
| CustomPMS = pms; |
| |
| /* Make sure that minsize and smallest are multiples of 32 bit words */ |
| if (minsize % 4 != 0) |
| minsize -= minsize % 4; |
| pms->minsize = minsize; |
| nlevels = numaGetCount(numalloc); |
| pms->nlevels = nlevels; |
| |
| if ((sizes = (size_t *)CALLOC(nlevels, sizeof(size_t))) == NULL) |
| return ERROR_INT("sizes not made", procName, 1); |
| pms->sizes = sizes; |
| if (smallest % 4 != 0) |
| smallest += 4 - (smallest % 4); |
| pms->smallest = smallest; |
| for (i = 0; i < nlevels; i++) |
| sizes[i] = smallest * (1 << i); |
| pms->largest = sizes[nlevels - 1]; |
| |
| alloca = numaGetIArray(numalloc); |
| pms->allocarray = alloca; |
| if ((paa = ptraaCreate(nlevels)) == NULL) |
| return ERROR_INT("paa not made", procName, 1); |
| pms->paa = paa; |
| |
| for (i = 0, nbytes = 0; i < nlevels; i++) |
| nbytes += alloca[i] * sizes[i]; |
| pms->nbytes = nbytes; |
| |
| if ((baseptr = (l_uint32 *)CALLOC(nbytes / 4, sizeof(l_uint32))) == NULL) |
| return ERROR_INT("calloc fail for baseptr", procName, 1); |
| pms->baseptr = baseptr; |
| pms->maxptr = baseptr + nbytes / 4; /* just beyond the memory store */ |
| if ((firstptr = (l_uint32 **)CALLOC(nlevels, sizeof(l_uint32 *))) == NULL) |
| return ERROR_INT("calloc fail for firstptr", procName, 1); |
| pms->firstptr = firstptr; |
| |
| data = baseptr; |
| for (i = 0; i < nlevels; i++) { |
| if ((pa = ptraCreate(alloca[i])) == NULL) |
| return ERROR_INT("pa not made", procName, 1); |
| ptraaInsertPtra(paa, i, pa); |
| firstptr[i] = data; |
| for (j = 0; j < alloca[i]; j++) { |
| ptraAdd(pa, data); |
| data += sizes[i] / 4; |
| } |
| } |
| |
| if (logfile) { |
| pms->memused = (l_int32 *)CALLOC(nlevels, sizeof(l_int32)); |
| pms->meminuse = (l_int32 *)CALLOC(nlevels, sizeof(l_int32)); |
| pms->memmax = (l_int32 *)CALLOC(nlevels, sizeof(l_int32)); |
| pms->memempty = (l_int32 *)CALLOC(nlevels, sizeof(l_int32)); |
| pms->logfile = stringNew(logfile); |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pmsDestroy() |
| * |
| * Input: (none) |
| * Return: void |
| * |
| * Notes: |
| * (1) Important: call this function at the end of the program, after |
| * the last pix has been destroyed. |
| */ |
| void |
| pmsDestroy() |
| { |
| L_PIX_MEM_STORE *pms; |
| |
| if ((pms = CustomPMS) == NULL) |
| return; |
| |
| ptraaDestroy(&pms->paa, FALSE, FALSE); /* don't touch the ptrs */ |
| FREE(pms->baseptr); /* free the memory */ |
| |
| if (pms->logfile) { |
| pmsLogInfo(); |
| FREE(pms->logfile); |
| FREE(pms->memused); |
| FREE(pms->meminuse); |
| FREE(pms->memmax); |
| FREE(pms->memempty); |
| } |
| |
| FREE(pms->sizes); |
| FREE(pms->allocarray); |
| FREE(pms->firstptr); |
| FREE(pms); |
| CustomPMS = NULL; |
| return; |
| } |
| |
| |
| /*! |
| * pmsCustomAlloc() |
| * |
| * Input: nbytes (min number of bytes in the chunk to be retrieved) |
| * Return: data (ptr to chunk) |
| * |
| * Notes: |
| * (1) This attempts to find a suitable pre-allocated chunk. |
| * If not found, it dynamically allocates the chunk. |
| * (2) If logging is turned on, the allocations that are not taken |
| * from the memory store, and are at least as large as the |
| * minimum size the store can handle, are logged to file. |
| */ |
| void * |
| pmsCustomAlloc(size_t nbytes) |
| { |
| l_int32 level; |
| void *data; |
| L_PIX_MEM_STORE *pms; |
| L_PTRA *pa; |
| |
| PROCNAME("pmsCustomAlloc"); |
| |
| if ((pms = CustomPMS) == NULL) |
| return (void *)ERROR_PTR("pms not defined", procName, NULL); |
| |
| pmsGetLevelForAlloc(nbytes, &level); |
| |
| if (level < 0) { /* size range invalid; must alloc */ |
| if ((data = pmsGetAlloc(nbytes)) == NULL) |
| return (void *)ERROR_PTR("data not made", procName, NULL); |
| } |
| else { /* get from store */ |
| pa = ptraaGetPtra(pms->paa, level, L_HANDLE_ONLY); |
| data = ptraRemoveLast(pa); |
| if (data && pms->logfile) { |
| pms->memused[level]++; |
| pms->meminuse[level]++; |
| if (pms->meminuse[level] > pms->memmax[level]) |
| pms->memmax[level]++; |
| } |
| if (!data) { /* none left at this level */ |
| data = pmsGetAlloc(nbytes); |
| if (pms->logfile) |
| pms->memempty[level]++; |
| } |
| } |
| |
| return data; |
| } |
| |
| |
| /*! |
| * pmsCustomDealloc() |
| * |
| * Input: data (to be freed or returned to the storage) |
| * Return: void |
| */ |
| void |
| pmsCustomDealloc(void *data) |
| { |
| l_int32 level; |
| FILE *fp; |
| L_PIX_MEM_STORE *pms; |
| L_PTRA *pa; |
| |
| PROCNAME("pmsCustomDealloc"); |
| |
| if ((pms = CustomPMS) == NULL) |
| return ERROR_VOID("pms not defined", procName); |
| |
| if (pmsGetLevelForDealloc(data, &level) == 1) |
| return ERROR_VOID("level not found", procName); |
| |
| if (level < 0) /* no logging; just free the data */ |
| FREE(data); |
| else { /* return the data to the store */ |
| pa = ptraaGetPtra(pms->paa, level, L_HANDLE_ONLY); |
| ptraAdd(pa, data); |
| if (pms->logfile) |
| pms->meminuse[level]--; |
| } |
| |
| return; |
| } |
| |
| |
| /*! |
| * pmsGetAlloc() |
| * |
| * Input: nbytes |
| * Return: data |
| * |
| * Notes: |
| * (1) This is called when a request for pix data cannot be |
| * obtained from the preallocated memory store. After use it |
| * is freed like normal memory. |
| * (2) If logging is on, only write out allocs that are as large as |
| * the minimum size handled by the memory store. |
| */ |
| void * |
| pmsGetAlloc(size_t nbytes) |
| { |
| void *data; |
| FILE *fp; |
| L_PIX_MEM_STORE *pms; |
| |
| PROCNAME("pmsGetAlloc"); |
| |
| if ((pms = CustomPMS) == NULL) |
| return (void *)ERROR_PTR("pms not defined", procName, NULL); |
| |
| if ((data = (void *)CALLOC(nbytes, sizeof(char))) == NULL) |
| return (void *)ERROR_PTR("data not made", procName, NULL); |
| if (pms->logfile && nbytes >= pms->smallest) { |
| fp = fopen(pms->logfile, "a"); |
| fprintf(fp, "Alloc %d bytes at %x\n", nbytes, data); |
| fclose(fp); |
| } |
| |
| return data; |
| } |
| |
| |
| /*! |
| * pmsGetLevelForAlloc() |
| * |
| * Input: nbytes (min number of bytes in the chunk to be retrieved) |
| * &level (<return>; -1 if either too small or too large) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pmsGetLevelForAlloc(size_t nbytes, |
| l_int32 *plevel) |
| { |
| l_int32 i; |
| l_float64 ratio; |
| L_PIX_MEM_STORE *pms; |
| |
| PROCNAME("pmsGetLevelForAlloc"); |
| |
| if (!plevel) |
| return ERROR_INT("&level not defined", procName, 1); |
| *plevel = -1; |
| if ((pms = CustomPMS) == NULL) |
| return ERROR_INT("pms not defined", procName, 1); |
| |
| if (nbytes < pms->minsize || nbytes > pms->largest) |
| return 0; /* -1 */ |
| |
| ratio = (l_float64)nbytes / (l_float64)(pms->smallest); |
| for (i = 0; i < pms->nlevels; i++) { |
| if (ratio <= 1.0) |
| break; |
| ratio /= 2.0; |
| } |
| *plevel = i; |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pmsGetLevelForDealloc() |
| * |
| * Input: data (ptr to memory chunk) |
| * &level (<return> level in memory store; -1 if allocated |
| * outside the store) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pmsGetLevelForDealloc(void *data, |
| l_int32 *plevel) |
| { |
| l_int32 i; |
| l_uint32 *first; |
| L_PIX_MEM_STORE *pms; |
| |
| PROCNAME("pmsGetLevelForDealloc"); |
| |
| if (!plevel) |
| return ERROR_INT("&level not defined", procName, 1); |
| *plevel = -1; |
| if (!data) |
| return ERROR_INT("data not defined", procName, 1); |
| if ((pms = CustomPMS) == NULL) |
| return ERROR_INT("pms not defined", procName, 1); |
| |
| if (data < (void *)pms->baseptr || data >= (void *)pms->maxptr) |
| return 0; /* -1 */ |
| |
| for (i = 1; i < pms->nlevels; i++) { |
| first = pms->firstptr[i]; |
| if (data < (void *)first) |
| break; |
| } |
| *plevel = i - 1; |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pmsLogInfo() |
| * |
| * Input: (none) |
| * Return: void |
| */ |
| void |
| pmsLogInfo() |
| { |
| l_int32 i; |
| L_PIX_MEM_STORE *pms; |
| |
| if ((pms = CustomPMS) == NULL) |
| return; |
| |
| fprintf(stderr, "Total number of pix used at each level\n"); |
| for (i = 0; i < pms->nlevels; i++) |
| fprintf(stderr, " Level %d (%d bytes): %d\n", i, pms->sizes[i], |
| pms->memused[i]); |
| |
| fprintf(stderr, "Max number of pix in use at any time in each level\n"); |
| for (i = 0; i < pms->nlevels; i++) |
| fprintf(stderr, " Level %d (%d bytes): %d\n", i, pms->sizes[i], |
| pms->memmax[i]); |
| |
| fprintf(stderr, "Number of pix alloc'd because none were available\n"); |
| for (i = 0; i < pms->nlevels; i++) |
| fprintf(stderr, " Level %d (%d bytes): %d\n", i, pms->sizes[i], |
| pms->memempty[i]); |
| |
| return; |
| } |
| |
| |