Avoid statically allocating formatting.c's format string caches.

This eliminates circa 120KB of static data from Postgres' memory
footprint.  In some usage patterns that space will get allocated
anyway, but in many processes it never will be allocated.

We can improve matters further by allocating only as many cache
entries as we actually use, rather than allocating the whole array
on first use.  However, to avoid wasting lots of space due to
palloc's habit of rounding requests up to power-of-2 sizes, tweak
the maximum cacheable format string length to make the struct sizes
be powers of 2 or just less.  The sizes I chose make the maximums
a little bit less than they were before, but I doubt it matters much.

While at it, rearrange struct FormatNode to avoid wasting quite so
much padding space.  This change actually halves the size of that
struct on 64-bit machines.

Discussion: https://postgr.es/m/20181015200754.7y7zfuzsoux2c4ya@alap3.anarazel.de
This commit is contained in:
Tom Lane 2018-10-16 13:11:05 -04:00
parent 02a30a09f9
commit fd85e9f78d

View File

@ -93,6 +93,7 @@
#include "utils/float.h"
#include "utils/formatting.h"
#include "utils/int8.h"
#include "utils/memutils.h"
#include "utils/numeric.h"
#include "utils/pg_locale.h"
@ -124,7 +125,7 @@
*/
typedef struct
{
char *name; /* suffix string */
const char *name; /* suffix string */
int len, /* suffix length */
id, /* used in node->suffix */
type; /* prefix / postfix */
@ -155,10 +156,10 @@ typedef struct
typedef struct
{
int type; /* NODE_TYPE_XXX, see below */
const KeyWord *key; /* if type is ACTION */
uint8 type; /* NODE_TYPE_XXX, see below */
char character[MAX_MULTIBYTE_CHAR_LEN + 1]; /* if type is CHAR */
int suffix; /* keyword prefix/suffix code, if any */
uint8 suffix; /* keyword prefix/suffix code, if any */
const KeyWord *key; /* if type is ACTION */
} FormatNode;
#define NODE_TYPE_END 1
@ -358,14 +359,27 @@ typedef struct
* For simplicity, the cache entries are fixed-size, so they allow for the
* worst case of a FormatNode for each byte in the picture string.
*
* The max number of entries in the caches is DCH_CACHE_ENTRIES
* The CACHE_SIZE constants are computed to make sizeof(DCHCacheEntry) and
* sizeof(NUMCacheEntry) be powers of 2, or just less than that, so that
* we don't waste too much space by palloc'ing them individually. Be sure
* to adjust those macros if you add fields to those structs.
*
* The max number of entries in each cache is DCH_CACHE_ENTRIES
* resp. NUM_CACHE_ENTRIES.
* ----------
*/
#define NUM_CACHE_SIZE 64
#define NUM_CACHE_ENTRIES 20
#define DCH_CACHE_SIZE 128
#define DCH_CACHE_OVERHEAD \
MAXALIGN(sizeof(bool) + sizeof(int))
#define NUM_CACHE_OVERHEAD \
MAXALIGN(sizeof(bool) + sizeof(int) + sizeof(NUMDesc))
#define DCH_CACHE_SIZE \
((2048 - DCH_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)
#define NUM_CACHE_SIZE \
((1024 - NUM_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)
#define DCH_CACHE_ENTRIES 20
#define NUM_CACHE_ENTRIES 20
typedef struct
{
@ -385,12 +399,12 @@ typedef struct
} NUMCacheEntry;
/* global cache for date/time format pictures */
static DCHCacheEntry DCHCache[DCH_CACHE_ENTRIES];
static DCHCacheEntry *DCHCache[DCH_CACHE_ENTRIES];
static int n_DCHCache = 0; /* current number of entries */
static int DCHCounter = 0; /* aging-event counter */
/* global cache for number format pictures */
static NUMCacheEntry NUMCache[NUM_CACHE_ENTRIES];
static NUMCacheEntry *NUMCache[NUM_CACHE_ENTRIES];
static int n_NUMCache = 0; /* current number of entries */
static int NUMCounter = 0; /* aging-event counter */
@ -496,7 +510,7 @@ do { \
*****************************************************************************/
/* ----------
* Suffixes:
* Suffixes (FormatNode.suffix is an OR of these codes)
* ----------
*/
#define DCH_S_FM 0x01
@ -3368,13 +3382,13 @@ DCH_cache_getnew(const char *str)
{
DCHCacheEntry *ent;
/* counter overflow check - paranoia? */
/* handle counter overflow by resetting all ages */
if (DCHCounter >= (INT_MAX - DCH_CACHE_ENTRIES))
{
DCHCounter = 0;
for (ent = DCHCache; ent < (DCHCache + DCH_CACHE_ENTRIES); ent++)
ent->age = (++DCHCounter);
for (int i = 0; i < n_DCHCache; i++)
DCHCache[i]->age = (++DCHCounter);
}
/*
@ -3382,15 +3396,16 @@ DCH_cache_getnew(const char *str)
*/
if (n_DCHCache >= DCH_CACHE_ENTRIES)
{
DCHCacheEntry *old = DCHCache + 0;
DCHCacheEntry *old = DCHCache[0];
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache);
#endif
if (old->valid)
{
for (ent = DCHCache + 1; ent < (DCHCache + DCH_CACHE_ENTRIES); ent++)
for (int i = 1; i < DCH_CACHE_ENTRIES; i++)
{
ent = DCHCache[i];
if (!ent->valid)
{
old = ent;
@ -3414,7 +3429,9 @@ DCH_cache_getnew(const char *str)
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache);
#endif
ent = DCHCache + n_DCHCache;
Assert(DCHCache[n_DCHCache] == NULL);
DCHCache[n_DCHCache] = ent = (DCHCacheEntry *)
MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
ent->valid = false;
StrNCpy(ent->str, str, DCH_CACHE_SIZE + 1);
ent->age = (++DCHCounter);
@ -3428,20 +3445,19 @@ DCH_cache_getnew(const char *str)
static DCHCacheEntry *
DCH_cache_search(const char *str)
{
int i;
DCHCacheEntry *ent;
/* counter overflow check - paranoia? */
/* handle counter overflow by resetting all ages */
if (DCHCounter >= (INT_MAX - DCH_CACHE_ENTRIES))
{
DCHCounter = 0;
for (ent = DCHCache; ent < (DCHCache + DCH_CACHE_ENTRIES); ent++)
ent->age = (++DCHCounter);
for (int i = 0; i < n_DCHCache; i++)
DCHCache[i]->age = (++DCHCounter);
}
for (i = 0, ent = DCHCache; i < n_DCHCache; i++, ent++)
for (int i = 0; i < n_DCHCache; i++)
{
DCHCacheEntry *ent = DCHCache[i];
if (ent->valid && strcmp(ent->str, str) == 0)
{
ent->age = (++DCHCounter);
@ -4047,13 +4063,13 @@ NUM_cache_getnew(const char *str)
{
NUMCacheEntry *ent;
/* counter overflow check - paranoia? */
/* handle counter overflow by resetting all ages */
if (NUMCounter >= (INT_MAX - NUM_CACHE_ENTRIES))
{
NUMCounter = 0;
for (ent = NUMCache; ent < (NUMCache + NUM_CACHE_ENTRIES); ent++)
ent->age = (++NUMCounter);
for (int i = 0; i < n_NUMCache; i++)
NUMCache[i]->age = (++NUMCounter);
}
/*
@ -4061,15 +4077,16 @@ NUM_cache_getnew(const char *str)
*/
if (n_NUMCache >= NUM_CACHE_ENTRIES)
{
NUMCacheEntry *old = NUMCache + 0;
NUMCacheEntry *old = NUMCache[0];
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache);
#endif
if (old->valid)
{
for (ent = NUMCache + 1; ent < (NUMCache + NUM_CACHE_ENTRIES); ent++)
for (int i = 1; i < NUM_CACHE_ENTRIES; i++)
{
ent = NUMCache[i];
if (!ent->valid)
{
old = ent;
@ -4093,7 +4110,9 @@ NUM_cache_getnew(const char *str)
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache);
#endif
ent = NUMCache + n_NUMCache;
Assert(NUMCache[n_NUMCache] == NULL);
NUMCache[n_NUMCache] = ent = (NUMCacheEntry *)
MemoryContextAllocZero(TopMemoryContext, sizeof(NUMCacheEntry));
ent->valid = false;
StrNCpy(ent->str, str, NUM_CACHE_SIZE + 1);
ent->age = (++NUMCounter);
@ -4107,20 +4126,19 @@ NUM_cache_getnew(const char *str)
static NUMCacheEntry *
NUM_cache_search(const char *str)
{
int i;
NUMCacheEntry *ent;
/* counter overflow check - paranoia? */
/* handle counter overflow by resetting all ages */
if (NUMCounter >= (INT_MAX - NUM_CACHE_ENTRIES))
{
NUMCounter = 0;
for (ent = NUMCache; ent < (NUMCache + NUM_CACHE_ENTRIES); ent++)
ent->age = (++NUMCounter);
for (int i = 0; i < n_NUMCache; i++)
NUMCache[i]->age = (++NUMCounter);
}
for (i = 0, ent = NUMCache; i < n_NUMCache; i++, ent++)
for (int i = 0; i < n_NUMCache; i++)
{
NUMCacheEntry *ent = NUMCache[i];
if (ent->valid && strcmp(ent->str, str) == 0)
{
ent->age = (++NUMCounter);