Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow different hash table size for dim/var/attr #132

Merged
merged 4 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions sneak_peek.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,18 @@ This is essentially a placeholder for the next release note ...
+ none

* New PnetCDF hint
+ `nc_hash_size_dim: Set hash table size for dimension names. Default: 256
+ `nc_hash_size_var: Set hash table size for variable names. Default: 256
+ `nc_hash_size_gattr: Set hash table size for global attribute names.
Default: 64
+ `nc_hash_size_vattr: Set hash table size for variable attribute names.
Default: 8
+ The above 4 new hints allow users to set different hash table sizes for
different objects. For instance, when the number of variables to be
defined is large and the number of attributes per variable is small,
increasing `nc_hash_size_var` can speed up the definition time, and
reducing `nc_hash_size_vattr` can reduce the memory footprint. See
[PR #132](https://github.com/Parallel-NetCDF/PnetCDF/pull/132).
+ `nc_header_collective` -- to instruct PnetCDF to call MPI collective APIs to
read and write the file header. The default is "false", meaning the file
header is only read/written by rank 0, using MPI independent read and write
Expand Down Expand Up @@ -179,6 +191,12 @@ This is essentially a placeholder for the next release note ...
+ none

* New test program
+ test/largefile/tst_hash_large_ndims.c - test hashing performance when
the number of dimensions is large.
+ test/largefile/tst_hash_large_nvars.c - test hashing performance when
the number of variables is large.
+ test/largefile/tst_hash_large_ngattr.c - test hashing performance when
the number of global attributes is large.
+ test/largefile/large_header.c - test file header size larger than 2 GiB.
+ test/largefile/large_reqs.c - test a single read/write request of size
larger than 2 GiB.
Expand Down
101 changes: 65 additions & 36 deletions src/drivers/ncmpio/ncmpio_NC.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,26 @@

/* XXX: this seems really low. do we end up spending a ton of time mallocing?
* could we reduce that by increasing this to something 21st century? */
#ifndef NC_ARRAY_GROWBY
#define NC_ARRAY_GROWBY 64
#endif
#define PNC_ARRAY_GROWBY 64

/* The number of attributes per variable is usually small. Thus a smaller
* growby can converse memory, particularly when the number of variables is
* large and the number of attributes per variable is small.
* This does not apply for global attributes, which are usually a lot more.
* PNC_ARRAY_GROWBY is used for global attributes.
*/
#define PNC_VATTR_ARRAY_GROWBY 4

/* ncmpi_create/ncmpi_open set up header to be 'chunksize' big and to grow
* by 'chunksize' as new items added. This used to be 4k. 256k lets us read
* in an entire climate header in one go */
#define NC_DEFAULT_CHUNKSIZE 262144
#define PNC_DEFAULT_CHUNKSIZE 262144

/* default size of temporal buffer to pack noncontiguous user buffers for MPI
* collective read and write during ncmpi_wait/wait_all(). On some systems,
* e.g. Cray KNL, using contiguous user buffers in collective I/O is much
* faster than noncontiguous. */
#define NC_DEFAULT_IBUF_SIZE 16777216
#define PNC_DEFAULT_IBUF_SIZE 16777216

/* when variable's nctype is NC_CHAR, I/O buffer's MPI type must be MPI_CHAR
* and vice versa */
Expand Down Expand Up @@ -89,29 +95,40 @@ typedef enum {

typedef struct NC NC; /* forward reference */

#define NC_NAME_TABLE_CHUNK 16
#define HASH_TABLE_SIZE 256
/* Default hash table sizes.
* They can be user customized by PnetCDF hints: nc_hash_size_dim,
* nc_hash_size_var, nc_hash_size_attr, and nc_hash_size_gattr.
*/
#define PNC_HSIZE_DIM 256
#define PNC_HSIZE_VAR 256
#define PNC_HSIZE_VATTR 8 /* attributes per variable */
#define PNC_HSIZE_GATTR 64 /* global attributes */

/* Array allocation growby for the list of each hash table key */
#define PNC_HLIST_GROWBY 4

/*
#define HASH_FUNC(x) ncmpio_jenkins_one_at_a_time_hash(x)
#define HASH_FUNC(x) ncmpio_jenkins_one_at_a_time_hash(x, hsize)
#define HASH_FUNC(x) ncmpio_additive_hash(x)
#define HASH_FUNC(x) ncmpio_rotating_hash(x)
#define HASH_FUNC(x) ncmpio_Pearson_hash(x)
#define HASH_FUNC(x) ncmpio_rotating_hash(x, hsize)
#define HASH_FUNC(x) ncmpio_Pearson_hash(x, hsize)
*/
#define HASH_FUNC(x) ncmpio_Bernstein_hash(x)
#define HASH_FUNC(x, hsize) ncmpio_Bernstein_hash(x, hsize)
/* Look like Bernstein's hashing function performs the best */

/* For the initial naive implementation of hashing:
* #define HASH_FUNC(x) (unsigned char)x[0]
* if used this simple hash function, HASH_TABLE_SIZE must be 256 which is the
* if used this simple hash function, hash table size must be 256 which is the
* number of possible keys can be stored in an unsigned char
*/

typedef struct NC_nametable {
int num; /* number of elements added in the list array. Its value starts
with zero and incremented with new name is created. When its
value becomes a multiple of NC_NAME_TABLE_CHUNK, list will be
reallocated to a space of size (num + NC_NAME_TABLE_CHUNK) */
int *list; /* dimension or variable IDs */
int num; /* number of elements in the list array. It starts with value
* zero and incremented with new name inserted. When its value
* becomes a multiple of PNC_HASH_TABLE_GROWBY, list will be
* reallocated to a space of size (num + PNC_HASH_TABLE_GROWBY)
*/
int *list; /* dimension, variable, attribute IDs */
} NC_nametable;

/*
Expand All @@ -137,8 +154,10 @@ typedef struct NC_dimarray {
int ndefined; /* number of defined dimensions */
int unlimited_id; /* -1 for not defined, otherwise >= 0 */
NC_dim **value;
NC_nametable nameT[HASH_TABLE_SIZE]; /* table for quick name lookup.
* indices 0, 1, ... HASH_TABLE_SIZE-1 are the hash keys.
int hash_size;
NC_nametable *nameT;
/* Hash table for quick name lookup.
* indices 0, 1, ... hash_size-1 are the hash keys.
* nameT[i].num is the number of hash collisions. The IDs of
* dimensions with names producing the same hash key i are
* stored in nameT[i].list[*]
Expand Down Expand Up @@ -177,8 +196,10 @@ typedef struct {
typedef struct NC_attrarray {
int ndefined; /* number of defined attributes */
NC_attr **value;
NC_nametable nameT[HASH_TABLE_SIZE]; /* table for quick name lookup.
* indices 0, 1, ... HASH_TABLE_SIZE-1 are the hash keys.
int hash_size;
NC_nametable *nameT;
/* Hash table for quick name lookup.
* indices 0, 1, ... hash_size-1 are the hash keys.
* nameT[i].num is the number of hash collisions. The IDs of
* variables with names producing the same hash key i are
* stored in nameT[i].list[*]
Expand Down Expand Up @@ -245,8 +266,10 @@ typedef struct NC_vararray {
int ndefined; /* number of defined variables */
int num_rec_vars;/* number of defined record variables */
NC_var **value;
NC_nametable nameT[HASH_TABLE_SIZE]; /* table for quick name lookup.
* indices 0, 1, ... HASH_TABLE_SIZE-1 are the hash keys.
int hash_size;
NC_nametable *nameT;
/* Hash table for quick name lookup.
* indices 0, 1, ... hash_size-1 are the hash keys.
* nameT[i].num is the number of hash collisions. The IDs of
* variables with names producing the same hash key i are
* stored in nameT[i].list[*]
Expand All @@ -264,7 +287,8 @@ extern void
ncmpio_free_NC_vararray(NC_vararray *ncap);

extern int
ncmpio_dup_NC_vararray(NC_vararray *ncap, const NC_vararray *ref);
ncmpio_dup_NC_vararray(NC_vararray *ncap, const NC_vararray *ref,
int attr_hsize);

extern int
ncmpio_NC_var_shape64(NC_var *varp, const NC_dimarray *dims);
Expand Down Expand Up @@ -391,6 +415,8 @@ struct NC {
NC_attrarray attrs; /* global attributes defined */
NC_vararray vars; /* variables defined */

int hash_size_attr; /* hash table size for non-global attributes */

int maxGetReqID; /* max get request ID */
int maxPutReqID; /* max put request ID */
int numLeadGetReqs; /* number of pending lead get requests */
Expand Down Expand Up @@ -504,45 +530,48 @@ ncmpio_igetput_varm(NC *ncp, NC_var *varp, const MPI_Offset *start,

/* Begin defined in ncmpio_hash_func.c --------------------------------------*/
extern int
ncmpio_jenkins_one_at_a_time_hash(const char *str_name);
ncmpio_jenkins_one_at_a_time_hash(const char *str_name, int hash_size);

extern int
ncmpio_additive_hash(const char *str_name);

extern int
ncmpio_rotating_hash(const char *str_name);
ncmpio_rotating_hash(const char *str_name, int hash_size);

extern int
ncmpio_Bernstein_hash(const char *str_name);
ncmpio_Bernstein_hash(const char *str_name, int hsize);

extern int
ncmpio_Pearson_hash(const char *str_name);
ncmpio_Pearson_hash(const char *str_name, int hsize);

extern int
ncmpio_update_name_lookup_table(NC_nametable *nameT, const int id,
ncmpio_update_name_lookup_table(NC_nametable *nameT, int hash_size, int id,
const char *oldname, const char *newname);

extern void
ncmpio_hash_insert(NC_nametable *nameT, const char *name, int id);
ncmpio_hash_insert(NC_nametable *nameT, int hash_size, const char *name,
int id);

extern int
ncmpio_hash_delete(NC_nametable *nameT, const char *name, int id);
ncmpio_hash_delete(NC_nametable *nameT, int hash_size, const char *name,
int id);

extern int
ncmpio_hash_replace(NC_nametable *nameT, const char *old_name,
ncmpio_hash_replace(NC_nametable *nameT, int hash_size, const char *old_name,
const char *new_name, int id);

extern void
ncmpio_hash_table_copy(NC_nametable *dest, const NC_nametable *src);
ncmpio_hash_table_copy(NC_nametable *dest, const NC_nametable *src,
int hash_size);

extern void
ncmpio_hash_table_free(NC_nametable *nameT);
ncmpio_hash_table_free(NC_nametable *nameT, int hash_size);

extern void
ncmpio_hash_table_populate_NC_var(NC_vararray *varsp);
ncmpio_hash_table_populate_NC_var(NC_vararray *varsp, int hash_size);

extern void
ncmpio_hash_table_populate_NC_dim(NC_dimarray *dimsp);
ncmpio_hash_table_populate_NC_dim(NC_dimarray *dimsp, int hash_size);

extern void
ncmpio_hash_table_populate_NC_attr(NC *ncp);
Expand Down
47 changes: 34 additions & 13 deletions src/drivers/ncmpio/ncmpio_attr.m4
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,12 @@ ncmpio_free_NC_attrarray(NC_attrarray *ncap)

#ifndef SEARCH_NAME_LINEARLY
/* free space allocated for attribute name lookup table */
ncmpio_hash_table_free(ncap->nameT);
if (ncap->nameT != NULL) {
ncmpio_hash_table_free(ncap->nameT, ncap->hash_size);
NCI_Free(ncap->nameT);
ncap->nameT = NULL;
ncap->hash_size = 0;
}
#endif
}

Expand All @@ -183,7 +188,7 @@ ncmpio_dup_NC_attrarray(NC_attrarray *ncap, const NC_attrarray *ref)
}

if (ref->ndefined > 0) {
size_t alloc_size = _RNDUP(ref->ndefined, NC_ARRAY_GROWBY);
size_t alloc_size = _RNDUP(ref->ndefined, PNC_ARRAY_GROWBY);
ncap->value = (NC_attr **) NCI_Calloc(alloc_size, sizeof(NC_attr*));
if (ncap->value == NULL) DEBUG_RETURN_ERROR(NC_ENOMEM)
}
Expand All @@ -201,8 +206,12 @@ ncmpio_dup_NC_attrarray(NC_attrarray *ncap, const NC_attrarray *ref)
assert(ncap->ndefined == ref->ndefined);

#ifndef SEARCH_NAME_LINEARLY
/* allocate hashing lookup table, if not allocated yet */
if (ncap->nameT == NULL)
ncap->nameT = NCI_Calloc(ncap->hash_size, sizeof(NC_nametable));

/* duplicate attribute name lookup table */
ncmpio_hash_table_copy(ncap->nameT, ref->nameT);
ncmpio_hash_table_copy(ncap->nameT, ref->nameT, ncap->hash_size);
#endif

return NC_NOERR;
Expand All @@ -211,14 +220,16 @@ ncmpio_dup_NC_attrarray(NC_attrarray *ncap, const NC_attrarray *ref)
/*----< incr_NC_attrarray() >------------------------------------------------*/
/* Add a new handle at the end of an array of handles */
static int
incr_NC_attrarray(NC_attrarray *ncap, NC_attr *new_attr)
incr_NC_attrarray(int isGlobal, NC_attrarray *ncap, NC_attr *new_attr)
{
size_t growby = (isGlobal) ? PNC_ARRAY_GROWBY : PNC_VATTR_ARRAY_GROWBY;

assert(ncap != NULL);
assert(new_attr != NULL);

if (ncap->ndefined % NC_ARRAY_GROWBY == 0) {
if (ncap->ndefined % growby == 0) {
/* grow the array to accommodate the new handle */
size_t alloc_size = (size_t)ncap->ndefined + NC_ARRAY_GROWBY;
size_t alloc_size = (size_t)ncap->ndefined + growby;

ncap->value = (NC_attr **) NCI_Realloc(ncap->value,
alloc_size * sizeof(NC_attr*));
Expand Down Expand Up @@ -279,7 +290,7 @@ ncmpio_NC_findattr(const NC_attrarray *ncap,
}
#else
/* hash name into a key for name lookup */
key = HASH_FUNC(name);
key = HASH_FUNC(name, ncap->hash_size);

/* check the list (all names sharing the same key) using linear search */
nchars = strlen(name);
Expand Down Expand Up @@ -489,7 +500,7 @@ err_check:
assert(attrp != NULL);

#ifndef SEARCH_NAME_LINEARLY
ncmpio_hash_replace(ncap->nameT, attrp->name, nnewname, attr_id);
ncmpio_hash_replace(ncap->nameT, ncap->hash_size, attrp->name, nnewname, attr_id);
#endif

/* replace the old name with new name */
Expand Down Expand Up @@ -634,10 +645,15 @@ err_check:
if (err != NC_NOERR) return err;

#ifndef SEARCH_NAME_LINEARLY
ncmpio_hash_insert(ncap_out->nameT, nname, ncap_out->ndefined);
/* allocate hashing lookup table, if not allocated yet */
if (ncap_out->nameT == NULL)
ncap_out->nameT = NCI_Calloc(ncap_out->hash_size, sizeof(NC_nametable));

/* insert nname to the lookup table */
ncmpio_hash_insert(ncap_out->nameT, ncap_out->hash_size, nname, ncap_out->ndefined);
#endif

err = incr_NC_attrarray(ncap_out, attrp);
err = incr_NC_attrarray((varid_out == NC_GLOBAL), ncap_out, attrp);
if (err != NC_NOERR) return err;
}

Expand Down Expand Up @@ -688,7 +704,7 @@ ncmpio_del_att(void *ncdp,

#ifndef SEARCH_NAME_LINEARLY
/* delete name entry from hash table */
err = ncmpio_hash_delete(ncap->nameT, nname, attrid);
err = ncmpio_hash_delete(ncap->nameT, ncap->hash_size, nname, attrid);
if (err != NC_NOERR) goto err_check;
#endif

Expand Down Expand Up @@ -1068,10 +1084,15 @@ err_check:
if (err != NC_NOERR) return err;

#ifndef SEARCH_NAME_LINEARLY
ncmpio_hash_insert(ncap->nameT, nname, ncap->ndefined);
/* allocate hashing lookup table, if not allocated yet */
if (ncap->nameT == NULL)
ncap->nameT = NCI_Calloc(ncap->hash_size, sizeof(NC_nametable));

/* insert nname to the lookup table */
ncmpio_hash_insert(ncap->nameT, ncap->hash_size, nname, ncap->ndefined);
#endif

err = incr_NC_attrarray(ncap, attrp);
err = incr_NC_attrarray((varid == NC_GLOBAL), ncap, attrp);
if (err != NC_NOERR) return err;
}

Expand Down
4 changes: 2 additions & 2 deletions src/drivers/ncmpio/ncmpio_create.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ ncmpio_create(MPI_Comm comm,
ncp->ncid = ncid;

/* chunk size for reading header, set to default before check hints */
ncp->chunk = NC_DEFAULT_CHUNKSIZE;
ncp->chunk = PNC_DEFAULT_CHUNKSIZE;

/* calculate the true header size (not-yet aligned)
* No need to do this now.
Expand All @@ -279,7 +279,7 @@ ncmpio_create(MPI_Comm comm,
ncp->dims.unlimited_id = -1;

/* buffer to pack noncontiguous user buffers when calling wait() */
ncp->ibuf_size = NC_DEFAULT_IBUF_SIZE;
ncp->ibuf_size = PNC_DEFAULT_IBUF_SIZE;

/* Extract PnetCDF specific I/O hints from user_info and set default hint
* values into info_used. Note some MPI libraries, such as MPICH 3.3.1 and
Expand Down
Loading
Loading