Allocate cache entries from memory pool
cover Allocate cache entries from memory pool
This patch series improves the performance of loading indexes by reducing the number of malloc() calls. Loading the index from disk is partly dominated by the time in malloc(), which is called for each index entry. This patch series reduces the number of times malloc() is called as part of loading the index, and instead allocates a block of memory upfront that is large enough to hold all of the cache entries, and chunks this memory itself. This change builds on [1], which is a prerequisite for this change.
Git previously allocated block of memory for the index cache entries until [2].
This 5 part patch series is broken up as follows:
1/5, 2/5 - Move cache entry lifecycle methods behind an API
3/5 - Fill out memory pool API to include lifecycle and other
methods used in later patches
4/5 - Allocate cache entry structs from memory pool
5/5 - Add extra optional validation
Performance Benchmarks:
To evaluate the performance of this approach, the p0002-read-cache.sh test was run with several combinations of allocators (glibc default, tcmalloc, jemalloc), with and without block allocation, and across several different index sized (100K, 1M, 2M entries). The details on how these repositories were constructed can be found in [3].The p0002-read-cache.sh was run with the iteration count set to 1 and $GIT_PERF_REPEAT_COUNT=10.
The tests were run with iteration count set to 1 because this best approximates the real behavior. The read_cache/discard_cache test will load / free the index N times, and the performance of this logic is different between N = 1 and N > 1. As the production code does not read / discard the index in a loop, a better approximation is when N = 1.
100K
Test baseline [4] block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.02(0.01+0.01) -33.3%
1M:
Test baseline block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.17(0.07+0.09) -26.1%
2M:
Test baseline block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.39(0.17+0.20) -13.3%
100K is not a large enough sample size to show the perf impact of this change, but we can see a perf improvement with 1M and 2M entries. For completeness, here is the p0002-read-cache tests for git.git and linux.git:
git.git:
Test baseline block_allocation
---------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1000 times 0.30(0.26+0.03) 0.17(0.13+0.03) -43.3%
linux.git:
Test baseline block_allocation
---------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1000 times 7.05(6.01+0.84) 4.61(3.74+0.66) -34.6%
We also investigated the performance of just using different allocators. We can see that there is not a consistent performance gain.
100K
Test baseline [4] tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.03(0.01+0.01) +0.0% 0.03(0.02+0.01) +0.0%
1M:
Test baseline tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.21(0.10+0.10) -8.7% 0.27(0.16+0.10) +17.4%
2M:
Test baseline tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.46(0.25+0.21) +2.2% 0.57(0.36+0.21) +26.7%
[1] https://public-inbox.org/git/[email protected]/
[2] debed2a629 (read-cache.c: allocate index entries individually - 2011-10-24)
[3] Constructing test repositories:
The test repositories were constructed with t/perf/repos/many_files.sh with the following parameters:
100K: many-files.sh 4 10 9
1M: many-files.sh 5 10 9
2M: many-files.sh 6 8 7
[4] baseline commit: 8b026eda Revert "Merge branch 'en/rename-directory-detection'"
Jameson Miller (5):
read-cache: teach refresh_cache_entry to take istate Add an API creating / discarding cache_entry structs
mem-pool: fill out functionality
Allocate cache entries from memory pools
Add optional memory validations around cache_entry lifecyle
apply.c | 26 +++--- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 8 +- builtin/reset.c | 6 +- builtin/update-index.c | 26 +++--- cache.h | 40 ++++++++- git.c | 3 + mem-pool.c | 136 ++++++++++++++++++++++++++++- mem-pool.h | 34 ++++++++ merge-recursive.c | 4 +- read-cache.c | 229 +++++++++++++++++++++++++++++++++++++++---------- resolve-undo.c | 6 +- split-index.c | 31 +++++-- tree.c | 4 +- unpack-trees.c | 27 +++--- 16 files changed, 476 insertions(+), 117 deletions(-)
base-commit: cafaccae98f749ebf33495aec42ea25060de8682
Signature · 1 line
2.14.3
cover Allocate cache entries from memory pool
This patch series improves the performance of loading indexes by reducing the number of malloc() calls. Loading the index from disk is partly dominated by the time in malloc(), which is called for each index entry. This patch series reduces the number of times malloc() is called as part of loading the index, and instead allocates a block of memory upfront that is large enough to hold all of the cache entries, and chunks this memory itself. This change builds on [1], which is a prerequisite for this change.
Git previously allocated block of memory for the index cache entries until [2].
This 5 part patch series is broken up as follows:
1/5, 2/5 - Move cache entry lifecycle methods behind an API
3/5 - Fill out memory pool API to include lifecycle and other
methods used in later patches
4/5 - Allocate cache entry structs from memory pool
5/5 - Add extra optional validation
Performance Benchmarks:
To evaluate the performance of this approach, the p0002-read-cache.sh test was run with several combinations of allocators (glibc default, tcmalloc, jemalloc), with and without block allocation, and across several different index sized (100K, 1M, 2M entries). The details on how these repositories were constructed can be found in [3].The p0002-read-cache.sh was run with the iteration count set to 1 and $GIT_PERF_REPEAT_COUNT=10.
The tests were run with iteration count set to 1 because this best approximates the real behavior. The read_cache/discard_cache test will load / free the index N times, and the performance of this logic is different between N = 1 and N > 1. As the production code does not read / discard the index in a loop, a better approximation is when N = 1.
100K
Test baseline [4] block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.02(0.01+0.01) -33.3%
1M:
Test baseline block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.17(0.07+0.09) -26.1%
2M:
Test baseline block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.39(0.17+0.20) -13.3%
100K is not a large enough sample size to show the perf impact of this change, but we can see a perf improvement with 1M and 2M entries.
For completeness, here is the p0002-read-cache tests for git.git and linux.git:
git.git:
Test baseline [4] block_allocation
---------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1000 times 0.30(0.26+0.03) 0.17(0.13+0.03) -43.3%
linux.git:
Test baseline block_allocation
---------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1000 times 7.05(6.01+0.84) 4.61(3.74+0.66) -34.6%
We also investigated the performance of just using different allocators. We can see that there is not a consistent performance gain.
100K
Test baseline [4] tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.03(0.01+0.01) +0.0% 0.03(0.02+0.01) +0.0%
1M:
Test baseline tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.21(0.10+0.10) -8.7% 0.27(0.16+0.10) +17.4%
2M:
Test baseline tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.46(0.25+0.21) +2.2% 0.57(0.36+0.21) +26.7%
[1] https://public-inbox.org/git/[email protected]/
[2] debed2a629 (read-cache.c: allocate index entries individually - 2011-10-24)
[3] Constructing test repositories:
The test repositories were constructed with t/perf/repos/many_files.sh with the following parameters:
100K: many-files.sh 4 10 9
1M: many-files.sh 5 10 9
2M: many-files.sh 6 8 7
[4] baseline commit: 8b026eda Revert "Merge branch 'en/rename-directory-detection'"
Jameson Miller (5):
read-cache: teach refresh_cache_entry to take istate Add an API creating / discarding cache_entry structs
mem-pool: fill out functionality
Allocate cache entries from memory pools
Add optional memory validations around cache_entry lifecyle
apply.c | 26 +++--- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 8 +- builtin/reset.c | 6 +- builtin/update-index.c | 26 +++--- cache.h | 40 ++++++++- git.c | 3 + mem-pool.c | 136 ++++++++++++++++++++++++++++- mem-pool.h | 34 ++++++++ merge-recursive.c | 4 +- read-cache.c | 229 +++++++++++++++++++++++++++++++++++++++---------- resolve-undo.c | 6 +- split-index.c | 31 +++++-- tree.c | 4 +- unpack-trees.c | 27 +++--- 16 files changed, 476 insertions(+), 117 deletions(-)
base-commit: cafaccae98f749ebf33495aec42ea25060de8682
Signature · 1 line
2.14.3
PATCH 1/5 read-cache: teach refresh_cache_entry to take istate
In reply to Jameson MillerRefactoring dependencies of make_cache_entry to work on a specific index, instead of implicitly using the_index. This is in preparation for making the make_cache_entry function work on a specific index.
--- cache.h | 2 +- merge-recursive.c | 2 +- read-cache.c | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-)
cache.h +1 -1
@@ -743,7 +743,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);#define REFRESH_IGNORE_SUBMODULES 0x0010/* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020/* user friendly output, not "needs update" */ extern int refresh_index (struct index_state *,unsigned int flags,const struct pathspec *pathspec,char *seen,const char *header_msg); -extern struct cache_entry *refresh_cache_entry (struct cache_entry *,unsigned int ); +extern struct cache_entry *refresh_cache_entry (struct index_state *,struct cache_entry *,unsigned int );/* * Opportunistically update the index but do not complain if we can't.
merge-recursive.c +1 -1
@@ -260,7 +260,7 @@ static int add_cacheinfo(struct merge_options *o,if (refresh) {struct cache_entry *nce; - nce =refresh_cache_entry (ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING ); + nce =refresh_cache_entry (&the_index, ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING );if (!nce)return err (o,_ ("addinfo_cache failed for path '%s'" ), path);if (nce != ce)
read-cache.c +4 -3
@@ -767,7 +767,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, ce->ce_namelen = len; ce->ce_mode =create_ce_mode (mode); - ret =refresh_cache_entry (ce, refresh_options); + ret =refresh_cache_entry (&the_index, ce, refresh_options);if (ret != ce)free (ce);return ret;
@@ -1448,10 +1448,11 @@ int refresh_index(struct index_state *istate, unsigned int flags,return has_errors; } -struct cache_entry *refresh_cache_entry (struct cache_entry *ce, +struct cache_entry *refresh_cache_entry (struct index_state *istate, +struct cache_entry *ce,unsigned int options) { -return refresh_cache_ent (&the_index, ce, options,NULL ,NULL ); +return refresh_cache_ent (istate, ce, options,NULL ,NULL ); }
Signature · 1 line
2.14.3
Re: [PATCH v1 1/5] read-cache: teach refresh_cache_entry to take istate
Quoted 1 line · Jameson Miller
Refactoring dependencies of
make_cache_entryto work on a specific
Refactoring/Refactor
It's helpful to refer to functions with parens i.e. make_cache_entry().
In addition, it appears you only needed to update refresh_cache_entry() so perhaps something like:
Refactor refresh_cache_entry() to work on a specific index, instead of implicitly using the_index. This is in preparation for making the make_cache_entry() function work on a specific index.
Also, be sure to certify your work by adding your "Signed-off-by: " line to each commit. Details at:
https://github.com/git/git/blob/master/Documentation/SubmittingPatches
Quoted 2 lines
index, instead of implicitly using
the_index. This is in preparation for making themake_cache_entryfunction work on a specific index.
PATCH 2/5 Add an API creating / discarding cache_entry structs
In reply to Jameson MillerAdd an API around managing the lifetime of cache_entry structs. Abstracting memory management details behind an API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated / freed. A later commit in this series will allocate cache entries from memory pools as appropriate.
Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated by malloc() calls. This change is in preparation for using memory pools to reduce the number of malloc() calls made when loading an index.
This API makes a distinction between cache entries that are intended for use with a particular to an index and cache entries that are not. This enables us to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
--- apply.c | 26 ++++++------ blame.c | 5 +-- builtin/checkout.c | 8 ++-- builtin/difftool.c | 8 ++-- builtin/reset.c | 6 +-- builtin/update-index.c | 26 ++++++------ cache.h | 29 +++++++++++++- merge-recursive.c | 2 +- read-cache.c | 105 +++++++++++++++++++++++++++++++++++-------------- resolve-undo.c | 6 ++- split-index.c | 8 ++-- tree.c | 4 +- unpack-trees.c | 27 ++++++++----- 13 files changed, 166 insertions(+), 94 deletions(-)
apply.c +12 -14
@@ -4090,12 +4090,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, oid.hash, name,0 ,0 ); + ce =make_index_cache_entry (&result, patch->old_mode, oid.hash, name,0 ,0 );if (!ce) -return error (_ ("make_cache_entry failed for path '%s'" ), +return error (_ ("make_index_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +index_cache_entry_discard (ce);return error (_ ("could not add %s to temporary index" ), name); }
@@ -4263,12 +4263,11 @@ static int add_index_file(struct apply_state *state,struct stat st;struct cache_entry *ce;int namelen =strlen (path); -unsigned ce_size =cache_entry_size (namelen);if (!state->update_index)return 0 ; - ce =xcalloc (1 , ce_size); + ce =make_empty_index_cache_entry (&the_index, namelen);memcpy (ce->name, path, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (0 );
@@ -4278,13 +4277,13 @@ static int add_index_file(struct apply_state *state,if (!skip_prefix (buf,"Subproject commit " , &s) ||get_oid_hex (s, &ce->oid)) { -free (ce); -return error (_ ("corrupt patch for submodule %s" ), path); +index_cache_entry_discard (ce); +return error (_ ("corrupt patch for submodule %s" ), path); } }else {if (!state->cached) {if (lstat (path, &st) <0 ) { -free (ce); +index_cache_entry_discard (ce);return error_errno (_ ("unable to stat newly " "created file '%s'" ), path);
@@ -4292,13 +4291,13 @@ static int add_index_file(struct apply_state *state,fill_stat_cache_info (ce, &st); }if (write_object_file (buf, size, blob_type, &ce->oid) <0 ) { -free (ce); +index_cache_entry_discard (ce);return error (_ ("unable to create backing store " "for newly created file %s" ), path); } }if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +index_cache_entry_discard (ce);return error (_ ("unable to add cache entry for %s" ), path); }
@@ -4422,27 +4421,26 @@ static int add_conflicted_stages_file(struct apply_state *state, struct patch *patch) {int stage, namelen; -unsigned ce_size, mode; +unsigned mode;struct cache_entry *ce;if (!state->update_index)return 0 ; namelen =strlen (patch->new_name); - ce_size =cache_entry_size (namelen); mode = patch->new_mode ? patch->new_mode : (S_IFREG |0644 );remove_file_from_cache (patch->new_name);for (stage =1 ; stage <4 ; stage++) {if (is_null_oid (&patch->threeway_stage[stage -1 ]))continue ; - ce =xcalloc (1 , ce_size); + ce =make_empty_index_cache_entry (&the_index, namelen);memcpy (ce->name, patch->new_name, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = namelen;oidcpy (&ce->oid, &patch->threeway_stage[stage -1 ]);if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +index_cache_entry_discard (ce);return error (_ ("unable to add cache entry for %s" ), patch->new_name); }
blame.c +2 -3
@@ -154,7 +154,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,struct strbuf buf =STRBUF_INIT ;const char *ident;time_t now; -int size, len; +int len;struct cache_entry *ce;unsigned mode;struct strbuf msg =STRBUF_INIT ;
@@ -252,8 +252,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,/* Let's not bother reading from HEAD tree */ mode =S_IFREG |0644 ; } - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, &origin->blob_oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 );
builtin/checkout.c +4 -4
@@ -77,7 +77,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,return READ_TREE_RECURSIVE ; len = base->len +strlen (pathname); - ce =xcalloc (1 ,cache_entry_size (len)); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, base->buf, base->len);memcpy (ce->name + base->len, pathname, len - base->len);
@@ -96,7 +96,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,if (ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { old->ce_flags |=CE_UPDATE ; -free (ce); +index_cache_entry_discard (ce);return 0 ; } }
@@ -230,11 +230,11 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, oid.hash, path,2 ,0 ); + ce =make_transient_cache_entry (mode, oid.hash, path,2 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL ); -free (ce); +transient_cache_entry_discard (ce);return status; }
builtin/difftool.c +4 -4
@@ -321,10 +321,10 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid->hash, path,0 ,0 ); + ce =make_transient_cache_entry (mode, oid->hash, path,0 ); ret =checkout_entry (ce, state,NULL ); -free (ce); +transient_cache_entry_discard (ce);return ret; }
@@ -488,8 +488,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, roid.hash, - dst_path,0 ,0 ); +make_index_cache_entry (&wtindex, rmode, roid.hash, + dst_path,0 ,0 );add_index_entry (&wtindex, ce2,ADD_CACHE_JUST_APPEND );
builtin/reset.c +3 -3
@@ -134,10 +134,10 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, one->oid.hash, one->path, -0 ,0 ); + ce =make_index_cache_entry (&the_index, one->mode, one->oid.hash, one->path, +0 ,0 );if (!ce) -die (_ ("make_cache_entry failed for path '%s'" ), +die (_ ("make_index_cache_entry failed for path '%s'" ), one->path);if (is_missing) { ce->ce_flags |=CE_INTENT_TO_ADD ;
builtin/update-index.c +11 -15
@@ -268,15 +268,14 @@ static int process_lstat_error(const char *path, int err)static int add_one_path (const struct cache_entry *old,const char *path,int len,struct stat *st) { -int option, size; +int option;struct cache_entry *ce;/* Was the old index entry already up-to-date? */ if (old && !ce_stage (old) && !ce_match_stat (old, st,0 ))return 0 ; - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 ); ce->ce_namelen = len;
@@ -285,13 +284,13 @@ static int add_one_path(const struct cache_entry *old, const char *path, int lenif (index_path (&ce->oid, path, st, info_only ?0 :HASH_WRITE_OBJECT )) { -free (ce); +index_cache_entry_discard (ce);return -1 ; } option = allow_add ?ADD_CACHE_OK_TO_ADD :0 ; option |= allow_replace ?ADD_CACHE_OK_TO_REPLACE :0 ;if (add_cache_entry (ce, option)) { -free (ce); +index_cache_entry_discard (ce);return error ("%s: cannot add to the index - missing --add option?" , path); }return 0 ;
@@ -403,15 +402,14 @@ static int process_path(const char *path)static int add_cacheinfo (unsigned int mode,const struct object_id *oid,const char *path,int stage) { -int size, len, option; +int len, option;struct cache_entry *ce;if (!verify_path (path))return error ("Invalid path '%s'" , path); len =strlen (path); - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, path, len);
@@ -589,7 +587,6 @@ static struct cache_entry *read_one_ent(const char *which, {unsigned mode;struct object_id oid; -int size;struct cache_entry *ce;if (get_tree_entry (ent, path, &oid, &mode)) {
@@ -602,8 +599,7 @@ static struct cache_entry *read_one_ent(const char *which,error ("%s: not a blob in %s branch." , path, which);return NULL ; } - size =cache_entry_size (namelen); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, namelen);oidcpy (&ce->oid, &oid);memcpy (ce->name, path, namelen);
@@ -680,8 +676,8 @@ static int unresolve_one(const char *path)error ("%s: cannot add their version to the index." , path); ret =-1 ; free_return: -free (ce_2); -free (ce_3); +index_cache_entry_discard (ce_2); +index_cache_entry_discard (ce_3);return ret; }
@@ -748,7 +744,7 @@ static int do_reupdate(int ac, const char **av, ce->name, ce_namelen(ce), 0);if (old && ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { -free (old); +index_cache_entry_discard (old);continue ;/* unchanged */ }/* Be careful. The working tree may not have the
@@ -759,7 +755,7 @@ static int do_reupdate(int ac, const char **av, path =xstrdup (ce->name);update_one (path);free (path); -free (old); +index_cache_entry_discard (old);if (save_nr != active_nr) goto redo; }
cache.h +28 -1
@@ -339,6 +339,34 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and freeing */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `index_cache_entry_discard`. +*/ +extern struct cache_entry *make_index_cache_entry (struct index_state *istate,unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options); +extern struct cache_entry *make_empty_index_cache_entry (struct index_state *istate,size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `transient_cache_entry_discard`. +*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage); +extern struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry allocated via `make_*_index_cache_entry`. +*/ +void index_cache_entry_discard (struct cache_entry *ce); + +/* +* Discard cache entry allocated via `make_*_transient_cache_entry`. +*/ +void transient_cache_entry_discard (struct cache_entry *ce); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
@@ -690,7 +718,6 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options);extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -252,7 +252,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid->hash : null_sha1, path, stage,0 ); + ce =make_index_cache_entry (&the_index, mode, oid ? oid->hash : null_sha1, path, stage,0 );if (!ce)return err (o,_ ("addinfo_cache failed for path '%s'" ), path);
read-cache.c +75 -30
241 lines not on this page · raw message
resolve-undo.c +4 -2
@@ -146,8 +146,10 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], ru->oid[i].hash, - name, i +1 ,0 ); + nce =make_index_cache_entry (istate, + ru->mode[i], + ru->oid[i].hash, + name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;if (add_index_entry (istate, nce,ADD_CACHE_OK_TO_ADD )) {
split-index.c +4 -4
@@ -123,7 +123,7 @@ static void replace_entry(size_t pos, void *data) src->ce_flags |=CE_UPDATE_IN_BASE ; src->ce_namelen = dst->ce_namelen;copy_cache_entry (dst, src); -free (src); +index_cache_entry_discard (src); si->nr_replacements++; }
@@ -224,7 +224,7 @@ void prepare_to_write_split_index(struct index_state *istate) base->ce_flags = base_flags;if (ret) ce->ce_flags |=CE_UPDATE_IN_BASE ; -free (base); +index_cache_entry_discard (base); si->base->cache[ce->index -1 ] = ce; }for (i =0 ; i < si->base->cache_nr; i++) {
@@ -301,7 +301,7 @@ void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce ce == istate->split_index->base->cache[ce->index - 1]) ce->ce_flags |= CE_REMOVE; else -free (ce ); +index_cache_entry_discard (ce ); } void replace_index_entry_in_base(struct index_state *istate,
@@ -314,7 +314,7 @@ void replace_index_entry_in_base(struct index_state *istate, old_entry->index <= istate->split_index->base->cache_nr) { new_entry->index = old_entry->index;if (old_entry != istate->split_index->base->cache[new_entry->index -1 ]) -free (istate->split_index->base->cache[new_entry->index -1 ]); +index_cache_entry_discard (istate->split_index->base->cache[new_entry->index -1 ]); istate->split_index->base->cache[new_entry->index -1 ] = new_entry; } }
tree.c +1 -3
@@ -16,15 +16,13 @@ static int read_one_entry_opt(struct index_state *istate, unsigned mode, int stage, int opt) { int len; -unsigned int size;struct cache_entry *ce;if (S_ISDIR (mode))return READ_TREE_RECURSIVE ; len =strlen (pathname); - size =cache_entry_size (baselen + len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (istate, baselen + len); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage);
unpack-trees.c +17 -10
@@ -192,10 +192,10 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce) +static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) {unsigned int size =ce_size (ce); -struct cache_entry *new_entry =xmalloc (size); +struct cache_entry *new_entry =make_empty_index_cache_entry (istate,ce_namelen (ce));memcpy (new_entry, ce, size);return new_entry;
@@ -205,7 +205,7 @@ static void add_entry(struct unpack_trees_options *o,const struct cache_entry *ce, unsigned int set, unsigned int clear) { -do_add_entry (o ,dup_entry (ce ),set ,clear ); + do_add_entry(o, dup_entry(ce, &o->result), set, clear); }/*
@@ -786,10 +786,17 @@ static int ce_in_traverse_path(const struct cache_entry *ce,return (info->pathlen <ce_namelen (ce)); } -static struct cache_entry *create_ce_entry (const struct traverse_info *info,const struct name_entry *n,int stage) +static struct cache_entry *create_ce_entry (const struct traverse_info *info, +const struct name_entry *n, +int stage, +struct index_state *istate, +int is_transient) {int len =traverse_path_len (info, n); -struct cache_entry *ce =xcalloc (1 ,cache_entry_size (len)); +struct cache_entry *ce = + is_transient ? +make_empty_transient_cache_entry (len) : +make_empty_index_cache_entry (istate, len); ce->ce_mode =create_ce_mode (n->mode); ce->ce_flags =create_ce_flags (stage);
@@ -835,7 +842,7 @@ static int unpack_nondirectories(int n, unsigned long mask, stage =3 ; else stage =2 ; - src[i + o->merge] =create_ce_entry (info, names + i, stage); + src[i + o->merge] =create_ce_entry (info, names + i, stage, &o->result, o->merge); }if (o->merge) {
@@ -844,7 +851,7 @@ static int unpack_nondirectories(int n, unsigned long mask,for (i =0 ; i < n; i++) {struct cache_entry *ce = src[i + o->merge];if (ce != o->df_conflict_entry) -free (ce); +transient_cache_entry_discard (ce); }return rc; }
@@ -1765,7 +1772,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce); +struct cache_entry *merge =dup_entry (ce, &o->result);if (!old) {/*
@@ -1785,7 +1792,7 @@ static int merged_entry(const struct cache_entry *ce,if (verify_absent (merge,ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN , o)) { -free (merge); +index_cache_entry_discard (merge);return -1 ; }invalidate_ce_path (merge, o);
@@ -1811,7 +1818,7 @@ static int merged_entry(const struct cache_entry *ce, update =0 ; }else {if (verify_uptodate (old, o)) { -free (merge); +index_cache_entry_discard (merge);return -1 ; }/* Migrate old flags over */
Signature · 1 line
2.14.3
Re: [PATCH v1 2/5] Add an API creating / discarding cache_entry structs
Quoted 6 lines · Jameson Miller
Add an API around managing the lifetime of
cache_entrystructs. Abstracting memory management details behind an API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated / freed. A later commit in this series will allocate cache entries from memory pools as appropriate.
Quoted 8 lines
Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated bymalloc()calls. This change is in preparation for using memory pools to reduce the number ofmalloc()calls made when loading an index.This API makes a distinction between cache entries that are intended for use with a particular to an index and cache entries that are not.
The wording here is awkward. Did you mean "intended for use with a particular index?"
This enables us
Quoted 25 lines
to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
--- apply.c | 26 ++++++------ blame.c | 5 +-- builtin/checkout.c | 8 ++-- builtin/difftool.c | 8 ++-- builtin/reset.c | 6 +-- builtin/update-index.c | 26 ++++++------ cache.h | 29 +++++++++++++- merge-recursive.c | 2 +- read-cache.c | 105 +++++++++++++++++++++++++++++++++++-------------- resolve-undo.c | 6 ++- split-index.c | 8 ++-- tree.c | 4 +- unpack-trees.c | 27 ++++++++----- 13 files changed, 166 insertions(+), 94 deletions(-)apply.c +0
-0@@ -4090,12 +4090,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name);
apply.c static int build_fake_ancestor(struct apply_state *state, struct patch *list) · line 4098
Quoted 10 lines
apply.c +3
-3- ce =make_cache_entry (patch->old_mode, oid.hash, name,0 ,0 ); + ce =make_index_cache_entry (&result, patch->old_mode, oid.hash, name,0 ,0 );if (!ce) -return error (_ ("make_cache_entry failed for path '%s'" ), +return error (_ ("make_index_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +index_cache_entry_discard (ce);
I personally prefer name symmetry. To me, make_index_cache_entry() should be paired with discard_index_cache_entry().
The rest of this patch looks like a fairly mechanical refactoring with the biggest exception being the difference between the the *_index_cache_entry() APIs and the *_transient_cache_entry() APIs.
There are quite a few changes but I didn't see any instances that were missed or any errors. I see that later patches will put verification code in place to detect if any were done incorrectly and to prevent regressions moving forward.
Overall, it looks correct and reasonable.
<snip>
Quoted 54 lines
apply.c +27
-7-struct cache_entry *make_cache_entry (unsigned int mode, -const unsigned char *sha1,const char *path,int stage, -unsigned int refresh_options) +struct cache_entry *make_empty_index_cache_entry (struct index_state *istate,size_t len) +{ +return xcalloc (1 ,cache_entry_size (len)); +} + +struct cache_entry *make_empty_transient_cache_entry (size_t len) +{ +return xcalloc (1 ,cache_entry_size (len)); +} + +struct cache_entry *make_index_cache_entry (struct index_state *istate,unsigned int mode, +const unsigned char *sha1,const char *path, +int stage,unsigned int refresh_options) { -int size, len;struct cache_entry *ce, *ret; +int len;if (!verify_path (path)) {error ("Invalid path '%s'" , path);@@ -758,8 +767,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, } len =strlen (path); - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (istate, len);hashcpy (ce->oid.hash, sha1);memcpy (ce->name, path, len);@@ -769,10 +777,34 @@ struct cache_entry *make_cache_entry(unsigned int mode, ret =refresh_cache_entry (&the_index, ce, refresh_options);if (ret != ce) -free (ce); +index_cache_entry_discard (ce); +return ret; } +struct cache_entry *make_transient_cache_entry (unsigned int mode,const unsigned char *sha1, +const char *path,int stage) +{ +struct cache_entry *ce; +int len; + +if (!verify_path (path)) { +error ("Invalid path '%s'" , path); +return NULL ; + }
apply.c struct cache_entry *make_cache_entry(unsigned int mode, · line 804
Quoted 10 lines
apply.c +10
-0+ + len =strlen (path); + ce =make_empty_transient_cache_entry (len); + +hashcpy (ce->oid.hash, sha1); +memcpy (ce->name, path, len); + ce->ce_flags =create_ce_flags (stage); + ce->ce_namelen = len; + ce->ce_mode =create_ce_mode (mode); +
Nit, feel free to ignore. There isn't a lot of initialization here but I wonder if it makes sense to have an internal helper function to ensure these stay the same.
Quoted 42 lines
apply.c +8
-5+return ce; +} +/* * Chmod an index entry with either +x or -x. * @@ -1243,7 +1275,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, {struct stat st;struct cache_entry *updated; -int changed, size; +int changed;int refresh = options &CE_MATCH_REFRESH ;int ignore_valid = options &CE_MATCH_IGNORE_VALID ;int ignore_skip_worktree = options &CE_MATCH_IGNORE_SKIP_WORKTREE ;@@ -1323,8 +1355,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,return NULL ; } - size =ce_size (ce); - updated =xmalloc (size); + updated =make_empty_index_cache_entry (istate,ce_namelen (ce));copy_cache_entry (updated, ce);memcpy (updated->name, ce->name, ce->ce_namelen +1 );fill_stat_cache_info (updated, &st);@@ -1610,12 +1641,13 @@ int read_index(struct index_state *istate)return read_index_from (istate,get_index_file (),get_git_dir ()); } -static struct cache_entry *cache_entry_from_ondisk (struct ondisk_cache_entry *ondisk, +static struct cache_entry *cache_entry_from_ondisk (struct index_state *istate, +struct ondisk_cache_entry *ondisk,unsigned int flags,const char *name,size_t len) { -struct cache_entry *ce =xmalloc (cache_entry_size (len)); +struct cache_entry *ce =make_empty_index_cache_entry (istate, len); ce->ce_stat_data.sd_ctime.sec =get_be32 (&ondisk->ctime.sec); ce->ce_stat_data.sd_mtime.sec =get_be32 (&ondisk->mtime.sec);
apply.c static unsigned long expand_name_field(struct strbuf *name, const char *cp_) · line 1695
Quoted 8 lines
apply.c +2
-1@@ -1657,7 +1689,8 @@ static unsigned long expand_name_field(struct strbuf *name, const char *cp_)return (const char *)ep +1 - cp_; } -static struct cache_entry *create_from_disk (struct ondisk_cache_entry *ondisk, +static struct cache_entry *create_from_disk (struct index_state *istate, +struct ondisk_cache_entry *ondisk,unsigned long *ent_size,struct strbuf *previous_name)
Nit. Just wondering why you pulled refresh_cache_entry() out into an earlier/separate commit but then did create_from_disk() as part of the large refactoring focused on adding the create/discard APIs?
<snip>
unpack-trees.c static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, · line 195
Quoted 10 lines
unpack-trees.c +1
-1@@ -192,10 +192,10 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce) +static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate)
Ditto with the dup_entry() and create_ce_entry() functions refactoring.
Quoted 6 lines
unpack-trees.c +5
-1-static struct cache_entry *create_ce_entry (const struct traverse_info *info,const struct name_entry *n,int stage) +static struct cache_entry *create_ce_entry (const struct traverse_info *info, +const struct name_entry *n, +int stage, +struct index_state *istate, +int is_transient)
PATCH 3/5 mem-pool: fill out functionality
In reply to Jameson MillerAdds the following functionality to memory pools:
- Lifecycle management functions (init, discard)
- Test whether a memory location is part of the managed pool
- Function to combine 2 pools
This also adds logic to track all memory allocations made by a memory pool.
These functions will be used in a future commit in this commit series.
--- mem-pool.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- mem-pool.h | 32 +++++++++++++++++++ 2 files changed, 131 insertions(+), 4 deletions(-)
mem-pool.c +99 -4
@@ -5,6 +5,8 @@#include "cache.h" #include "mem-pool.h" +#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block); +static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc) {struct mp_block *p;
@@ -19,6 +21,59 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t breturn p; } +static void *mem_pool_alloc_custom (struct mem_pool *mem_pool,size_t block_alloc) +{ +char *p; +ALLOC_GROW (mem_pool->custom, mem_pool->nr +1 , mem_pool->alloc); +ALLOC_GROW (mem_pool->custom_end, mem_pool->nr_end +1 , mem_pool->alloc_end); + + p =xmalloc (block_alloc); + mem_pool->custom[mem_pool->nr++] = p; + mem_pool->custom_end[mem_pool->nr_end++] = p + block_alloc; + + mem_pool->pool_alloc += block_alloc; + +return mem_pool->custom[mem_pool->nr -1 ]; +} + +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size) +{ +if (!(*mem_pool)) + { + *mem_pool =xmalloc (sizeof (struct mem_pool )); + (*mem_pool)->pool_alloc =0 ; + (*mem_pool)->mp_block =NULL ; + (*mem_pool)->block_alloc =BLOCK_GROWTH_SIZE ; + (*mem_pool)->custom =NULL ; + (*mem_pool)->nr =0 ; + (*mem_pool)->alloc =0 ; + (*mem_pool)->custom_end =NULL ; + (*mem_pool)->nr_end =0 ; + (*mem_pool)->alloc_end =0 ; + +if (initial_size >0 ) +mem_pool_alloc_block (*mem_pool, initial_size); + } +} + +void mem_pool_discard (struct mem_pool *mem_pool) +{ +int i; +struct mp_block *block, *block_to_free; +for (block = mem_pool->mp_block; block;) { + block_to_free = block; + block = block->next_block; +free (block_to_free); + } + +for (i =0 ; i < mem_pool->nr; i++) +free (mem_pool->custom[i]); + +free (mem_pool->custom); +free (mem_pool->custom_end); +free (mem_pool); +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p;
@@ -33,10 +88,8 @@ void *mem_pool_alloc(struct mem_pool *mem_pool, size_t len)break ;if (!p) { -if (len >= (mem_pool->block_alloc /2 )) { - mem_pool->pool_alloc += len; -return xmalloc (len); - } +if (len >= (mem_pool->block_alloc /2 )) +return mem_pool_alloc_custom (mem_pool, len); p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc); }
@@ -53,3 +106,45 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +struct mp_block *p; +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +int i; +struct mp_block **tail = &dst->mp_block; + +/* Find pointer of dst's last block (if any) */ +while (*tail) + tail = &(*tail)->next_block; + +/* Append the blocks from src to dst */ + *tail = src->mp_block; + +ALLOC_GROW (dst->custom, dst->nr + src->nr, dst->alloc); +ALLOC_GROW (dst->custom_end, dst->nr_end + src->nr_end, dst->alloc_end); + +for (i =0 ; i < src->nr; i++) { + dst->custom[dst->nr++] = src->custom[i]; + dst->custom_end[dst->nr_end++] = src->custom_end[i]; + } + + dst->pool_alloc += src->pool_alloc; + src->pool_alloc =0 ; + src->mp_block =NULL ; + src->custom =NULL ; + src->nr =0 ; + src->alloc =0 ; + src->custom_end =NULL ; + src->nr_end =0 ; + src->alloc_end =0 ; +}
mem-pool.h +32 -0
@@ -19,8 +19,27 @@ struct mem_pool {/* The total amount of memory allocated by the pool. */ size_t pool_alloc; + +/* +* Array of pointers to "custom size" memory allocations. +* This is used for "large" memory allocations. +* The *_end variables are used to track the range of memory +* allocated. +*/ +void **custom, **custom_end; +int nr, nr_end, alloc, alloc_end; }; +/* +* Initialize mem_pool specified initial. +*/ +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size); + +/* +* Discard a memory pool and free all the memory it is responsible for. +*/ +void mem_pool_discard (struct mem_pool *mem_pool); +/* * Alloc memory from the mem_pool. */
@@ -31,4 +50,17 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len);*/ void *mem_pool_calloc (struct mem_pool *pool,size_t count,size_t size); +/* +* Move the memory associated with the 'src' pool to the 'dst' pool. The 'src' +* pool will be empty and not contain any memory. It still needs to be free'd +* with a call to `mem_pool_discard`. +*/ +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src); + +/* +* Check if a memory pointed at by 'mem' is part of the range of +* memory managed by the specified mem_pool. +*/ +int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +#endif
Signature · 1 line
2.14.3
Re: [PATCH v1 3/5] mem-pool: fill out functionality
mem-pool.h struct mem_pool { · line 30
Quoted 13 lines · On Tue, 17 Apr 2018 16:34:42 +0000 Jameson Miller <[email protected]> wrote:
mem-pool.h +9
-0@@ -19,8 +19,27 @@ struct mem_pool {/* The total amount of memory allocated by the pool. */ size_t pool_alloc; + +/* +* Array of pointers to "custom size" memory allocations. +* This is used for "large" memory allocations. +* The *_end variables are used to track the range of memory +* allocated. +*/ +void **custom, **custom_end; +int nr, nr_end, alloc, alloc_end;
This seems overly complicated - the struct mem_pool already has a linked list of pages, so couldn't you create a custom page and insert it behind the current front page instead whenever you needed a large-size page?
Also, when combining, there could be some wasted space on one of the pages. I'm not sure if that's worth calling out, though.
Re: [PATCH v1 3/5] mem-pool: fill out functionality
Quoted 5 lines · Jonathan Tan
Quoted 2 lines · On Tue, 17 Apr 2018 16:34:42 +0000 Jameson Miller <[email protected]> wrote:
(quoted diff) +0
-0@@ -19,8 +19,27 @@ struct mem_pool {
Quoted 15 lines
mem-pool.h struct mem_pool { · line 30
Quoted 11 lines
mem-pool.h +9
-0/* The total amount of memory allocated by the pool. */ size_t pool_alloc; + +/* +* Array of pointers to "custom size" memory allocations. +* This is used for "large" memory allocations. +* The *_end variables are used to track the range of memory +* allocated. +*/ +void **custom, **custom_end; +int nr, nr_end, alloc, alloc_end;This seems overly complicated - the struct
mem_poolalready has a linked list of pages, so couldn't you create a custom page and insert it behind the current front page instead whenever you needed a large-size page?
Yes - that is another option. However, the linked list of pages includes memory that *could* have space for an allocation, while the "custom" region will never have left over memory that can be used for other allocations. When searching pages for memory to satisfy a request, there is no reason to search through the "custom" pages. There is a trade-off between complexity and implementation, so I am open to suggestions.
This was discussed in [1], where it originally was implemented closer to what you describe here.
Quoted 4 lines
Also, when combining, there could be some wasted space on one of the pages. I'm not sure if that's worth calling out, though.
Yes, we bring over the whole page. However, these pages are now available for new allocations.
Re: [PATCH v1 3/5] mem-pool: fill out functionality
Quoted 14 lines · On Mon, 23 Apr 2018 13:27:09 -0400 Jameson Miller <[email protected]>…
Quoted 3 lines
This seems overly complicated - the struct
mem_poolalready has a linked list of pages, so couldn't you create a custom page and insert it behind the current front page instead whenever you needed a large-size page?Yes - that is another option. However, the linked list of pages includes memory that *could* have space for an allocation, while the "custom" region will never have left over memory that can be used for other allocations. When searching pages for memory to satisfy a request, there is no reason to search through the "custom" pages. There is a trade-off between complexity and implementation, so I am open to suggestions.
This was discussed in [1], where it originally was implemented closer to what you describe here.
Quoted 8 lines
Quoted 2 lines
Also, when combining, there could be some wasted space on one of the pages. I'm not sure if that's worth calling out, though.
Yes, we bring over the whole page. However, these pages are now available for new allocations.
Ah, I didn't realize that the plan was to search over all pages when allocating memory from the pool, instead of only searching the last page. This seems like a departure from the fast-import.c way, where as far as I can tell, new_object() searches only one page. If we do plan to do this, searching all pages doesn't seem like a good idea to me, especially since the objects we're storing in the pool are of similar size.
If we decide to go ahead with searching all the pages, though, the "custom" pages should probably be another linked list instead of an array.
Re: [PATCH v1 3/5] mem-pool: fill out functionality
Quoted 26 lines · Jonathan Tan
Quoted 14 lines · On Mon, 23 Apr 2018 13:27:09 -0400 Jameson Miller <[email protected]>…
Quoted 3 lines
This seems overly complicated - the struct
mem_poolalready has a linked list of pages, so couldn't you create a custom page and insert it behind the current front page instead whenever you needed a large-size page?Yes - that is another option. However, the linked list of pages includes memory that *could* have space for an allocation, while the "custom" region will never have left over memory that can be used for other allocations. When searching pages for memory to satisfy a request, there is no reason to search through the "custom" pages. There is a trade-off between complexity and implementation, so I am open to suggestions.
This was discussed in [1], where it originally was implemented closer to what you describe here.
Quoted 8 lines
Quoted 2 lines
Also, when combining, there could be some wasted space on one of the pages. I'm not sure if that's worth calling out, though.
Yes, we bring over the whole page. However, these pages are now available for new allocations.
Quoted 7 lines
Ah, I didn't realize that the plan was to search over all pages when allocating memory from the pool, instead of only searching the last page. This seems like a departure from the
fast-import.cway, where as far as I can tell,new_object()searches only one page. If we do plan to do this, searching all pages doesn't seem like a good idea to me, especially since the objects we're storing in the pool are of similar size.
I see. However, the new_object() logic in fast-import is a different than the logic mem_pool was abstracting, and is not covered by the mem_pool functionality. The behavior of searching over all pages for one to satisfy the request existed previously and was not changed in the mem_pool implementation.
Quoted 5 lines
If we decide to go ahead with searching all the pages, though, the "custom" pages should probably be another linked list instead of an array.
This is an option - I went with the current design because we only need pointers to a block of memory (as well tracking how large the allocation is for verification purposes). We don't necessarily need the extra overhead of a structure to track the linked list nodes, when it is provided by the existing array manipulation functions. I am open to feedback on this point, however.
PATCH 4/5 Allocate cache entries from memory pools
In reply to Jameson MillerImprove performance of reading a large index by reducing the number of malloc() calls. When reading an index with a large number of entries, a portion of the time is dominated in malloc() calls. This can be mitigated by allocating a single large block of memory up front into a memory pool and have git hand out chunks of time.
This change moves the cache entry allocation to be on top of memory pools.
Design:
The index_state struct will gain a notion of an associated memory_pool from which cache_entry structs will be allocated from. When reading in the index from disk, we have information on the number of entries and their size, which can guide us in deciding how large our initial memory allocation should be. When an index is discarded, the associated memory_pool and cache entries from the memory pool will be discarded as well. This means the lifetime of cache_entry structs are tied to the lifetime of the index_state they were allocated for.
In the case of a Split Index, the following rules are followed. 1st, some terminology is defined:
Terminology:
- 'the_index': represents the logical view of the index
- 'split_index': represents the "base" cache entries. Read from the split index file.
'the_index' can reference a single split_index, as well as cache_entries from the split_index. the_index will be discarded before the split_index is. This means that when we are allocating cache_entries in the presence of a split index, we need to allocate the entries from the split_index's memory pool. This allows us to follow the pattern that the_index can reference cache_entries from the split_index, and that the cache_entries will not be freed while they are still being referenced.
--- cache.h | 2 ++ read-cache.c | 95 +++++++++++++++++++++++++++++++++++++++++++++-------------- split-index.c | 23 +++++++++++++-- 3 files changed, 95 insertions(+), 25 deletions(-)
cache.h +2 -0
@@ -15,6 +15,7 @@#include "path.h" #include "sha1-array.h" #include "repository.h" +#include "mem-pool.h" #include <zlib.h> typedef struct git_zstream {
@@ -328,6 +329,7 @@ struct index_state {struct untracked_cache *untracked;uint64_t fsmonitor_last_update;struct ewah_bitmap *fsmonitor_dirty; +struct mem_pool *ce_mem_pool; };extern struct index_state the_index;
read-cache.c +73 -22
176 lines not on this page · raw message
split-index.c +20 -3
@@ -73,16 +73,31 @@ void move_cache_to_base_index(struct index_state *istate)int i;/* -* do not delete old si->base, its index entries may be shared -* with istate->cache[]. Accept a bit of leaking here because -* this code is only used by short-lived update-index. +* If there was a previous base index, then transfer ownership of allocated +* entries to the parent index. */ +if (si->base && + si->base->ce_mem_pool) { + +if (!istate->ce_mem_pool) +mem_pool_init (&istate->ce_mem_pool,0 ); + +mem_pool_combine (istate->ce_mem_pool, istate->split_index->base->ce_mem_pool); + } + si->base =xcalloc (1 ,sizeof (*si->base)); si->base->version = istate->version;/* zero timestamp disables racy test in ce_write_index() */ si->base->timestamp = istate->timestamp;ALLOC_GROW (si->base->cache, istate->cache_nr, si->base->cache_alloc); si->base->cache_nr = istate->cache_nr; + +/* +* The mem_pool needs to move with the allocated entries. +*/ + si->base->ce_mem_pool = istate->ce_mem_pool; + istate->ce_mem_pool =NULL ; +COPY_ARRAY (si->base->cache, istate->cache, istate->cache_nr);mark_base_index_entries (si->base);for (i =0 ; i < si->base->cache_nr; i++)
@@ -330,6 +345,8 @@ void add_split_index(struct index_state *istate)void remove_split_index (struct index_state *istate) {if (istate->split_index) { +mem_pool_combine (istate->ce_mem_pool, istate->split_index->base->ce_mem_pool); +/* * can't discard_split_index(&the_index); because that * will destroy split_index->base->cache[], which may
Signature · 1 line
2.14.3
PATCH 5/5 Add optional memory validations around cache_entry lifecyle
In reply to Jameson MillerAdd an option (controlled by an environment variable) perform extra validations on mem_pool allocated cache entries. When set:
1) Invalidate cache_entry memory when discarding cache_entry.
2) When discarding index_state struct, verify that all cache_entries were allocated from expected mem_pool.
3) When discarding mem_pools, invalidate mem_pool memory.
This should provide extra checks that mem_pools and their allocated cache_entries are being used as expected.
--- cache.h | 7 +++++++ git.c | 3 +++ mem-pool.c | 35 ++++++++++++++++++++++++++++++++++- mem-pool.h | 2 ++ read-cache.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 1 deletion(-)
cache.h +7 -0
@@ -369,6 +369,13 @@ void index_cache_entry_discard(struct cache_entry *ce);*/ void transient_cache_entry_discard (struct cache_entry *ce); +/* +* Validate the cache entries in the index. This is an internal +* consistency check that the cache_entry structs are allocated from +* the expected memory pool. +*/ +void validate_cache_entries (const struct index_state *istate); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
git.c +3 -0
@@ -347,7 +347,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)trace_argv_printf (argv,"trace: built-in: git" ); +validate_cache_entries (&the_index); status = p->fn (argc, argv, prefix); +validate_cache_entries (&the_index); +if (status)return status;
mem-pool.c +34 -1
@@ -60,20 +60,44 @@ void mem_pool_discard(struct mem_pool *mem_pool) {int i;struct mp_block *block, *block_to_free; +int invalidate_memory =should_validate_cache_entries (); +for (block = mem_pool->mp_block; block;) { block_to_free = block; block = block->next_block; + +if (invalidate_memory) +memset (block_to_free->space,0xDD , ((char *)block_to_free->end) - ((char *)block_to_free->space)); +free (block_to_free); } -for (i =0 ; i < mem_pool->nr; i++) +for (i =0 ; i < mem_pool->nr; i++) { +if (invalidate_memory) +memset (mem_pool->custom[i],0xDD , ((char *)mem_pool->custom_end[i]) - ((char *)mem_pool->custom[i])); +free (mem_pool->custom[i]); + }free (mem_pool->custom);free (mem_pool->custom_end);free (mem_pool); } +int should_validate_cache_entries (void ) +{ +static int validate_index_cache_entries =-1 ; + +if (validate_index_cache_entries <0 ) { +if (getenv ("GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES" )) + validate_index_cache_entries =1 ; +else + validate_index_cache_entries =0 ; + } + +return validate_index_cache_entries; +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p;
@@ -110,11 +134,20 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)int mem_pool_contains (struct mem_pool *mem_pool,void *mem) {struct mp_block *p; +int i; + +/* Check if memory is allocated in a block */ for (p = mem_pool->mp_block; p; p = p->next_block)if ((mem >= ((void *)p->space)) && (mem < ((void *)p->end)))return 1 ; +/* Check custom memory allocations */ +for (i =0 ; i < mem_pool->nr; i++) +if (mem >= mem_pool->custom[i] && + mem < mem_pool->custom_end[i]) +return 1 ; +return 0 ; }
mem-pool.h +2 -0
@@ -63,4 +63,6 @@ void mem_pool_combine(struct mem_pool *dst, struct mem_pool *src);*/ int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +int should_validate_cache_entries (void ); +#endif
read-cache.c +44 -0
68 lines not on this page · raw message
Signature · 1 line
2.14.3
Quoted 14 lines · Jameson Miller
100K
Test baseline [4]
block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.02(0.01+0.01) -33.3%1M:
Test baseline
block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.17(0.07+0.09) -26.1%2M:
Quoted 7 lines
Test baseline
block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.39(0.17+0.20) -13.3%100K is not a large enough sample size to show the perf impact of this change, but we can see a perf improvement with 1M and 2M entries.
I see a 33% change with 100K files which is a substantial improvement even in the 100K case. I do see that the actual wall clock savings aren't nearly as much with a small repo as it is with the larger repos which makes sense.
Quoted 23 lines · Ben Peart
Quoted 19 lines · Jameson Miller
100K
Test baseline [4]
block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.02(0.01+0.01) -33.3%1M:
Test baseline
block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.17(0.07+0.09) -26.1%2M:
Test baseline
block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.39(0.17+0.20) -13.3%
Quoted 4 lines
Quoted 2 lines
100K is not a large enough sample size to show the perf impact of this change, but we can see a perf improvement with 1M and 2M entries.
I see a 33% change with 100K files which is a substantial improvement even in the 100K case. I do see that the actual wall clock savings aren't nearly as much with a small repo as it is with the larger repos which makes sense.
You are correct - I should have been more careful in my wording. What I meant is that the wall time savings with 100K is not large, because this operation is already very fast.
Quoted 29 lines · Jameson Miller
This patch series improves the performance of loading indexes by reducing the number of
malloc()calls. ...Jameson Miller (5):
read-cache: teachrefresh_cache_entryto take istate Add an API creating / discardingcache_entrystructs
mem-pool: fill out functionality
Allocate cache entries from memory pools
Add optional memory validations aroundcache_entrylifecyleapply.c | 26 +++--- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 8 +- builtin/reset.c | 6 +- builtin/update-index.c | 26 +++--- cache.h | 40 ++++++++- git.c | 3 + mem-pool.c | 136 ++++++++++++++++++++++++++++- mem-pool.h | 34 ++++++++ merge-recursive.c | 4 +- read-cache.c | 229 +++++++++++++++++++++++++++++++++++++++---------- resolve-undo.c | 6 +- split-index.c | 31 +++++-- tree.c | 4 +- unpack-trees.c | 27 +++--- 16 files changed, 476 insertions(+), 117 deletions(-)
Quoted 1 line
base-commit:
cafaccae98f749ebf33495aec42ea25060de8682
I couldn't quite figure out what these five patches were based on, even with this line. Basing on and referring to a commit that is not part of our published history with "base-commit" is not all that useful to others.
Offhand without applying these patches and viewing the changes in wider contexts, one thing that makes me wonder is how the two allocation schemes can be used with two implementations of free(). Upon add_index_entry() that replaces an index entry for an existing path, we'd discard an entry that was originally read as part of read_cache(). If we do that again, the second add_index_entry() will be discading, in its call to replace_index_entry(), the entry that was allocated by the caller of the first add_index_entry() call. And replace_index_entry() would not have a way to know from which allocation the entry's memory came from.
Perhaps it is not how you are using the "transient" stuff, and from the comment in 2/5, it is for "entries that are not meant to go into the index", but then higher stage index entries in unpack_trees seem to be allocated via the "transient" stuff, so I am not sure what the plans are for things like merge_recursive() that uses unpack_trees() to prepare the index and then later "fix it up" by further futzing around the index to adjust for renames it finds, etc.
Let me read it fully once we know where these patches are to be applied, but before that I cannot say much about them X-<.
Thanks.
Quoted 6 lines
Quoted 1 line
base-commit:
cafaccae98f749ebf33495aec42ea25060de8682I couldn't quite figure out what these five patches were based on, even with this line. Basing on and referring to a commit that is not part of our published history with "base-commit" is not all that useful to others.
I'd like to second this. In the object store refactoring, I am at a point where I'd want to migrate the memory management of {object, tree, commit, tag}.c which currently is done in alloc.c to a memory pool, that has a dedicated pointer to it.
So I'd either have to refactor alloc.c to take the_repository[1] or I'd play around with the mem_pool to manage memory in the object layer. I guess this playing around can happen with what is at origin/jm/mem-pool, however the life cycle management part of the third patch[2] would allow for stopping memleaks there. So I am interested in this series as well.
[1] proof of concept in patches nearby
https://public-inbox.org/git/[email protected]/
[2] https://public-inbox.org/git/[email protected]/
Thanks,
Stefan
Quoted 12 lines · Stefan Beller
Quoted 6 lines
Quoted 1 line
base-commit:
cafaccae98f749ebf33495aec42ea25060de8682I couldn't quite figure out what these five patches were based on, even with this line. Basing on and referring to a commit that is not part of our published history with "base-commit" is not all that useful to others.
I'd like to second this. In the object store refactoring, I am at a point where I'd want to migrate the memory management of {object, tree, commit, tag}.c which currently is done in
alloc.cto a memory pool, that has a dedicated pointer to it.
Quoted 7 lines
So I'd either have to refactor
alloc.cto take the_repository[1] or I'd play around with themem_poolto manage memory in the object layer. I guess this playing around can happen with what is at origin/jm/mem-pool, however the life cycle management part of the third patch[2] would allow for stopping memleaks there. So I am interested in this series as well.
Sorry about the confusion here. This patch series can be applied to the next branch. They apply cleanly on [3]. The lifecycle functions are re-introduced in [4], which we could incorporate sooner if useful. I didn't have a consumer for the calls in the original patch series, and so deferred them until there was a caller. I would be interested to understand how the mem_pool would fit your needs, and if it is sufficient or needs modification for your use cases.
Quoted 8 lines
[1] proof of concept in patches nearby
https://public-inbox.org/git/[email protected]/[2] https://public-inbox.org/git/[email protected]/
Thanks,
Stefan
[3] b46fe60e1d ("merge-fix/ps/test-chmtime-get", 2018-04-20)
Quoted 7 lines · Jameson Miller
I would be interested to understand how the
mem_poolwould fit your needs, and if it is sufficient or needs modification for your use cases.Quoted 3 lines
[1] proof of concept in patches nearby
https://public-inbox.org/git/[email protected]/
Currenlty the parsed objects are loaded into memory and never freed. See alloc.c which implements a specialized memory allocator for this object loading.
When working with submodules, their objects are also just put into this globally-namespaced object store. (See struct object **obj_hash in object.c)
I want to make the object store a per-repository object, such that when working with submodules, you can free up all submodule objects once you are done with a given submodule.
To do so, the memory allocation needs to manage the whole life cycle, while preserving the efficiency of alloc.c. See 855419f764 (Add specialized object allocator, 2006-06-19). The mem-pool that you propose can allocate large slabs of memory and we can put objects in there without alignment overhead, such that we preserve the memory efficiency while being able to track all the memory.
So I would think it is sufficient as-is with this series, maybe we need a little tweaking there, but nothing large IMHO.
Thanks,
Stefan
Quoted 31 lines · Junio C Hamano
Quoted 29 lines · Jameson Miller
This patch series improves the performance of loading indexes by reducing the number of
malloc()calls. ...Jameson Miller (5):
read-cache: teachrefresh_cache_entryto take istate Add an API creating / discardingcache_entrystructs
mem-pool: fill out functionality
Allocate cache entries from memory pools
Add optional memory validations aroundcache_entrylifecyleapply.c | 26 +++--- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 8 +- builtin/reset.c | 6 +- builtin/update-index.c | 26 +++--- cache.h | 40 ++++++++- git.c | 3 + mem-pool.c | 136 ++++++++++++++++++++++++++++- mem-pool.h | 34 ++++++++ merge-recursive.c | 4 +- read-cache.c | 229 +++++++++++++++++++++++++++++++++++++++---------- resolve-undo.c | 6 +- split-index.c | 31 +++++-- tree.c | 4 +- unpack-trees.c | 27 +++--- 16 files changed, 476 insertions(+), 117 deletions(-)
Quoted 5 lines
Quoted 1 line
base-commit:
cafaccae98f749ebf33495aec42ea25060de8682I couldn't quite figure out what these five patches were based on, even with this line. Basing on and referring to a commit that is not part of our published history with "base-commit" is not all that useful to others.
My apologies - this patch series should be applied to the 'next' branch. It applies cleanly on top ofb46fe60e1d ("merge-fix/ps/test-chmtime-get", 2018-04-20), which is a commit in the 'next' branch.
Quoted 11 lines
Offhand without applying these patches and viewing the changes in wider contexts, one thing that makes me wonder is how the two allocation schemes can be used with two implementations of
free(). Uponadd_index_entry()that replaces an index entry for an existing path, we'd discard an entry that was originally read as part ofread_cache(). If we do that again, the secondadd_index_entry()will be discading, in its call toreplace_index_entry(), the entry that was allocated by the caller of the firstadd_index_entry()call. Andreplace_index_entry()would not have a way to know from which allocation the entry's memory came from.
Quoted 7 lines
Perhaps it is not how you are using the "transient" stuff, and from the comment in 2/5, it is for "entries that are not meant to go into the index", but then higher stage index entries in
unpack_treesseem to be allocated via the "transient" stuff, so I am not sure what the plans are for things likemerge_recursive()that usesunpack_trees()to prepare the index and then later "fix it up" by further futzing around the index to adjust for renames it finds, etc.
Good points. The intention with this logic is that any entries that *could* go into an index are allocated from the memory pool. The "transient" entries only exist for a short period of time. These have a defined lifetime and we can always trace the corresponding "free" call. make_transient_cache_entry() is only used to construct a temporary cache_entry to pass to the checkout_entry() / write_entry(). There is a note in checkout.c indicating that write_entry() needs to be re-factored to not take a cache_entry.
The cache_entry type could gain knowledge about where it was allocated from. This would allow us to only have a single "free()" function, which could inspect the cache_entry to see if it was allocated from a mem_pool (and possibly which mem_pool) and take the appropriate action. The downside of this approach is that cache_entry would need to gain a field to track this information, and I *think* all of the bit field spots are taken.
Quoted 5 lines
Let me read it fully once we know where these patches are to be applied, but before that I cannot say much about them X-<.
Thanks.
Quoted 6 lines · On Tue, 17 Apr 2018 16:34:39 +0000 Jameson Miller <[email protected]> wrote:
Jameson Miller (5):
read-cache: teachrefresh_cache_entryto take istate Add an API creating / discardingcache_entrystructs
mem-pool: fill out functionality
Allocate cache entries from memory pools
Add optional memory validations aroundcache_entrylifecyle
In this patch set, there is no enforcement that the cache entry created by make_index_cache_entry() goes into the correct index when add_index_entry() is invoked. (Junio described similar things, I believe, in [1].) This might be an issue when we bring up and drop multiple indexes, and dropping one index causes a cache entry in another to become invalidated.
One solution is to store the index for which the cache entry was created in the cache entry itself, but that does increase its size. Another is to change the API such that a cache entry is created and added in the same function, and then have some rollback if the cache entry turns out to be invalid (to support add-empty-entry -> fill -> verify), but I don't know if this is feasible. Anyway, all these alternatives should be at least discussed in the commit message, I think.
The make_transient_cache_entry() function might be poorly named, since as far as I can tell, the entries produced by that function are actually the longest lasting, since they are never freed.
Along those lines, I was slightly surprised to find out in patch 4 that cache entry freeing is a no-op. That's fine, but in that case, it would be better to delete all the calls to the "discard" function, and document in the others that the entries they create will only be freed when the memory pool itself is discarded.
Quoted 10 lines · Jonathan Tan
Quoted 6 lines · On Tue, 17 Apr 2018 16:34:39 +0000 Jameson Miller <[email protected]> wrote:
Jameson Miller (5):
read-cache: teachrefresh_cache_entryto take istate Add an API creating / discardingcache_entrystructs
mem-pool: fill out functionality
Allocate cache entries from memory pools
Add optional memory validations aroundcache_entrylifecyle
Quoted 6 lines
In this patch set, there is no enforcement that the cache entry created by
make_index_cache_entry()goes into the correct index whenadd_index_entry()is invoked. (Junio described similar things, I believe, in [1].) This might be an issue when we bring up and drop multiple indexes, and dropping one index causes a cache entry in another to become invalidated.
Correct - it is up to the caller here to coordinate this. The code should be set up so this is not a problem here. In the case of a split-index, the cache entries should be allocated from the memory pool associated with the "most common" / base index. If you found a place I missed or seems questionable, or have suggestions, I would be glad to look into it.
Quoted 3 lines
One solution is to store the index for which the cache entry was created in the cache entry itself, but that does increase its size. Another is
Yes, this is an option. For this initial patch series, I decided to not add extra fields to the cache_entry type, but I think incorporating this in cache_entry is a viable option, and has some positive properties.
Quoted 5 lines
to change the API such that a cache entry is created and added in the same function, and then have some rollback if the cache entry turns out to be invalid (to support add-empty-entry -> fill -> verify), but I don't know if this is feasible. Anyway, all these alternatives should be at least discussed in the commit message, I think.
I can include a discussion of these in the commit message. Thanks.
Quoted 4 lines
The
make_transient_cache_entry()function might be poorly named, since as far as I can tell, the entries produced by that function are actually the longest lasting, since they are never freed.
They should always be freed (and are usually freed close to where they are allocated, or by the calling function). If you see an instance where this is not the case, please point it out, because that is not the intention.
Quoted 6 lines
Along those lines, I was slightly surprised to find out in patch 4 that cache entry freeing is a no-op. That's fine, but in that case, it would be better to delete all the calls to the "discard" function, and document in the others that the entries they create will only be freed when the memory pool itself is discarded.
I can add a comment inside the function body. In the next commit, I do add logic to perform extra (optional) verification in the discard function. I did wrestle with this fact, but I feel there is value in having the locations where it is appropriate to free these entries in code, even if this particular implementation is not utilizing it right now. Hopefully the verification logic added in 5/5 is enough to justify keeping this function around.
Quoted 3 lines
v2 cover Allocate cache entries from memory pool
In reply to Jameson MillerChanges from V1:
- Based patch series off of commit in master
- Minor updates based on initial code review feedback
Summary:
This patch series improves the performance of loading indexes by reducing the number of malloc() calls. Loading the index from disk is partly dominated by the time in malloc(), which is called for each index entry. This patch series reduces the number of times malloc() is called as part of loading the index, and instead allocates a block of memory upfront that is large enough to hold all of the cache entries, and chunks this memory itself. This change builds on [1].
Git previously allocated block of memory for the index cache entries until [2].
This 5 part patch series is broken up as follows:
1/5, 2/5 - Move cache entry lifecycle methods behind an API
3/5 - Fill out memory pool API to include lifecycle and other
methods used in later patches
4/5 - Allocate cache entry structs from memory pool
5/5 - Add extra optional validation
Performance Benchmarks:
To evaluate the performance of this approach, the p0002-read-cache.sh test was run with several combinations of allocators (glibc default, tcmalloc, jemalloc), with and without block allocation, and across several different index sized (100K, 1M, 2M entries). The details on how these repositories were constructed can be found in [3].The p0002-read-cache.sh was run with the iteration count set to 1 and $GIT_PERF_REPEAT_COUNT=10.
The tests were run with iteration count set to 1 because this best approximates the real behavior. The read_cache/discard_cache test will load / free the index N times, and the performance of this logic is different between N = 1 and N > 1. As the production code does not read / discard the index in a loop, a better approximation is when N = 1.
100K
Test baseline block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.02(0.01+0.01) -33.3%
1M:
Test baseline block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.17(0.07+0.09) -26.1%
2M:
Test baseline block_allocation
------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.39(0.17+0.20) -13.3%
With 100K entries, it only takes 0.3 seconds to read the entries even without using block allocation, so there is only a small change in the wall clock time. We can see a larger wall clack improvement with 1M and 2M entries.
For completeness, here is the p0002-read-cache tests for git.git and linux.git:
git.git:
Test baseline block_allocation
---------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1000 times 0.30(0.26+0.03) 0.17(0.13+0.03) -43.3%
linux.git:
Test baseline block_allocation
---------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1000 times 7.05(6.01+0.84) 4.61(3.74+0.66) -34.6%
We also investigated the performance of just using different allocators. We can see that there is not a consistent performance gain.
100K
Test baseline tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.03(0.01+0.01) 0.03(0.01+0.01) +0.0% 0.03(0.02+0.01) +0.0%
1M:
Test baseline tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.23(0.12+0.11) 0.21(0.10+0.10) -8.7% 0.27(0.16+0.10) +17.4%
2M:
Test baseline tcmalloc jemalloc
------------------------------------------------------------------------------------------------------------------
0002.1: read_cache/discard_cache 1 times 0.45(0.26+0.19) 0.46(0.25+0.21) +2.2% 0.57(0.36+0.21) +26.7%
[1] https://public-inbox.org/git/[email protected]/
[2] debed2a629 (read-cache.c: allocate index entries individually - 2011-10-24)
[3] Constructing test repositories:
The test repositories were constructed with t/perf/repos/many_files.sh with the following parameters:
100K: many-files.sh 4 10 9
1M: many-files.sh 5 10 9
2M: many-files.sh 6 8 7
Base Ref: master
Web-Diff: [email protected]:jamill/git.git/commit/2152d28016
Checkout: git fetch [email protected]:jamill/git.git users/jamill/block_allocation-v2 && git checkout 2152d28016
Jameson Miller (5):
read-cache: teach refresh_cache_entry() to take istate block alloc: add lifecycle APIs for cache_entry structs
mem-pool: fill out functionality
block alloc: allocate cache entries from mem_pool block alloc: add validations around cache_entry lifecyle
apply.c | 26 +++--- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 8 +- builtin/reset.c | 6 +- builtin/update-index.c | 26 +++--- cache.h | 40 ++++++++- git.c | 3 + mem-pool.c | 136 ++++++++++++++++++++++++++++- mem-pool.h | 34 ++++++++ merge-recursive.c | 4 +- read-cache.c | 232 +++++++++++++++++++++++++++++++++++++++---------- resolve-undo.c | 6 +- split-index.c | 31 +++++-- tree.c | 4 +- unpack-trees.c | 27 +++--- 16 files changed, 479 insertions(+), 117 deletions(-)
base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d
Signature · 1 line
2.14.3
PATCH v2 1/5 read-cache: teach refresh_cache_entry() to take istate
Refactor refresh_cache_entry() to work on a specific index, instead of implicitly using the_index. This is in preparation for making the make_cache_entry function work on a specific index.
--- cache.h | 2 +- merge-recursive.c | 2 +- read-cache.c | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-)
cache.h +1 -1
@@ -743,7 +743,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);#define REFRESH_IGNORE_SUBMODULES 0x0010/* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020/* user friendly output, not "needs update" */ extern int refresh_index (struct index_state *,unsigned int flags,const struct pathspec *pathspec,char *seen,const char *header_msg); -extern struct cache_entry *refresh_cache_entry (struct cache_entry *,unsigned int ); +extern struct cache_entry *refresh_cache_entry (struct index_state *,struct cache_entry *,unsigned int );/* * Opportunistically update the index but do not complain if we can't.
merge-recursive.c +1 -1
@@ -260,7 +260,7 @@ static int add_cacheinfo(struct merge_options *o,if (refresh) {struct cache_entry *nce; - nce =refresh_cache_entry (ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING ); + nce =refresh_cache_entry (&the_index, ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING );if (!nce)return err (o,_ ("addinfo_cache failed for path '%s'" ), path);if (nce != ce)
read-cache.c +4 -3
@@ -767,7 +767,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, ce->ce_namelen = len; ce->ce_mode =create_ce_mode (mode); - ret =refresh_cache_entry (ce, refresh_options); + ret =refresh_cache_entry (&the_index, ce, refresh_options);if (ret != ce)free (ce);return ret;
@@ -1448,10 +1448,11 @@ int refresh_index(struct index_state *istate, unsigned int flags,return has_errors; } -struct cache_entry *refresh_cache_entry (struct cache_entry *ce, +struct cache_entry *refresh_cache_entry (struct index_state *istate, +struct cache_entry *ce,unsigned int options) { -return refresh_cache_ent (&the_index, ce, options,NULL ,NULL ); +return refresh_cache_ent (istate, ce, options,NULL ,NULL ); }
Signature · 1 line
2.14.3
PATCH v2 2/5 block alloc: add lifecycle APIs for cache_entry structs
In reply to Jameson MillerAdd an API around managing the lifetime of cache_entry structs. Abstracting memory management details behind an API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated / freed. A later commit in this series will allocate cache entries from memory pools as appropriate.
Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated by malloc() calls. This change is in preparation for using memory pools to reduce the number of malloc() calls made when loading an index.
This API makes a distinction between cache entries that are intended for use with a particular index and cache entries that are not. This enables us to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
--- apply.c | 26 ++++++------ blame.c | 5 +-- builtin/checkout.c | 8 ++-- builtin/difftool.c | 8 ++-- builtin/reset.c | 6 +-- builtin/update-index.c | 26 ++++++------ cache.h | 29 +++++++++++++- merge-recursive.c | 2 +- read-cache.c | 105 +++++++++++++++++++++++++++++++++++-------------- resolve-undo.c | 6 ++- split-index.c | 8 ++-- tree.c | 4 +- unpack-trees.c | 27 ++++++++----- 13 files changed, 166 insertions(+), 94 deletions(-)
apply.c +12 -14
@@ -4090,12 +4090,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, oid.hash, name,0 ,0 ); + ce =make_index_cache_entry (&result, patch->old_mode, oid.hash, name,0 ,0 );if (!ce) -return error (_ ("make_cache_entry failed for path '%s'" ), +return error (_ ("make_index_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +discard_index_cache_entry (ce);return error (_ ("could not add %s to temporary index" ), name); }
@@ -4263,12 +4263,11 @@ static int add_index_file(struct apply_state *state,struct stat st;struct cache_entry *ce;int namelen =strlen (path); -unsigned ce_size =cache_entry_size (namelen);if (!state->update_index)return 0 ; - ce =xcalloc (1 , ce_size); + ce =make_empty_index_cache_entry (&the_index, namelen);memcpy (ce->name, path, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (0 );
@@ -4278,13 +4277,13 @@ static int add_index_file(struct apply_state *state,if (!skip_prefix (buf,"Subproject commit " , &s) ||get_oid_hex (s, &ce->oid)) { -free (ce); -return error (_ ("corrupt patch for submodule %s" ), path); +discard_index_cache_entry (ce); +return error (_ ("corrupt patch for submodule %s" ), path); } }else {if (!state->cached) {if (lstat (path, &st) <0 ) { -free (ce); +discard_index_cache_entry (ce);return error_errno (_ ("unable to stat newly " "created file '%s'" ), path);
@@ -4292,13 +4291,13 @@ static int add_index_file(struct apply_state *state,fill_stat_cache_info (ce, &st); }if (write_object_file (buf, size, blob_type, &ce->oid) <0 ) { -free (ce); +discard_index_cache_entry (ce);return error (_ ("unable to create backing store " "for newly created file %s" ), path); } }if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_index_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), path); }
@@ -4422,27 +4421,26 @@ static int add_conflicted_stages_file(struct apply_state *state, struct patch *patch) {int stage, namelen; -unsigned ce_size, mode; +unsigned mode;struct cache_entry *ce;if (!state->update_index)return 0 ; namelen =strlen (patch->new_name); - ce_size =cache_entry_size (namelen); mode = patch->new_mode ? patch->new_mode : (S_IFREG |0644 );remove_file_from_cache (patch->new_name);for (stage =1 ; stage <4 ; stage++) {if (is_null_oid (&patch->threeway_stage[stage -1 ]))continue ; - ce =xcalloc (1 , ce_size); + ce =make_empty_index_cache_entry (&the_index, namelen);memcpy (ce->name, patch->new_name, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = namelen;oidcpy (&ce->oid, &patch->threeway_stage[stage -1 ]);if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_index_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), patch->new_name); }
blame.c +2 -3
@@ -154,7 +154,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,struct strbuf buf =STRBUF_INIT ;const char *ident;time_t now; -int size, len; +int len;struct cache_entry *ce;unsigned mode;struct strbuf msg =STRBUF_INIT ;
@@ -252,8 +252,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,/* Let's not bother reading from HEAD tree */ mode =S_IFREG |0644 ; } - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, &origin->blob_oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 );
builtin/checkout.c +4 -4
@@ -77,7 +77,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,return READ_TREE_RECURSIVE ; len = base->len +strlen (pathname); - ce =xcalloc (1 ,cache_entry_size (len)); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, base->buf, base->len);memcpy (ce->name + base->len, pathname, len - base->len);
@@ -96,7 +96,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,if (ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { old->ce_flags |=CE_UPDATE ; -free (ce); +discard_index_cache_entry (ce);return 0 ; } }
@@ -230,11 +230,11 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, oid.hash, path,2 ,0 ); + ce =make_transient_cache_entry (mode, oid.hash, path,2 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL ); -free (ce); +discard_transient_cache_entry (ce);return status; }
builtin/difftool.c +4 -4
@@ -321,10 +321,10 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid->hash, path,0 ,0 ); + ce =make_transient_cache_entry (mode, oid->hash, path,0 ); ret =checkout_entry (ce, state,NULL ); -free (ce); +discard_transient_cache_entry (ce);return ret; }
@@ -488,8 +488,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, roid.hash, - dst_path,0 ,0 ); +make_index_cache_entry (&wtindex, rmode, roid.hash, + dst_path,0 ,0 );add_index_entry (&wtindex, ce2,ADD_CACHE_JUST_APPEND );
builtin/reset.c +3 -3
@@ -134,10 +134,10 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, one->oid.hash, one->path, -0 ,0 ); + ce =make_index_cache_entry (&the_index, one->mode, one->oid.hash, one->path, +0 ,0 );if (!ce) -die (_ ("make_cache_entry failed for path '%s'" ), +die (_ ("make_index_cache_entry failed for path '%s'" ), one->path);if (is_missing) { ce->ce_flags |=CE_INTENT_TO_ADD ;
builtin/update-index.c +11 -15
@@ -268,15 +268,14 @@ static int process_lstat_error(const char *path, int err)static int add_one_path (const struct cache_entry *old,const char *path,int len,struct stat *st) { -int option, size; +int option;struct cache_entry *ce;/* Was the old index entry already up-to-date? */ if (old && !ce_stage (old) && !ce_match_stat (old, st,0 ))return 0 ; - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 ); ce->ce_namelen = len;
@@ -285,13 +284,13 @@ static int add_one_path(const struct cache_entry *old, const char *path, int lenif (index_path (&ce->oid, path, st, info_only ?0 :HASH_WRITE_OBJECT )) { -free (ce); +discard_index_cache_entry (ce);return -1 ; } option = allow_add ?ADD_CACHE_OK_TO_ADD :0 ; option |= allow_replace ?ADD_CACHE_OK_TO_REPLACE :0 ;if (add_cache_entry (ce, option)) { -free (ce); +discard_index_cache_entry (ce);return error ("%s: cannot add to the index - missing --add option?" , path); }return 0 ;
@@ -403,15 +402,14 @@ static int process_path(const char *path)static int add_cacheinfo (unsigned int mode,const struct object_id *oid,const char *path,int stage) { -int size, len, option; +int len, option;struct cache_entry *ce;if (!verify_path (path))return error ("Invalid path '%s'" , path); len =strlen (path); - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, path, len);
@@ -589,7 +587,6 @@ static struct cache_entry *read_one_ent(const char *which, {unsigned mode;struct object_id oid; -int size;struct cache_entry *ce;if (get_tree_entry (ent, path, &oid, &mode)) {
@@ -602,8 +599,7 @@ static struct cache_entry *read_one_ent(const char *which,error ("%s: not a blob in %s branch." , path, which);return NULL ; } - size =cache_entry_size (namelen); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, namelen);oidcpy (&ce->oid, &oid);memcpy (ce->name, path, namelen);
@@ -680,8 +676,8 @@ static int unresolve_one(const char *path)error ("%s: cannot add their version to the index." , path); ret =-1 ; free_return: -free (ce_2); -free (ce_3); +discard_index_cache_entry (ce_2); +discard_index_cache_entry (ce_3);return ret; }
@@ -748,7 +744,7 @@ static int do_reupdate(int ac, const char **av, ce->name, ce_namelen(ce), 0);if (old && ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { -free (old); +discard_index_cache_entry (old);continue ;/* unchanged */ }/* Be careful. The working tree may not have the
@@ -759,7 +755,7 @@ static int do_reupdate(int ac, const char **av, path =xstrdup (ce->name);update_one (path);free (path); -free (old); +discard_index_cache_entry (old);if (save_nr != active_nr) goto redo; }
cache.h +28 -1
@@ -339,6 +339,34 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and freeing */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_index_cache_entry`. +*/ +extern struct cache_entry *make_index_cache_entry (struct index_state *istate,unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options); +extern struct cache_entry *make_empty_index_cache_entry (struct index_state *istate,size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_transient_cache_entry`. +*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage); +extern struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry allocated via `make_*_index_cache_entry`. +*/ +void discard_index_cache_entry (struct cache_entry *ce); + +/* +* Discard cache entry allocated via `make_*_transient_cache_entry`. +*/ +void discard_transient_cache_entry (struct cache_entry *ce); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
@@ -690,7 +718,6 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options);extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -252,7 +252,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid->hash : null_sha1, path, stage,0 ); + ce =make_index_cache_entry (&the_index, mode, oid ? oid->hash : null_sha1, path, stage,0 );if (!ce)return err (o,_ ("addinfo_cache failed for path '%s'" ), path);
read-cache.c +75 -30
241 lines not on this page · raw message
resolve-undo.c +4 -2
@@ -146,8 +146,10 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], ru->oid[i].hash, - name, i +1 ,0 ); + nce =make_index_cache_entry (istate, + ru->mode[i], + ru->oid[i].hash, + name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;if (add_index_entry (istate, nce,ADD_CACHE_OK_TO_ADD )) {
split-index.c +4 -4
@@ -123,7 +123,7 @@ static void replace_entry(size_t pos, void *data) src->ce_flags |=CE_UPDATE_IN_BASE ; src->ce_namelen = dst->ce_namelen;copy_cache_entry (dst, src); -free (src); +discard_index_cache_entry (src); si->nr_replacements++; }
@@ -224,7 +224,7 @@ void prepare_to_write_split_index(struct index_state *istate) base->ce_flags = base_flags;if (ret) ce->ce_flags |=CE_UPDATE_IN_BASE ; -free (base); +discard_index_cache_entry (base); si->base->cache[ce->index -1 ] = ce; }for (i =0 ; i < si->base->cache_nr; i++) {
@@ -301,7 +301,7 @@ void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce ce == istate->split_index->base->cache[ce->index - 1]) ce->ce_flags |= CE_REMOVE; else -free (ce ); +discard_index_cache_entry (ce ); } void replace_index_entry_in_base(struct index_state *istate,
@@ -314,7 +314,7 @@ void replace_index_entry_in_base(struct index_state *istate, old_entry->index <= istate->split_index->base->cache_nr) { new_entry->index = old_entry->index;if (old_entry != istate->split_index->base->cache[new_entry->index -1 ]) -free (istate->split_index->base->cache[new_entry->index -1 ]); +discard_index_cache_entry (istate->split_index->base->cache[new_entry->index -1 ]); istate->split_index->base->cache[new_entry->index -1 ] = new_entry; } }
tree.c +1 -3
@@ -16,15 +16,13 @@ static int read_one_entry_opt(struct index_state *istate, unsigned mode, int stage, int opt) { int len; -unsigned int size;struct cache_entry *ce;if (S_ISDIR (mode))return READ_TREE_RECURSIVE ; len =strlen (pathname); - size =cache_entry_size (baselen + len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (istate, baselen + len); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage);
unpack-trees.c +17 -10
@@ -192,10 +192,10 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce) +static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) {unsigned int size =ce_size (ce); -struct cache_entry *new_entry =xmalloc (size); +struct cache_entry *new_entry =make_empty_index_cache_entry (istate,ce_namelen (ce));memcpy (new_entry, ce, size);return new_entry;
@@ -205,7 +205,7 @@ static void add_entry(struct unpack_trees_options *o,const struct cache_entry *ce, unsigned int set, unsigned int clear) { -do_add_entry (o ,dup_entry (ce ),set ,clear ); + do_add_entry(o, dup_entry(ce, &o->result), set, clear); }/*
@@ -786,10 +786,17 @@ static int ce_in_traverse_path(const struct cache_entry *ce,return (info->pathlen <ce_namelen (ce)); } -static struct cache_entry *create_ce_entry (const struct traverse_info *info,const struct name_entry *n,int stage) +static struct cache_entry *create_ce_entry (const struct traverse_info *info, +const struct name_entry *n, +int stage, +struct index_state *istate, +int is_transient) {int len =traverse_path_len (info, n); -struct cache_entry *ce =xcalloc (1 ,cache_entry_size (len)); +struct cache_entry *ce = + is_transient ? +make_empty_transient_cache_entry (len) : +make_empty_index_cache_entry (istate, len); ce->ce_mode =create_ce_mode (n->mode); ce->ce_flags =create_ce_flags (stage);
@@ -835,7 +842,7 @@ static int unpack_nondirectories(int n, unsigned long mask, stage =3 ; else stage =2 ; - src[i + o->merge] =create_ce_entry (info, names + i, stage); + src[i + o->merge] =create_ce_entry (info, names + i, stage, &o->result, o->merge); }if (o->merge) {
@@ -844,7 +851,7 @@ static int unpack_nondirectories(int n, unsigned long mask,for (i =0 ; i < n; i++) {struct cache_entry *ce = src[i + o->merge];if (ce != o->df_conflict_entry) -free (ce); +discard_transient_cache_entry (ce); }return rc; }
@@ -1765,7 +1772,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce); +struct cache_entry *merge =dup_entry (ce, &o->result);if (!old) {/*
@@ -1785,7 +1792,7 @@ static int merged_entry(const struct cache_entry *ce,if (verify_absent (merge,ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN , o)) { -free (merge); +discard_index_cache_entry (merge);return -1 ; }invalidate_ce_path (merge, o);
@@ -1811,7 +1818,7 @@ static int merged_entry(const struct cache_entry *ce, update =0 ; }else {if (verify_uptodate (old, o)) { -free (merge); +discard_index_cache_entry (merge);return -1 ; }/* Migrate old flags over */
Signature · 1 line
2.14.3
PATCH v2 3/5 mem-pool: fill out functionality
In reply to Jameson MillerAdds the following functionality to memory pools:
- Lifecycle management functions (init, discard)
- Test whether a memory location is part of the managed pool
- Function to combine 2 pools
This also adds logic to track all memory allocations made by a memory pool.
These functions will be used in a future commit in this commit series.
--- mem-pool.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- mem-pool.h | 32 +++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-)
mem-pool.c +110 -4
@@ -5,6 +5,8 @@#include "cache.h" #include "mem-pool.h" +#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block); +static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc) {struct mp_block *p;
@@ -19,6 +21,60 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t breturn p; } +static void *mem_pool_alloc_custom (struct mem_pool *mem_pool,size_t block_alloc) +{ +char *p; +ALLOC_GROW (mem_pool->custom, mem_pool->nr +1 , mem_pool->alloc); +ALLOC_GROW (mem_pool->custom_end, mem_pool->nr_end +1 , mem_pool->alloc_end); + + p =xmalloc (block_alloc); + mem_pool->custom[mem_pool->nr++] = p; + mem_pool->custom_end[mem_pool->nr_end++] = p + block_alloc; + + mem_pool->pool_alloc += block_alloc; + +return mem_pool->custom[mem_pool->nr]; +} + +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size) +{ +if (!(*mem_pool)) + { + *mem_pool =xmalloc (sizeof (struct mem_pool )); + (*mem_pool)->pool_alloc =0 ; + (*mem_pool)->mp_block =NULL ; + (*mem_pool)->block_alloc =BLOCK_GROWTH_SIZE ; + (*mem_pool)->custom =NULL ; + (*mem_pool)->nr =0 ; + (*mem_pool)->alloc =0 ; + (*mem_pool)->custom_end =NULL ; + (*mem_pool)->nr_end =0 ; + (*mem_pool)->alloc_end =0 ; + +if (initial_size >0 ) +mem_pool_alloc_block (*mem_pool, initial_size); + } +} + +void mem_pool_discard (struct mem_pool *mem_pool) +{ +int i; +struct mp_block *block, *block_to_free; +for (block = mem_pool->mp_block; block;) + { + block_to_free = block; + block = block->next_block; +free (block_to_free); + } + +for (i =0 ; i < mem_pool->nr; i++) +free (mem_pool->custom[i]); + +free (mem_pool->custom); +free (mem_pool->custom_end); +free (mem_pool); +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p;
@@ -33,10 +89,8 @@ void *mem_pool_alloc(struct mem_pool *mem_pool, size_t len)break ;if (!p) { -if (len >= (mem_pool->block_alloc /2 )) { - mem_pool->pool_alloc += len; -return xmalloc (len); - } +if (len >= (mem_pool->block_alloc /2 )) +return mem_pool_alloc_custom (mem_pool, len); p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc); }
@@ -53,3 +107,55 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +int i; +struct mp_block *p; + +/* Check if memory is allocated in a block */ +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +/* Check if memory is allocated in custom block */ +for (i =0 ; i < mem_pool->nr; i++) +if ((mem >= mem_pool->custom[i]) && + (mem < mem_pool->custom_end[i])) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +int i; +struct mp_block **tail = &dst->mp_block; + +/* Find pointer of dst's last block (if any) */ +while (*tail) + tail = &(*tail)->next_block; + +/* Append the blocks from src to dst */ + *tail = src->mp_block; + +/* Combine custom allocations */ +ALLOC_GROW (dst->custom, dst->nr + src->nr, dst->alloc); +ALLOC_GROW (dst->custom_end, dst->nr_end + src->nr_end, dst->alloc_end); + +for (i =0 ; i < src->nr; i++) { + dst->custom[dst->nr++] = src->custom[i]; + dst->custom_end[dst->nr_end++] = src->custom_end[i]; + } + + dst->pool_alloc += src->pool_alloc; + src->pool_alloc =0 ; + src->mp_block =NULL ; + src->custom =NULL ; + src->nr =0 ; + src->alloc =0 ; + src->custom_end =NULL ; + src->nr_end =0 ; + src->alloc_end =0 ; +}
mem-pool.h +32 -0
@@ -19,8 +19,27 @@ struct mem_pool {/* The total amount of memory allocated by the pool. */ size_t pool_alloc; + +/* +* Array of pointers to "custom size" memory allocations. +* This is used for "large" memory allocations. +* The *_end variables are used to track the range of memory +* allocated. +*/ +void **custom, **custom_end; +int nr, nr_end, alloc, alloc_end; }; +/* +* Initialize mem_pool specified initial. +*/ +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size); + +/* +* Discard a memory pool and free all the memory it is responsible for. +*/ +void mem_pool_discard (struct mem_pool *mem_pool); +/* * Alloc memory from the mem_pool. */
@@ -31,4 +50,17 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len);*/ void *mem_pool_calloc (struct mem_pool *pool,size_t count,size_t size); +/* +* Move the memory associated with the 'src' pool to the 'dst' pool. The 'src' +* pool will be empty and not contain any memory. It still needs to be free'd +* with a call to `mem_pool_discard`. +*/ +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src); + +/* +* Check if a memory pointed at by 'mem' is part of the range of +* memory managed by the specified mem_pool. +*/ +int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +#endif
Signature · 1 line
2.14.3
Re: [PATCH v2 3/5] mem-pool: fill out functionality
Quoted 26 lines · Jameson Miller
Adds the following functionality to memory pools:
- Lifecycle management functions (init, discard)
- Test whether a memory location is part of the managed pool
- Function to combine 2 pools
This also adds logic to track all memory allocations made by a memory pool.
These functions will be used in a future commit in this commit series.
Signed-off-by Jameson Miller <[email protected]>--- mem-pool.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- mem-pool.h | 32 +++++++++++++++++mem-pool.h +0
-0@@ -19,8 +19,27 @@ struct mem_pool {/* The total amount of memory allocated by the pool. */ size_t pool_alloc;
mem-pool.h struct mem_pool { · line 31
Quoted 10 lines
mem-pool.h +9
-0+ +/* +* Array of pointers to "custom size" memory allocations. +* This is used for "large" memory allocations. +* The *_end variables are used to track the range of memory +* allocated. +*/ +void **custom, **custom_end; +int nr, nr_end, alloc, alloc_end; };
What is the design goal of this mem pool?
What is it really good at, which patterns of use should we avoid?
It looks like internally the mem-pool can either use mp_blocks that are stored as a linked list, or it can have custom allocations stored in an array.
Is the linked list or the custom part sorted by some key? Does it need to be sorted?
I am currently looking at alloc.c, which is really good for allocating memory for equally sized parts, i.e. it is very efficient at providing memory for fixed sized structs. And on top of that it is not tracking any memory as it relies on program termination for cleanup.
This memory pool seems to be optimized for allocations of varying sizes, some of them huge (to be stored in the custom part) and most of them rather small as they go into the mp_blocks?
Thanks,
Stefan
RE: [PATCH v2 3/5] mem-pool: fill out functionality
Thank you for taking a look - I think these are good questions. Please let me know if you have further questions.
Quoted 35 lines
-----Original Message-----
From: Stefan Beller <[email protected]>
Sent: Monday, April 30, 2018 5:42 PM
To: Jameson Miller <[email protected]>
Cc: [email protected]; [email protected]; [email protected]; [email protected]
Subject: Re: [PATCH v2 3/5] mem-pool: fill out functionalityQuoted 25 lines · Jameson Miller
Adds the following functionality to memory pools:
- Lifecycle management functions (init, discard)
- Test whether a memory location is part of the managed pool
- Function to combine 2 pools
This also adds logic to track all memory allocations made by a memory pool.
These functions will be used in a future commit in this commit series.
Signed-off-by Jameson Miller <[email protected]>--- mem-pool.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- mem-pool.h | 32 +++++++++++++++++mem-pool.h +0
-0@@ -19,8 +19,27 @@ struct mem_pool {
Quoted 16 lines
mem-pool.h struct mem_pool { · line 31
Quoted 12 lines
mem-pool.h +9
-0/* The total amount of memory allocated by the pool. */ size_t pool_alloc; + +/* +* Array of pointers to "custom size" memory allocations. +* This is used for "large" memory allocations. +* The *_end variables are used to track the range of memory +* allocated. +*/ +void **custom, **custom_end; +int nr, nr_end, alloc, alloc_end; };What is the design goal of this mem pool?
What is it really good at, which patterns of use should we avoid?
This memory pool is designed to provide many small (small compared to the memory pool block size) chunks of memory from a larger block of allocated memory. This reduces the overhead of performing many small memory allocations from the heap. In the ideal case, we know the total amount of memory required, and the pool can make a single allocation to satisfy that requirement, and hand it out in chunks to consumers.
We should avoid making many large memory requests (large compared to the memory pool block size), as these requests will be fulfilled from individual memory allocations (i.e. the "custom" allocations). While there is not a correctness issue here, it will not perform as well when requests are fulfilled from the internal memory blocks.
Quoted 10 lines
It looks like internally the mem-pool can either use
mp_blocksthat are stored as a linked list, or it can have custom allocations stored in an array.Is the linked list or the custom part sorted by some key? Does it need to be sorted?
I am currently looking at
alloc.c, which is really good for allocating memory for equally sized parts, i.e. it is very efficient at providing memory for fixed sized structs. And on top of that it is not tracking any memory as it relies on program termination for cleanup.
The linked list is ordered in the order the blocks were allocated in. The last allocated block will be the head of the linked list. This means that most memory requests should be fulfilled by the head block, reducing the need to iterate through the list to find available memory.
The custom memory blocks are in their own list because they will never contain any free memory. There is no need to include these allocations in the list of blocks that could potentially have free memory.
I expect this code would be efficient at allocating many equal sized parts, as long as those parts are comparitively small compared to the block size. In this case, you would never allocate "custom" blocks, and the overwhelming majority of allocations would come from the head block. If you know the total amount of memory you will need, then you can size the memory pool so all allocations come from the head block.
Quoted 4 lines
This memory pool seems to be optimized for allocations of varying sizes, some of them huge (to be stored in the custom
part) and most of them rather small as they go into themp_blocks?
I would say this memory pool is optimized for allocations of varying sizes (although it should be pretty efficient when the allocations are of the same size), but can handle the edge case when there happens to be a need for a "huge allocation".
Quoted 3 lines
Thanks,
Stefan
Re: [PATCH v2 3/5] mem-pool: fill out functionality
In reply to Jameson MillerAnother I noticed in the jm/mem-pool series is this loop in mem_pool_alloc()
for (p = mem_pool->mp_block; p; p = p->next_block)
if (p->end - p->next_free >= len)
break;You always go from the start (mp_block) but at some point those first blocks are filled up and we don't really need to walk from the start anymore. If we allow the mem-pool user to set a "minimum alloc" limit, then we can determine if the remaining space in a block is not useful for any future allocation, we can just skip it and start looking for an available from a new pointer, avail_block or something.
I'm writing this with alloc.c in mind because we have a lot more blocks to allocate there. Unlike read-cache, you can't really estimate how many mp_blocks you're going to need. This linked list could become long. And because alloc.c does fixed size allocation, we use up one block after another and will never find free space in previous blocks.
mem-pool.c line 8
Quoted 9 lines · Jameson Miller
mem-pool.c +1
-0@@ -5,6 +5,8 @@#include "cache.h" #include "mem-pool.h" +#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block);
#define BLOCK_GROWTH_SIZE (1024*1024 - sizeof(struct mp_block))
(wrapped in brackets and no trailing semicolon)
mem-pool.c static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t b · line 26
Quoted 11 lines
mem-pool.c +4
-0+static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc) {struct mp_block *p;@@ -19,6 +21,60 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t breturn p; } +static void *mem_pool_alloc_custom (struct mem_pool *mem_pool,size_t block_alloc) +{ +char *p;
An empty line between variable declaration and function body would be nice.
mem-pool.c static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t b · line 29
Quoted 3 lines
mem-pool.c +3
-0+ALLOC_GROW (mem_pool->custom, mem_pool->nr +1 , mem_pool->alloc); +ALLOC_GROW (mem_pool->custom_end, mem_pool->nr_end +1 , mem_pool->alloc_end); +
If you put both custom and custom_end in a struct, then you can grow just one array (of the new struct) and have fewer support variables like nr_end and alloc_end
The only downside that I can see is the potential padding between struct increasing memory consumption here. but since you don't care about reallocation cost (i.e. large memory allocations should be rare), it probably does not matter either.
But wait, can we just reuse struct mp_block for this? You allocate a new mp_block (for just one allocation) as usual, then you can can maintain a linked list of custom alloc in "struct mp_block *mp_custom_block" or something. This way we can walk both bulk and custom allocation the same way.
mem-pool.c static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t b · line 41
Quoted 12 lines
mem-pool.c +12
-0+ p =xmalloc (block_alloc); + mem_pool->custom[mem_pool->nr++] = p; + mem_pool->custom_end[mem_pool->nr_end++] = p + block_alloc; + + mem_pool->pool_alloc += block_alloc; + +return mem_pool->custom[mem_pool->nr]; +} + +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size) +{ +if (!(*mem_pool))
I think (!*mem_pool) should be enough, although you could avoid the operator precedence headache by doing
if (*mem_pool)
return;
mem-pool.c static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t b · line 43
Quoted 2 lines
mem-pool.c +2
-0+ { + *mem_pool =xmalloc (sizeof (struct mem_pool ));
I think we tend to do *mem_pool = xmalloc(sizeof(**mem_pool));
mem-pool.c static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t b · line 44
Quoted 1 line
mem-pool.c +1
-0+ (*mem_pool)->pool_alloc =0 ;
Eghh.. perhaps just declare
struct mem_pool *pool;
then allocate a new memory to this, initialize everything and only do
*mem_pool = pool;
at the end? It's much less of this (*mem_pool)->
mem-pool.c static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t b · line 45
Quoted 1 line
mem-pool.c +1
-0+ (*mem_pool)->mp_block =NULL ;
Just memset() it once (or use xcallo) and only initialize non-zero/null fields afterwards.
Quoted 157 lines
mem-pool.c +86
-4+ (*mem_pool)->block_alloc =BLOCK_GROWTH_SIZE ; + (*mem_pool)->custom =NULL ; + (*mem_pool)->nr =0 ; + (*mem_pool)->alloc =0 ; + (*mem_pool)->custom_end =NULL ; + (*mem_pool)->nr_end =0 ; + (*mem_pool)->alloc_end =0 ; + +if (initial_size >0 ) +mem_pool_alloc_block (*mem_pool, initial_size); + } +} + +void mem_pool_discard (struct mem_pool *mem_pool) +{ +int i; +struct mp_block *block, *block_to_free; +for (block = mem_pool->mp_block; block;) + { + block_to_free = block; + block = block->next_block; +free (block_to_free); + } + +for (i =0 ; i < mem_pool->nr; i++) +free (mem_pool->custom[i]); + +free (mem_pool->custom); +free (mem_pool->custom_end); +free (mem_pool); +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p;@@ -33,10 +89,8 @@ void *mem_pool_alloc(struct mem_pool *mem_pool, size_t len)break ;if (!p) { -if (len >= (mem_pool->block_alloc /2 )) { - mem_pool->pool_alloc += len; -return xmalloc (len); - } +if (len >= (mem_pool->block_alloc /2 )) +return mem_pool_alloc_custom (mem_pool, len); p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc); }@@ -53,3 +107,55 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +int i; +struct mp_block *p; + +/* Check if memory is allocated in a block */ +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +/* Check if memory is allocated in custom block */ +for (i =0 ; i < mem_pool->nr; i++) +if ((mem >= mem_pool->custom[i]) && + (mem < mem_pool->custom_end[i])) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +int i; +struct mp_block **tail = &dst->mp_block; + +/* Find pointer of dst's last block (if any) */ +while (*tail) + tail = &(*tail)->next_block; + +/* Append the blocks from src to dst */ + *tail = src->mp_block; + +/* Combine custom allocations */ +ALLOC_GROW (dst->custom, dst->nr + src->nr, dst->alloc); +ALLOC_GROW (dst->custom_end, dst->nr_end + src->nr_end, dst->alloc_end); + +for (i =0 ; i < src->nr; i++) { + dst->custom[dst->nr++] = src->custom[i]; + dst->custom_end[dst->nr_end++] = src->custom_end[i]; + } + + dst->pool_alloc += src->pool_alloc; + src->pool_alloc =0 ; + src->mp_block =NULL ; + src->custom =NULL ; + src->nr =0 ; + src->alloc =0 ; + src->custom_end =NULL ; + src->nr_end =0 ; + src->alloc_end =0 ; +}mem-pool.h +32
-1@@ -19,8 +19,27 @@ struct mem_pool {/* The total amount of memory allocated by the pool. */ size_t pool_alloc; + +/* +* Array of pointers to "custom size" memory allocations. +* This is used for "large" memory allocations. +* The *_end variables are used to track the range of memory +* allocated. +*/ +void **custom, **custom_end; +int nr, nr_end, alloc, alloc_end; }; +/* +* Initialize mem_pool specified initial. +*/ +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size); + +/* +* Discard a memory pool and free all the memory it is responsible for. +*/ +void mem_pool_discard (struct mem_pool *mem_pool); +/* * Alloc memory from the mem_pool. */ @@ -31,4 +50,17 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len);*/ void *mem_pool_calloc (struct mem_pool *pool,size_t count,size_t size); +/* +* Move the memory associated with the 'src' pool to the 'dst' pool. The 'src' +* pool will be empty and not contain any memory. It still needs to be free'd +* with a call to `mem_pool_discard`. +*/ +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src); + +/* +* Check if a memory pointed at by 'mem' is part of the range of +* memory managed by the specified mem_pool. +*/ +int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +#endif --2.14.3
Signature · 1 line
Duy
PATCH v2 4/5 block alloc: allocate cache entries from mem_pool
In reply to Jameson MillerWhen reading large indexes from disk, a portion of the time is dominated in malloc() calls. This can be mitigated by allocating a large block of memory and manage it ourselves via memory pools.
This change moves the cache entry allocation to be on top of memory pools.
Design:
The index_state struct will gain a notion of an associated memory_pool from which cache_entries will be allocated from. When reading in the index from disk, we have information on the number of entries and their size, which can guide us in deciding how large our initial memory allocation should be. When an index is discarded, the associated memory_pool will be discarded as well - so the lifetime of a cache_entry is tied to the lifetime of the index_state that it was allocated for.
In the case of a Split Index, the following rules are followed. 1st, some terminology is defined:
Terminology:
- 'the_index': represents the logical view of the index
- 'split_index': represents the "base" cache entries. Read from the split index file.
'the_index' can reference a single split_index, as well as cache_entries from the split_index. the_index will be discarded before the split_index is. This means that when we are allocating cache_entries in the presence of a split index, we need to allocate the entries from the split_index's memory pool. This allows us to follow the pattern that the_index can reference cache_entries from the split_index, and that the cache_entries will not be freed while they are still being referenced.
Alternatives:
The current design does not track whether a cache_entry is allocated from a pool or not. Instead, it relies on the caller to know how the cache_entry will be used and to handle its lifecyle appropriately, including calling the correct free() method. Instead, we could include a bit of information in the cache_entry (either a bit indicating whether cache_entry was allocated from a pool or not, or possibly even a pointer back to the allocating pool), which can then be used to make informed decisions about these objects. The downside of this approach is that the cache_entry type would need to grow to incorporate this information.
--- cache.h | 2 ++ read-cache.c | 95 +++++++++++++++++++++++++++++++++++++++++++++-------------- split-index.c | 23 +++++++++++++-- 3 files changed, 95 insertions(+), 25 deletions(-)
cache.h +2 -0
@@ -15,6 +15,7 @@#include "path.h" #include "sha1-array.h" #include "repository.h" +#include "mem-pool.h" #include <zlib.h> typedef struct git_zstream {
@@ -328,6 +329,7 @@ struct index_state {struct untracked_cache *untracked;uint64_t fsmonitor_last_update;struct ewah_bitmap *fsmonitor_dirty; +struct mem_pool *ce_mem_pool; };extern struct index_state the_index;
read-cache.c +73 -22
176 lines not on this page · raw message
split-index.c +20 -3
@@ -73,16 +73,31 @@ void move_cache_to_base_index(struct index_state *istate)int i;/* -* do not delete old si->base, its index entries may be shared -* with istate->cache[]. Accept a bit of leaking here because -* this code is only used by short-lived update-index. +* If there was a previous base index, then transfer ownership of allocated +* entries to the parent index. */ +if (si->base && + si->base->ce_mem_pool) { + +if (!istate->ce_mem_pool) +mem_pool_init (&istate->ce_mem_pool,0 ); + +mem_pool_combine (istate->ce_mem_pool, istate->split_index->base->ce_mem_pool); + } + si->base =xcalloc (1 ,sizeof (*si->base)); si->base->version = istate->version;/* zero timestamp disables racy test in ce_write_index() */ si->base->timestamp = istate->timestamp;ALLOC_GROW (si->base->cache, istate->cache_nr, si->base->cache_alloc); si->base->cache_nr = istate->cache_nr; + +/* +* The mem_pool needs to move with the allocated entries. +*/ + si->base->ce_mem_pool = istate->ce_mem_pool; + istate->ce_mem_pool =NULL ; +COPY_ARRAY (si->base->cache, istate->cache, istate->cache_nr);mark_base_index_entries (si->base);for (i =0 ; i < si->base->cache_nr; i++)
@@ -330,6 +345,8 @@ void add_split_index(struct index_state *istate)void remove_split_index (struct index_state *istate) {if (istate->split_index) { +mem_pool_combine (istate->ce_mem_pool, istate->split_index->base->ce_mem_pool); +/* * can't discard_split_index(&the_index); because that * will destroy split_index->base->cache[], which may
Signature · 1 line
2.14.3
PATCH v2 5/5 block alloc: add validations around cache_entry lifecyle
In reply to Jameson MillerAdd an option (controlled by an environment variable) perform extra validations on mem_pool allocated cache entries. When set:
1) Invalidate cache_entry memory when discarding cache_entry.
2) When discarding index_state struct, verify that all cache_entries were allocated from expected mem_pool.
3) When discarding mem_pools, invalidate mem_pool memory.
This should provide extra checks that mem_pools and their allocated cache_entries are being used as expected.
--- cache.h | 7 +++++++ git.c | 3 +++ mem-pool.c | 24 +++++++++++++++++++++++- mem-pool.h | 2 ++ read-cache.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-)
cache.h +7 -0
@@ -369,6 +369,13 @@ void discard_index_cache_entry(struct cache_entry *ce);*/ void discard_transient_cache_entry (struct cache_entry *ce); +/* +* Validate the cache entries in the index. This is an internal +* consistency check that the cache_entry structs are allocated from +* the expected memory pool. +*/ +void validate_cache_entries (const struct index_state *istate); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
git.c +3 -0
@@ -347,7 +347,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)trace_argv_printf (argv,"trace: built-in: git" ); +validate_cache_entries (&the_index); status = p->fn (argc, argv, prefix); +validate_cache_entries (&the_index); +if (status)return status;
mem-pool.c +23 -1
@@ -60,21 +60,43 @@ void mem_pool_discard(struct mem_pool *mem_pool) {int i;struct mp_block *block, *block_to_free; +int invalidate_memory =should_validate_cache_entries (); +for (block = mem_pool->mp_block; block;) { block_to_free = block; block = block->next_block; + +if (invalidate_memory) +memset (block_to_free->space,0xDD , ((char *)block_to_free->end) - ((char *)block_to_free->space)); +free (block_to_free); } -for (i =0 ; i < mem_pool->nr; i++) +for (i =0 ; i < mem_pool->nr; i++) { +memset (mem_pool->custom[i],0xDD , ((char *)mem_pool->custom_end[i]) - ((char *)mem_pool->custom[i]));free (mem_pool->custom[i]); + }free (mem_pool->custom);free (mem_pool->custom_end);free (mem_pool); } +int should_validate_cache_entries (void ) +{ +static int validate_index_cache_entries =-1 ; + +if (validate_index_cache_entries <0 ) { +if (getenv ("GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES" )) + validate_index_cache_entries =1 ; +else + validate_index_cache_entries =0 ; + } + +return validate_index_cache_entries; +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p;
mem-pool.h +2 -0
@@ -63,4 +63,6 @@ void mem_pool_combine(struct mem_pool *dst, struct mem_pool *src);*/ int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +int should_validate_cache_entries (void ); +#endif
read-cache.c +47 -0
77 lines not on this page · raw message
Signature · 1 line
2.14.3
Re: [PATCH v2 5/5] block alloc: add validations around cache_entry lifecyle
Quoted 9 lines · Jameson Miller
Add an option (controlled by an environment variable) perform extra validations on
mem_poolallocated cache entries. When set:1) Invalidate
cache_entrymemory when discardingcache_entry.2) When discarding
index_statestruct, verify that allcache_entrieswere allocated from expectedmem_pool.3) When discarding
mem_pools, invalidatemem_poolmemory.
On linux step 3 could be better achieved by allocating blocks with mmap() and freeing them with munmap(). Access to already munmap()'d blocks will result in segmentation fault regardless of values. I guess Windows also has something similar (I vaguely remember something about "locking memory" and stuff, but my win32 knowledge is decade old at this point)
(Actually with glibc on linux, i'm pretty sure mmap is already used for large allocation so step 3 is achieved without doing anything; not sure about other libc implementations)
Quoted 50 lines
This should provide extra checks that
mem_poolsand their allocatedcache_entriesare being used as expected.Signed-off-by Jameson Miller <[email protected]>--- cache.h | 7 +++++++ git.c | 3 +++ mem-pool.c | 24 +++++++++++++++++++++++- mem-pool.h | 2 ++ read-cache.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-)cache.h +7
-0@@ -369,6 +369,13 @@ void discard_index_cache_entry(struct cache_entry *ce);*/ void discard_transient_cache_entry (struct cache_entry *ce); +/* +* Validate the cache entries in the index. This is an internal +* consistency check that the cache_entry structs are allocated from +* the expected memory pool. +*/ +void validate_cache_entries (const struct index_state *istate); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)git.c +3
-0@@ -347,7 +347,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)trace_argv_printf (argv,"trace: built-in: git" ); +validate_cache_entries (&the_index); status = p->fn (argc, argv, prefix); +validate_cache_entries (&the_index); +if (status)return status;mem-pool.c +0
-0
mem-pool.c void mem_pool_discard(struct mem_pool *mem_pool) · line 63
Quoted 4 lines
mem-pool.c +1
-0@@ -60,21 +60,43 @@ void mem_pool_discard(struct mem_pool *mem_pool) {int i;struct mp_block *block, *block_to_free; +int invalidate_memory =should_validate_cache_entries ();
Heh.. cache-entries logic should not enter mem-pool.c.
Quoted 5 lines
mem-pool.c +1
-0+for (block = mem_pool->mp_block; block;) { block_to_free = block; block = block->next_block;
mem-pool.c void mem_pool_discard(struct mem_pool *mem_pool) · line 77
Quoted 10 lines
mem-pool.c +6
-1+ +if (invalidate_memory) +memset (block_to_free->space,0xDD , ((char *)block_to_free->end) - ((char *)block_to_free->space)); +free (block_to_free); } -for (i =0 ; i < mem_pool->nr; i++) +for (i =0 ; i < mem_pool->nr; i++) { +memset (mem_pool->custom[i],0xDD , ((char *)mem_pool->custom_end[i]) - ((char *)mem_pool->custom[i]));
"if (invalidate_memory)" missing
Quoted 4 lines
mem-pool.c +1
-0free (mem_pool->custom[i]); + }free (mem_pool->custom);
mem-pool.c void mem_pool_discard(struct mem_pool *mem_pool) · line 91
Quoted 10 lines
mem-pool.c +6
-0free (mem_pool->custom_end);free (mem_pool); } +int should_validate_cache_entries (void ) +{ +static int validate_index_cache_entries =-1 ; + +if (validate_index_cache_entries <0 ) { +if (getenv ("GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES" ))
There's a safer version that you can use, get_env_bool()
Quoted 27 lines
mem-pool.c +8
-0+ validate_index_cache_entries =1 ; + else + validate_index_cache_entries =0 ; + } + +return validate_index_cache_entries; +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p;mem-pool.h +2
-0@@ -63,4 +63,6 @@ void mem_pool_combine(struct mem_pool *dst, struct mem_pool *src);*/ int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +int should_validate_cache_entries (void ); +#endif read-cache.c +0
-0
read-cache.c int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti · line 1273
Quoted 4 lines
read-cache.c +1
-0@@ -1270,6 +1270,8 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti {int pos; +validate_cache_entries (istate);
Validating _all_ entries every time an entry is added sounds way too expensive.
Quoted 80 lines
read-cache.c +46
-1+if (option &ADD_CACHE_JUST_APPEND ) pos = istate->cache_nr;else {@@ -1290,6 +1292,8 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti istate->cache_nr - pos - 1);set_index_entry (istate, pos, ce); istate->cache_changed |=CE_ENTRY_ADDED ; + +validate_cache_entries (istate);return 0 ; }@@ -2013,6 +2017,8 @@ int is_index_unborn(struct index_state *istate)int discard_index (struct index_state *istate) { +validate_cache_entries (istate); +resolve_undo_clear_index (istate); istate->cache_nr =0 ; istate->cache_changed =0 ;@@ -2035,6 +2041,43 @@ int discard_index(struct index_state *istate)return 0 ; } + +/* +* Validate the cache entries of this index. +* All cache entries associated with this index +* should have been allocated by the memory pool +* associated with this index, or by a referenced +* split index. +*/ +void validate_cache_entries (const struct index_state *istate) +{ +int i; +int validate_index_cache_entries =should_validate_cache_entries (); + +if (!validate_index_cache_entries) +return ; + +if (!istate || !istate->initialized) +return ; + +for (i =0 ; i < istate->cache_nr; i++) { +if (!istate) { +die ("internal error: cache entry is not allocated from expected memory pool" ); + }else if (!istate->ce_mem_pool || + !mem_pool_contains (istate->ce_mem_pool, istate->cache[i])) { +if (!istate->split_index || + !istate->split_index->base || + !istate->split_index->base->ce_mem_pool || + !mem_pool_contains (istate->split_index->base->ce_mem_pool, istate->cache[i])) { +die ("internal error: cache entry is not allocated from expected memory pool" ); + } + } + } + +if (istate->split_index) +validate_cache_entries (istate->split_index->base); +} +int unmerged_index (const struct index_state *istate) {int i;@@ -2828,6 +2871,10 @@ void move_index_extensions(struct index_state *dst, struct index_state *src)*/ void discard_index_cache_entry (struct cache_entry *ce) { +int invalidate_cache_entry =should_validate_cache_entries (); + +if (ce && invalidate_cache_entry) +memset (ce,0xCD ,cache_entry_size (ce->ce_namelen)); }void discard_transient_cache_entry (struct cache_entry *ce) --2.14.3
Signature · 1 line
Duy
Quoted 7 lines · Jameson Miller
This patch series improves the performance of loading indexes by reducing the number of
malloc()calls. Loading the index from disk is partly dominated by the time inmalloc(), which is called for each index entry. This patch series reduces the number of timesmalloc()is called as part of loading the index, and instead allocates a block of memory upfront that is large enough to hold all of the cache entries, and chunks this memory itself. This change builds on [1].
I have only looked at the mem-pool related patches to see if mem-pool.c is good enough to replace alloc.c. To me, it's a "yes" after we optimize mem_pool_alloc() a bit (not that performance really matters in alloc.c case, but that may be because it's already blazingly fast that we never noticed about it).
I probably should look at read-cache.c changes too. Maybe later. Although after the change to use xmalloc() per entry a few years(?) ago, it should be straight forward to use a different memory allocator.
Signature · 1 line
Duy
Quoted 9 lines · Duy Nguyen
Quoted 7 lines · Jameson Miller
This patch series improves the performance of loading indexes by reducing the number of
malloc()calls. Loading the index from disk is partly dominated by the time inmalloc(), which is called for each index entry. This patch series reduces the number of timesmalloc()is called as part of loading the index, and instead allocates a block of memory upfront that is large enough to hold all of the cache entries, and chunks this memory itself. This change builds on [1].
Quoted 5 lines
I have only looked at the mem-pool related patches to see if
mem-pool.cis good enough to replacealloc.c. To me, it's a "yes" after we optimizemem_pool_alloc()a bit (not that performance really matters inalloc.ccase, but that may be because it's already blazingly fast that we never noticed about it).
alloc.c was not just about speed, but mostly about dense packing? 855419f764a (Add specialized object allocator, 2006-06-19)
To me it is also a clear yes when it comes to combining these two memory pools.
Quoted 10 lines · Stefan Beller
Quoted 9 lines · Duy Nguyen
Quoted 7 lines · Jameson Miller
This patch series improves the performance of loading indexes by reducing the number of
malloc()calls. Loading the index from disk is partly dominated by the time inmalloc(), which is called for each index entry. This patch series reduces the number of timesmalloc()is called as part of loading the index, and instead allocates a block of memory upfront that is large enough to hold all of the cache entries, and chunks this memory itself. This change builds on [1].
Quoted 8 lines
Quoted 5 lines
I have only looked at the mem-pool related patches to see if
mem-pool.cis good enough to replacealloc.c. To me, it's a "yes" after we optimizemem_pool_alloc()a bit (not that performance really matters inalloc.ccase, but that may be because it's already blazingly fast that we never noticed about it).
alloc.cwas not just about speed, but mostly about dense packing?855419f764a(Add specialized object allocator, 2006-06-19)
I know. I vaguely remembered Linus made that change but did not really look it up :) That reference should be included when/if you switch from alloc.c to mem-pool.c.
Quoted 2 lines
To me it is also a clear yes when it comes to combining these two memory pools.
I also did not notice that jm/mem-pool already landed in master. Have you tried measure (both memory usage and allocation speed) of it and alloc.c? Just take some big repo as an example and do count-objects -v to see how many blobs/trees/commits it has, then allocate the same amount with both alloc.c and mem-pool.c and measure both speed/mem. I'm pretty sure you're right that mem-pool.c is a clear yes. I was just being more conservative because we do (slightly) change allocator's behavior when we make the switch. But it's also very likely that any performance difference will be insignificant.
I'm asking this because if mem-pool.c is a clear winner, you can start to update you series to use it now and kill alloc.c in the process.
PS. Is Jeff back yet? I'm sure Junio is listening and all but I'm afraid he's too busy being a maintainer so Jeff's opinion in this area is really valuable. He has all the fun and weird use cases to play with at github.
Signature · 1 line
Duy
Quoted 5 lines · Duy Nguyen
Quoted 3 lines
To me it is also a clear yes when it comes to combining these two memory pools.
I also did not notice that jm/mem-pool already landed in master.
Oh, thanks for telling! Now that I look at it, I am doubting it;
The reason for my doubt is the potential quadratic behavior for new allocations, in mem_pool_alloc() we walk all mp_blocks to see if we can fit the requested allocation in one of the later blocks. So if we call mem_pool_alloc a million times, we get a O(n) mp_blocks which we'd have to walk in each call.
However in alloc.c we do know that a slab is full as soon as we look take the next slab. That is the beauty of knowing 'len' at construction time of the allocator.
So I guess I'll just re-use the mp_block and introduce another struct fixed_sized_mem_pool, which will not look into other mp_blocks but the current.
Quoted 3 lines
Have
you tried measure (both memory usage and allocation speed) of it andalloc.c?
No, I was about to, but then started reading the code in an attempt to replace alloc.c by a mempool and saw the quadratic behavior.
Quoted 10 lines
Just take some big repo as an example and do count-objects -v to see how many blobs/trees/commits it has, then allocate the same amount with both
alloc.candmem-pool.cand measure both speed/mem. I'm pretty sure you're right thatmem-pool.cis a clear yes. I was just being more conservative because we do (slightly) change allocator's behavior when we make the switch. But it's also very likely that any performance difference will be insignificant.I'm asking this because if
mem-pool.cis a clear winner, you can start to update you series to use it now and killalloc.cin the process.
I'll implement the fixed_sized_mem_pool and take some measurements.
Quoted 2 lines
PS. Is Jeff back yet?
His last email on the public list is Apr 10th, stating that he'll be offline for "a few weeks", in <[email protected]> he said the vacation part is 3 weeks. So I think he is done with vacation and is just hiding to figure out a nice comeback. ;-)
Quoted 4 lines
I'm sure Junio is listening and all but I'm
afraid he's too busy being a maintainer so Jeff's opinion in this area is really valuable. He has all the fun and weird use cases to play with at github.
ok. I'll cc him for these patches.
Thanks,
Stefan
Quoted 18 lines
-----Original Message-----
From: [email protected] <[email protected]> On Behalf Of Stefan Beller
Sent: Thursday, May 3, 2018 4:59 PM
To: Duy Nguyen <[email protected]>
Cc: Jameson Miller <[email protected]>; [email protected]; [email protected]; [email protected]
Subject: Re: [PATCH v2 0/5] Allocate cache entries from memory poolQuoted 5 lines · Duy Nguyen
Quoted 3 lines
To me it is also a clear yes when it comes to combining these two memory pools.
I also did not notice that jm/mem-pool already landed in master.
Quoted 7 lines
Oh, thanks for telling! Now that I look at it, I am doubting it;
The reason for my doubt is the potential quadratic behavior for new allocations, in
mem_pool_alloc()we walk allmp_blocksto see if we can fit the requested allocation in one of the later blocks.
So if we callmem_pool_alloca million times, we get a O(n)mp_blockswhich we'd have to walk in each call.
With the current design, when a new mp_block is allocated, it is placed at the head of the linked list. This means that the most recently allocated mp_block is the 1st block that is searched. The *vast* majority of allocations should be fulfilled from this 1st block. It is only when the block is full that we search other mp_blocks in the list. If this is a concern, I think we have a couple low cost options to mitigate it (maybe a flag to control whether we search past the 1st mp_block for space, or logic to move blocks out of the search queue when they are full or fall below a threshold for available space).
If this is of interest, I could contribute a patch to enable one of these behaviors?
Quoted 46 lines
However in
alloc.cwe do know that a slab is full as soon as we look take the next slab. That is the beauty of knowing 'len' at construction time of the allocator.So I guess I'll just re-use the
mp_blockand introduce another structfixed_sized_mem_pool, which will not look into othermp_blocksbut the current.Quoted 3 lines
Have
you tried measure (both memory usage and allocation speed) of it andalloc.c?No, I was about to, but then started reading the code in an attempt to replace
alloc.cby a mempool and saw the quadratic behavior.Quoted 10 lines
Just take some big repo as an example and do count-objects -v to see how many blobs/trees/commits it has, then allocate the same amount with both
alloc.candmem-pool.cand measure both speed/mem. I'm pretty sure you're right thatmem-pool.cis a clear yes. I was just being more conservative because we do (slightly) change allocator's behavior when we make the switch. But it's also very likely that any performance difference will be insignificant.I'm asking this because if
mem-pool.cis a clear winner, you can start to update you series to use it now and killalloc.cin the process.I'll implement the
fixed_sized_mem_pooland take some measurements.Quoted 2 lines
PS. Is Jeff back yet?
His last email on the public list is Apr 10th, stating that he'll be offline for "a few weeks", in <[email protected]> he said the vacation part is 3 weeks. So I think he is done with vacation and is just hiding to figure out a nice comeback. ;-)
Quoted 3 lines
I'm sure Junio is listening and all but I'm afraid he's too busy being a maintainer so Jeff's opinion in this area is really valuable. He has all the fun and weird use cases to play with at github.
ok. I'll cc him for these patches.
Thanks,
Stefan
PATCH 1/1 alloc.c: replace alloc by mempool
---
Quoted 6 lines
Quoted 5 lines
The reason for my doubt is the potential quadratic behavior for new allocations, in
mem_pool_alloc()we walk allmp_blocksto see if we can fit the requested allocation in one of the later blocks.
So if we callmem_pool_alloca million times, we get a O(n)mp_blockswhich we'd have to walk in each call.
Quoted 6 lines
With the current design, when a new
mp_blockis allocated, it is placed at the head of the linked list. This means that the most recently allocatedmp_blockis the 1st block that is searched. The *vast* majority of allocations should be fulfilled from this 1st block. It is only when the block is full that we search othermp_blocksin the list.
I just measured on git.git and linux.git (both of which are not *huge* by any standard, but should give a good indication. linux has 6M objects, and when allocating 1024 at a time, we run into the new block allocation ~6000 times).
I could not measure any meaningful difference.
linux.git $ git count-objects -v
count: 0
size: 0
in-pack: 6036543
packs: 2
size-pack: 2492985
prune-packable: 0
garbage: 0
size-garbage: 0
(with this patch)
Performance counter stats for '/u/git/git count-objects -v' (30 runs):
2.123683 task-clock:u (msec) # 0.831 CPUs utilized ( +- 2.32% )
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
126 page-faults:u # 0.059 M/sec ( +- 0.22% )
895,900 cycles:u # 0.422 GHz ( +- 1.40% )
976,596 instructions:u # 1.09 insn per cycle ( +- 0.01% )
218,256 branches:u # 102.772 M/sec ( +- 0.01% )
8,331 branch-misses:u # 3.82% of all branches ( +- 0.61% )0.002556203 seconds time elapsed ( +- 2.20% )
Performance counter stats for 'git count-objects -v' (30 runs):
2.410352 task-clock:u (msec) # 0.801 CPUs utilized ( +- 2.79% )
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
131 page-faults:u # 0.054 M/sec ( +- 0.16% )
993,301 cycles:u # 0.412 GHz ( +- 1.99% )
1,087,428 instructions:u # 1.09 insn per cycle ( +- 0.02% )
244,292 branches:u # 101.351 M/sec ( +- 0.02% )
9,264 branch-misses:u # 3.79% of all branches ( +- 0.57% )0.003010854 seconds time elapsed ( +- 2.54% )
So I think we could just replace it for now and optimize again later, if it turns out to be a problem. I think the easiest optimisation is to increase the allocation size of having a lot more objects per mp_block.
Quoted 5 lines
If this is a concern, I think
we have a couple low cost options to mitigate it (maybe a flag to control whether we search past the 1stmp_blockfor space, or logic to move blocks out of the search queue when they are full or fall below a threshold for available space).
Instead of a flag I thought of its own struct with its own functions, which would not just have a different searching behavior, but also store the size in the struct such that you can just call fixed_size_mem_pool_alloc(void) to get another pointer.
A flag might be more elegant.
Quoted 3 lines
If this is of interest, I could contribute a patch to enable one of these behaviors?
I am tempted to just do away with alloc.c for now and use the mem-pool.
Thanks,
Stefan
alloc.c | 60 +++++++++++---------------------------------------------- 1 file changed, 11 insertions(+), 49 deletions(-)
alloc.c +11 -49
@@ -15,6 +15,7 @@#include "tree.h" #include "commit.h" #include "tag.h" +#include "mem-pool.h" #define BLOCKING 1024
@@ -26,61 +27,39 @@ union any_object {struct tag tag; }; -struct alloc_state { -int count;/* total number of nodes allocated */ -int nr;/* number of nodes left in current allocation */ -void *p;/* first free node in current allocation */ -}; - -static inline void *alloc_node (struct alloc_state *s,size_t node_size) -{ -void *ret; - -if (!s->nr) { - s->nr =BLOCKING ; - s->p =xmalloc (BLOCKING * node_size); - } - s->nr--; - s->count++; - ret = s->p; - s->p = (char *)s->p + node_size; -memset (ret,0 , node_size); -return ret; -} - -static struct alloc_state blob_state; +static struct mem_pool blob_state = {NULL ,sizeof (struct blob )*1024 -sizeof (struct mp_block ),0 };void *alloc_blob_node (void ) { -struct blob *b =alloc_node (&blob_state,sizeof (struct blob )); +struct blob *b =mem_pool_alloc (&blob_state,sizeof (struct blob )); b->object.type =OBJ_BLOB ;return b; } -static struct alloc_state tree_state; +static struct mem_pool tree_state = {NULL ,sizeof (struct tree )*1024 -sizeof (struct mp_block ),0 };void *alloc_tree_node (void ) { -struct tree *t =alloc_node (&tree_state,sizeof (struct tree )); +struct tree *t =mem_pool_alloc (&tree_state,sizeof (struct tree )); t->object.type =OBJ_TREE ;return t; } -static struct alloc_state tag_state; +static struct mem_pool tag_state = {NULL ,sizeof (struct tag )*1024 -sizeof (struct mp_block ),0 };void *alloc_tag_node (void ) { -struct tag *t =alloc_node (&tag_state,sizeof (struct tag )); +struct tag *t =mem_pool_alloc (&tag_state,sizeof (struct tag )); t->object.type =OBJ_TAG ;return t; } -static struct alloc_state object_state; +static struct mem_pool object_state = {NULL ,sizeof (union any_object )*1024 -sizeof (struct mp_block ),0 };void *alloc_object_node (void ) { -struct object *obj =alloc_node (&object_state,sizeof (union any_object )); +struct object *obj =mem_pool_alloc (&object_state,sizeof (union any_object )); obj->type =OBJ_NONE ;return obj; } -static struct alloc_state commit_state; +static struct mem_pool commit_state = {NULL ,sizeof (struct commit )*1024 -sizeof (struct mp_block ),0 };unsigned int alloc_commit_index (void ) {
@@ -90,26 +69,9 @@ unsigned int alloc_commit_index(void)void *alloc_commit_node (void ) { -struct commit *c =alloc_node (&commit_state,sizeof (struct commit )); +struct commit *c =mem_pool_alloc (&commit_state,sizeof (struct commit )); c->object.type =OBJ_COMMIT ; c->index =alloc_commit_index ();return c; } -static void report (const char *name,unsigned int count,size_t size) -{ -fprintf (stderr,"%10s: %8u (%" PRIuMAX" kB)\n" , - name, count, (uintmax_t ) size); -} - -#define REPORT (name, type) \ - report(#name, name##_state.count, name##_state.count * sizeof(type) >> 10) - -void alloc_report (void ) -{ - REPORT(blob, struct blob); - REPORT(tree, struct tree); - REPORT(commit, struct commit); - REPORT(tag, struct tag); - REPORT(object, union any_object); -}
Signature · 1 line
2.17.0.441.gb46fe60e1d-goog
Re: [PATCH] alloc.c: replace alloc by mempool
Quoted 42 lines · Stefan Beller
I just measured on git.git and linux.git (both of which are not *huge* by any standard, but should give a good indication. linux has 6M objects, and when allocating 1024 at a time, we run into the new block allocation ~6000 times).
I could not measure any meaningful difference.
linux.git $ git count-objects -v
count: 0
size: 0
in-pack: 6036543
packs: 2
size-pack: 2492985
prune-packable: 0
garbage: 0
size-garbage: 0(with this patch)
Performance counter stats for '/u/git/git count-objects -v' (30 runs):2.123683 task-clock:u (msec) # 0.831 CPUs utilized ( +- 2.32% ) 0 context-switches:u # 0.000 K/sec 0 cpu-migrations:u # 0.000 K/sec 126 page-faults:u # 0.059 M/sec ( +- 0.22% ) 895,900 cycles:u # 0.422 GHz ( +- 1.40% ) 976,596 instructions:u # 1.09 insn per cycle ( +- 0.01% ) 218,256 branches:u # 102.772 M/sec ( +- 0.01% ) 8,331 branch-misses:u # 3.82% of all branches ( +- 0.61% )0.002556203 seconds time elapsed ( +- 2.20% )
Performance counter stats for 'git count-objects -v' (30 runs):
2.410352 task-clock:u (msec) # 0.801 CPUs utilized ( +- 2.79% ) 0 context-switches:u # 0.000 K/sec 0 cpu-migrations:u # 0.000 K/sec 131 page-faults:u # 0.054 M/sec ( +- 0.16% ) 993,301 cycles:u # 0.412 GHz ( +- 1.99% ) 1,087,428 instructions:u # 1.09 insn per cycle ( +- 0.02% ) 244,292 branches:u # 101.351 M/sec ( +- 0.02% ) 9,264 branch-misses:u # 3.79% of all branches ( +- 0.57% )
Quoted 5 lines
0.003010854 seconds time elapsed ( +- 2.54% )
So I think we could just replace it for now and optimize again later, if it turns out to be a problem. I think the easiest optimisation is to increase the allocation size of having a lot more objects per
mp_block.
Yeah. I also tested this from a different angle: memory overhead. For 2M objects with one mp_block containing 1024 objects (same setting as alloc.c), the overhead (not counting malloc() internal overhead) is 46KB and we don't have any extra overhead due to padding between objects. This is true for all struct blob, commit, tree and tag. This is really good. alloc.c has zero overhead when measured this way but 46KB is practically zero to me.
Signature · 1 line
Duy
Re: [PATCH] alloc.c: replace alloc by mempool
Quoted 11 lines · Duy Nguyen
Quoted 3 lines
So I think we could just replace it for now and optimize again later, if it turns out to be a problem. I think the easiest optimisation is to increase the allocation size of having a lot more objects per
mp_block.Yeah. I also tested this from a different angle: memory overhead. For 2M objects with one
mp_blockcontaining 1024 objects (same setting asalloc.c), the overhead (not countingmalloc()internal overhead) is 46KB and we don't have any extra overhead due to padding between objects. This is true for all struct blob, commit, tree and tag. This is really good.alloc.chas zero overhead when measured this way but 46KB is practically zero to me.
Thanks.
The above in short sounds like arguing "replacing alloc.c internal with mempool incurs negligible memory overhead and performance degradation, but that can be optimized later". It was unclear to me why such a replacement needs to happen in the first place, though. Without such a code churn, there won't be extra overhead or need to fix it up later, no?
Re: [PATCH] alloc.c: replace alloc by mempool
Quoted 14 lines · Junio C Hamano
Quoted 11 lines · Duy Nguyen
Quoted 3 lines
So I think we could just replace it for now and optimize again later, if it turns out to be a problem. I think the easiest optimisation is to increase the allocation size of having a lot more objects per
mp_block.Yeah. I also tested this from a different angle: memory overhead. For 2M objects with one
mp_blockcontaining 1024 objects (same setting asalloc.c), the overhead (not countingmalloc()internal overhead) is 46KB and we don't have any extra overhead due to padding between objects. This is true for all struct blob, commit, tree and tag. This is really good.alloc.chas zero overhead when measured this way but 46KB is practically zero to me.
Quoted 6 lines
Thanks.
The above in short sounds like arguing "replacing
alloc.cinternal with mempool incurs negligible memory overhead and performance degradation, but that can be optimized later". It was unclear to me why such a replacement needs to happen in the first place, though.
The replacement with mem-pool might be easier than making sure that alloc.c has no globals and handles allocations per repository correctly. It would make the sb/object-store-alloc series shorter than it currently is, and maybe easier to review the code.
However now that sb/object-store-alloc is rerolled with keeping the logic of alloc.c and not replacing it with mem-pool, the only reason would be ease of maintainability by less code
On the other hand we have different implementations of lists, vectors and hashmaps, so having different memory allocators doesn't hurt.
Quoted 2 lines
Without such a code churn, there won't be extra overhead or need to fix it up later, no?
The original motivation is to get the object store on a per-repository basis. It might be cheaper to use an existing 'objectified' code instead of doing that again.
Re: [PATCH] alloc.c: replace alloc by mempool
Quoted 8 lines · Stefan Beller
The replacement with mem-pool might be easier than making sure that
alloc.chas no globals and handles allocations per repository correctly. It would make the sb/object-store-alloc series shorter than it currently is, and maybe easier to review the code.However now that sb/object-store-alloc is rerolled with keeping the logic of
alloc.cand not replacing it with mem-pool, the only reason would be ease of maintainability by less code
That is sensible considertation; the patch should be sold as such, instead of with an empty log message as if the reasoning is obvious to everybody without being told ;-)
v3 cover allocate cache entries from memory pool
In reply to Jameson MillerChanges from V2:
- Tweak logic of finding available memory block for memory allocation
- Only search head block
- Tweaked handling of large memory allocations.
- Large blocks now tracked in same manner as "regular" blocks
- Large blocks are placed at end of linked list of memory blocks
- Cache_entry type gains notion of whether it was allocated from memory pool or not
- Collapsed cache_entry discard logic into single function. This should make code easier to maintain
- Small tweaks based on V1 feedback
Base Ref: master
Web-Diff: [email protected]:jamill/git.git/commit/d608515f9e
Checkout: git fetch [email protected]:jamill/git.git users/jamill/block_allocation-v3 && git checkout d608515f9e
Jameson Miller (7):
read-cache: teach refresh_cache_entry() to take istate block alloc: add lifecycle APIs for cache_entry structs
mem-pool: only search head block for available space
mem-pool: add lifecycle management functions
mem-pool: fill out functionality
block alloc: allocate cache entries from mem_pool block alloc: add validations around cache_entry lifecyle
apply.c | 26 +++-- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 8 +- builtin/reset.c | 6 +- builtin/update-index.c | 26 +++-- cache.h | 53 +++++++++- fast-import.c | 2 +- git.c | 3 + mem-pool.c | 116 ++++++++++++++++++++-- mem-pool.h | 25 ++++- merge-recursive.c | 4 +- read-cache.c | 261 ++++++++++++++++++++++++++++++++++++++++--------- resolve-undo.c | 6 +- split-index.c | 58 ++++++++--- tree.c | 4 +- unpack-trees.c | 38 +++---- 17 files changed, 514 insertions(+), 135 deletions(-)
base-commit: ccdcbd54c4475c2238b310f7113ab3075b5abc9c
Signature · 1 line
2.14.3
PATCH v3 1/7 read-cache: teach refresh_cache_entry() to take istate
Refactor refresh_cache_entry() to work on a specific index, instead of implicitly using the_index. This is in preparation for making the make_cache_entry function work on a specific index.
--- cache.h | 2 +- merge-recursive.c | 2 +- read-cache.c | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-)
cache.h +1 -1
@@ -744,7 +744,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);#define REFRESH_IGNORE_SUBMODULES 0x0010/* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020/* user friendly output, not "needs update" */ extern int refresh_index (struct index_state *,unsigned int flags,const struct pathspec *pathspec,char *seen,const char *header_msg); -extern struct cache_entry *refresh_cache_entry (struct cache_entry *,unsigned int ); +extern struct cache_entry *refresh_cache_entry (struct index_state *,struct cache_entry *,unsigned int );/* * Opportunistically update the index but do not complain if we can't.
merge-recursive.c +1 -1
@@ -260,7 +260,7 @@ static int add_cacheinfo(struct merge_options *o,if (refresh) {struct cache_entry *nce; - nce =refresh_cache_entry (ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING ); + nce =refresh_cache_entry (&the_index, ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING );if (!nce)return err (o,_ ("addinfo_cache failed for path '%s'" ), path);if (nce != ce)
read-cache.c +4 -3
@@ -767,7 +767,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, ce->ce_namelen = len; ce->ce_mode =create_ce_mode (mode); - ret =refresh_cache_entry (ce, refresh_options); + ret =refresh_cache_entry (&the_index, ce, refresh_options);if (ret != ce)free (ce);return ret;
@@ -1448,10 +1448,11 @@ int refresh_index(struct index_state *istate, unsigned int flags,return has_errors; } -struct cache_entry *refresh_cache_entry (struct cache_entry *ce, +struct cache_entry *refresh_cache_entry (struct index_state *istate, +struct cache_entry *ce,unsigned int options) { -return refresh_cache_ent (&the_index, ce, options,NULL ,NULL ); +return refresh_cache_ent (istate, ce, options,NULL ,NULL ); }
Signature · 1 line
2.14.3
Re: [PATCH v3 1/7] read-cache: teach refresh_cache_entry() to take istate
Quoted 5 lines · Jameson Miller
Refactor
refresh_cache_entry()to work on a specific index, instead of implicitly usingthe_index. This is in preparation for making themake_cache_entryfunction work on a specific index.Signed-off-by Jameson Miller <[email protected]>
PATCH v3 2/7 block alloc: add lifecycle APIs for cache_entry structs
In reply to Jameson MillerAdd an API around managing the lifetime of cache_entry structs. Abstracting memory management details behind an API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated / freed. A later commit in this series will allocate cache entries from memory pools as appropriate.
Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated by malloc() calls. This change is in preparation for using memory pools to reduce the number of malloc() calls made when loading an index.
This API makes a distinction between cache entries that are intended for use with a particular index and cache entries that are not. This enables us to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
--- apply.c | 26 ++++++------- blame.c | 5 +-- builtin/checkout.c | 8 ++-- builtin/difftool.c | 8 ++-- builtin/reset.c | 6 +-- builtin/update-index.c | 26 ++++++------- cache.h | 24 +++++++++++- merge-recursive.c | 2 +- read-cache.c | 100 ++++++++++++++++++++++++++++++++++--------------- resolve-undo.c | 6 ++- split-index.c | 8 ++-- tree.c | 4 +- unpack-trees.c | 33 +++++++++++----- 13 files changed, 162 insertions(+), 94 deletions(-)
apply.c +12 -14
@@ -4090,12 +4090,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, oid.hash, name,0 ,0 ); + ce =make_index_cache_entry (&result, patch->old_mode, oid.hash, name,0 ,0 );if (!ce) -return error (_ ("make_cache_entry failed for path '%s'" ), +return error (_ ("make_index_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +discard_cache_entry (ce);return error (_ ("could not add %s to temporary index" ), name); }
@@ -4263,12 +4263,11 @@ static int add_index_file(struct apply_state *state,struct stat st;struct cache_entry *ce;int namelen =strlen (path); -unsigned ce_size =cache_entry_size (namelen);if (!state->update_index)return 0 ; - ce =xcalloc (1 , ce_size); + ce =make_empty_index_cache_entry (&the_index, namelen);memcpy (ce->name, path, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (0 );
@@ -4278,13 +4277,13 @@ static int add_index_file(struct apply_state *state,if (!skip_prefix (buf,"Subproject commit " , &s) ||get_oid_hex (s, &ce->oid)) { -free (ce); -return error (_ ("corrupt patch for submodule %s" ), path); +discard_cache_entry (ce); +return error (_ ("corrupt patch for submodule %s" ), path); } }else {if (!state->cached) {if (lstat (path, &st) <0 ) { -free (ce); +discard_cache_entry (ce);return error_errno (_ ("unable to stat newly " "created file '%s'" ), path);
@@ -4292,13 +4291,13 @@ static int add_index_file(struct apply_state *state,fill_stat_cache_info (ce, &st); }if (write_object_file (buf, size, blob_type, &ce->oid) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to create backing store " "for newly created file %s" ), path); } }if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), path); }
@@ -4422,27 +4421,26 @@ static int add_conflicted_stages_file(struct apply_state *state, struct patch *patch) {int stage, namelen; -unsigned ce_size, mode; +unsigned mode;struct cache_entry *ce;if (!state->update_index)return 0 ; namelen =strlen (patch->new_name); - ce_size =cache_entry_size (namelen); mode = patch->new_mode ? patch->new_mode : (S_IFREG |0644 );remove_file_from_cache (patch->new_name);for (stage =1 ; stage <4 ; stage++) {if (is_null_oid (&patch->threeway_stage[stage -1 ]))continue ; - ce =xcalloc (1 , ce_size); + ce =make_empty_index_cache_entry (&the_index, namelen);memcpy (ce->name, patch->new_name, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = namelen;oidcpy (&ce->oid, &patch->threeway_stage[stage -1 ]);if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), patch->new_name); }
blame.c +2 -3
@@ -154,7 +154,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,struct strbuf buf =STRBUF_INIT ;const char *ident;time_t now; -int size, len; +int len;struct cache_entry *ce;unsigned mode;struct strbuf msg =STRBUF_INIT ;
@@ -252,8 +252,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,/* Let's not bother reading from HEAD tree */ mode =S_IFREG |0644 ; } - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, &origin->blob_oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 );
builtin/checkout.c +4 -4
@@ -77,7 +77,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,return READ_TREE_RECURSIVE ; len = base->len +strlen (pathname); - ce =xcalloc (1 ,cache_entry_size (len)); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, base->buf, base->len);memcpy (ce->name + base->len, pathname, len - base->len);
@@ -96,7 +96,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,if (ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { old->ce_flags |=CE_UPDATE ; -free (ce); +discard_cache_entry (ce);return 0 ; } }
@@ -230,11 +230,11 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, oid.hash, path,2 ,0 ); + ce =make_transient_cache_entry (mode, oid.hash, path,2 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return status; }
builtin/difftool.c +4 -4
@@ -321,10 +321,10 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid->hash, path,0 ,0 ); + ce =make_transient_cache_entry (mode, oid->hash, path,0 ); ret =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return ret; }
@@ -488,8 +488,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, roid.hash, - dst_path,0 ,0 ); +make_index_cache_entry (&wtindex, rmode, roid.hash, + dst_path,0 ,0 );add_index_entry (&wtindex, ce2,ADD_CACHE_JUST_APPEND );
builtin/reset.c +3 -3
@@ -134,10 +134,10 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, one->oid.hash, one->path, -0 ,0 ); + ce =make_index_cache_entry (&the_index, one->mode, one->oid.hash, one->path, +0 ,0 );if (!ce) -die (_ ("make_cache_entry failed for path '%s'" ), +die (_ ("make_index_cache_entry failed for path '%s'" ), one->path);if (is_missing) { ce->ce_flags |=CE_INTENT_TO_ADD ;
builtin/update-index.c +11 -15
@@ -268,15 +268,14 @@ static int process_lstat_error(const char *path, int err)static int add_one_path (const struct cache_entry *old,const char *path,int len,struct stat *st) { -int option, size; +int option;struct cache_entry *ce;/* Was the old index entry already up-to-date? */ if (old && !ce_stage (old) && !ce_match_stat (old, st,0 ))return 0 ; - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 ); ce->ce_namelen = len;
@@ -285,13 +284,13 @@ static int add_one_path(const struct cache_entry *old, const char *path, int lenif (index_path (&ce->oid, path, st, info_only ?0 :HASH_WRITE_OBJECT )) { -free (ce); +discard_cache_entry (ce);return -1 ; } option = allow_add ?ADD_CACHE_OK_TO_ADD :0 ; option |= allow_replace ?ADD_CACHE_OK_TO_REPLACE :0 ;if (add_cache_entry (ce, option)) { -free (ce); +discard_cache_entry (ce);return error ("%s: cannot add to the index - missing --add option?" , path); }return 0 ;
@@ -403,15 +402,14 @@ static int process_path(const char *path)static int add_cacheinfo (unsigned int mode,const struct object_id *oid,const char *path,int stage) { -int size, len, option; +int len, option;struct cache_entry *ce;if (!verify_path (path))return error ("Invalid path '%s'" , path); len =strlen (path); - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, path, len);
@@ -589,7 +587,6 @@ static struct cache_entry *read_one_ent(const char *which, {unsigned mode;struct object_id oid; -int size;struct cache_entry *ce;if (get_tree_entry (ent, path, &oid, &mode)) {
@@ -602,8 +599,7 @@ static struct cache_entry *read_one_ent(const char *which,error ("%s: not a blob in %s branch." , path, which);return NULL ; } - size =cache_entry_size (namelen); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (&the_index, namelen);oidcpy (&ce->oid, &oid);memcpy (ce->name, path, namelen);
@@ -680,8 +676,8 @@ static int unresolve_one(const char *path)error ("%s: cannot add their version to the index." , path); ret =-1 ; free_return: -free (ce_2); -free (ce_3); +discard_cache_entry (ce_2); +discard_cache_entry (ce_3);return ret; }
@@ -748,7 +744,7 @@ static int do_reupdate(int ac, const char **av, ce->name, ce_namelen(ce), 0);if (old && ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { -free (old); +discard_cache_entry (old);continue ;/* unchanged */ }/* Be careful. The working tree may not have the
@@ -759,7 +755,7 @@ static int do_reupdate(int ac, const char **av, path =xstrdup (ce->name);update_one (path);free (path); -free (old); +discard_cache_entry (old);if (save_nr != active_nr) goto redo; }
cache.h +23 -1
@@ -339,6 +339,29 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and freeing */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +extern struct cache_entry *make_index_cache_entry (struct index_state *istate,unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options); +extern struct cache_entry *make_empty_index_cache_entry (struct index_state *istate,size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage); +extern struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
@@ -691,7 +714,6 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options);extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -252,7 +252,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid->hash : null_sha1, path, stage,0 ); + ce =make_index_cache_entry (&the_index, mode, oid ? oid->hash : null_sha1, path, stage,0 );if (!ce)return err (o,_ ("addinfo_cache failed for path '%s'" ), path);
read-cache.c +70 -30
236 lines not on this page · raw message
resolve-undo.c +4 -2
@@ -146,8 +146,10 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], ru->oid[i].hash, - name, i +1 ,0 ); + nce =make_index_cache_entry (istate, + ru->mode[i], + ru->oid[i].hash, + name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;if (add_index_entry (istate, nce,ADD_CACHE_OK_TO_ADD )) {
split-index.c +4 -4
@@ -123,7 +123,7 @@ static void replace_entry(size_t pos, void *data) src->ce_flags |=CE_UPDATE_IN_BASE ; src->ce_namelen = dst->ce_namelen;copy_cache_entry (dst, src); -free (src); +discard_cache_entry (src); si->nr_replacements++; }
@@ -224,7 +224,7 @@ void prepare_to_write_split_index(struct index_state *istate) base->ce_flags = base_flags;if (ret) ce->ce_flags |=CE_UPDATE_IN_BASE ; -free (base); +discard_cache_entry (base); si->base->cache[ce->index -1 ] = ce; }for (i =0 ; i < si->base->cache_nr; i++) {
@@ -301,7 +301,7 @@ void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce ce == istate->split_index->base->cache[ce->index - 1]) ce->ce_flags |= CE_REMOVE; else -free (ce ); +discard_cache_entry (ce ); } void replace_index_entry_in_base(struct index_state *istate,
@@ -314,7 +314,7 @@ void replace_index_entry_in_base(struct index_state *istate, old_entry->index <= istate->split_index->base->cache_nr) { new_entry->index = old_entry->index;if (old_entry != istate->split_index->base->cache[new_entry->index -1 ]) -free (istate->split_index->base->cache[new_entry->index -1 ]); +discard_cache_entry (istate->split_index->base->cache[new_entry->index -1 ]); istate->split_index->base->cache[new_entry->index -1 ] = new_entry; } }
tree.c +1 -3
@@ -16,15 +16,13 @@ static int read_one_entry_opt(struct index_state *istate, unsigned mode, int stage, int opt) { int len; -unsigned int size;struct cache_entry *ce;if (S_ISDIR (mode))return READ_TREE_RECURSIVE ; len =strlen (pathname); - size =cache_entry_size (baselen + len); - ce =xcalloc (1 , size); + ce =make_empty_index_cache_entry (istate, baselen + len); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage);
unpack-trees.c +23 -10
@@ -192,10 +192,10 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce) +static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) {unsigned int size =ce_size (ce); -struct cache_entry *new_entry =xmalloc (size); +struct cache_entry *new_entry =make_empty_index_cache_entry (istate,ce_namelen (ce));memcpy (new_entry, ce, size);return new_entry;
@@ -205,7 +205,7 @@ static void add_entry(struct unpack_trees_options *o,const struct cache_entry *ce, unsigned int set, unsigned int clear) { -do_add_entry (o ,dup_entry (ce ),set ,clear ); + do_add_entry(o, dup_entry(ce, &o->result), set, clear); }/*
@@ -786,10 +786,17 @@ static int ce_in_traverse_path(const struct cache_entry *ce,return (info->pathlen <ce_namelen (ce)); } -static struct cache_entry *create_ce_entry (const struct traverse_info *info,const struct name_entry *n,int stage) +static struct cache_entry *create_ce_entry (const struct traverse_info *info, +const struct name_entry *n, +int stage, +struct index_state *istate, +int is_transient) {int len =traverse_path_len (info, n); -struct cache_entry *ce =xcalloc (1 ,cache_entry_size (len)); +struct cache_entry *ce = + is_transient ? +make_empty_transient_cache_entry (len) : +make_empty_index_cache_entry (istate, len); ce->ce_mode =create_ce_mode (n->mode); ce->ce_flags =create_ce_flags (stage);
@@ -835,7 +842,13 @@ static int unpack_nondirectories(int n, unsigned long mask, stage =3 ; else stage =2 ; - src[i + o->merge] =create_ce_entry (info, names + i, stage); + +/* +* If this is a merge operation, then the cache +* entries are only temporary and discarded several +* lines below. +*/ + src[i + o->merge] =create_ce_entry (info, names + i, stage, &o->result, o->merge); }if (o->merge) {
@@ -844,7 +857,7 @@ static int unpack_nondirectories(int n, unsigned long mask,for (i =0 ; i < n; i++) {struct cache_entry *ce = src[i + o->merge];if (ce != o->df_conflict_entry) -free (ce); +discard_cache_entry (ce); }return rc; }
@@ -1765,7 +1778,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce); +struct cache_entry *merge =dup_entry (ce, &o->result);if (!old) {/*
@@ -1785,7 +1798,7 @@ static int merged_entry(const struct cache_entry *ce,if (verify_absent (merge,ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN , o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }invalidate_ce_path (merge, o);
@@ -1811,7 +1824,7 @@ static int merged_entry(const struct cache_entry *ce, update =0 ; }else {if (verify_uptodate (old, o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }/* Migrate old flags over */
Signature · 1 line
2.14.3
Re: [PATCH v3 2/7] block alloc: add lifecycle APIs for cache_entry structs
Quoted 13 lines · Jameson Miller
Add an API around managing the lifetime of
cache_entrystructs. Abstracting memory management details behind an API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated / freed. A later commit in this series will allocate cache entries from memory pools as appropriate.Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated bymalloc()calls. This change is in preparation for using memory pools to reduce the number ofmalloc()calls made when loading an index.
Quoted 5 lines
This API makes a distinction between cache entries that are intended for use with a particular index and cache entries that are not. This enables us to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
Yuck. make_index_cache_entry()?
Generally we use "cache" when working on the_index without passing istate, and otherwise "index", which means that readers can assume that distim_cache_entry(...)" is a shorter and more limited way to say "distim_index_entry(&the_index, ...)". Having both index and cache in the same name smells crazy.
If most of the alocations are for permanent kind, give it a shorter name call it make_cache_entry(&the_index, ...), and call the other non-permanent one with a longer and more cumbersome name, perhaps make_transient_cache_entry(...). Avoid saying "index" in the former name, as the design decision this series is making to allocate memory for a cache-entry from a pool associated to an index_state is already seen by what its first parameter is.
Quoted 20 lines
cache.h +12
-0@@ -339,6 +339,29 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and freeing */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +extern struct cache_entry *make_index_cache_entry (struct index_state *istate,unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options); +extern struct cache_entry *make_empty_index_cache_entry (struct index_state *istate,size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index.
cache.h extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce) · line 363
Quoted 10 lines
cache.h +10
-0+* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage); +extern struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce);
I am not yet convinced that it is a good idea to require each istate to hold a separate pool. Anything that uses unpack_trees() can do "starting from this src_index, perform various mergy operations and deposit the result in dst_index". Sometimes the two logical indices point at the same istate, sometimes different. When src and dst are different istates, the code that used to simply add another pointer to the same ce to the dst index now needs to duplicate it out of the pool associated with dst?
In any case, perhaps it will become clearer why it is a good idea as we read on, so let's do so.
RE: [PATCH v3 2/7] block alloc: add lifecycle APIs for cache_entry structs
Quoted 40 lines
-----Original Message-----
From: Junio C Hamano <[email protected]> On Behalf Of Junio C Hamano
Sent: Thursday, May 24, 2018 12:52 AM
To: Jameson Miller <[email protected]>
Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Subject: Re: [PATCH v3 2/7] block alloc: add lifecycle APIs forcache_entrystructsQuoted 13 lines · Jameson Miller
Add an API around managing the lifetime of
cache_entrystructs. Abstracting memory management details behind an API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated / freed. A later commit in this series will allocate cache entries from memory pools as appropriate.Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated bymalloc()calls. This change is in preparation for using memory pools to reduce the number ofmalloc()calls made when loading an index.Quoted 5 lines
This API makes a distinction between cache entries that are intended for use with a particular index and cache entries that are not. This enables us to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
Yuck.
make_index_cache_entry()?Generally we use "cache" when working on
the_indexwithout passing istate, and otherwise "index", which means that readers can assume thatdistim_cache_entry(...)" is a shorter and more limited way to say "distim_index_entry(&the_index, ...)". Having both index and cache in the same name smells crazy.
Quoted 7 lines
If most of the alocations are for permanent kind, give it a shorter name call it
make_cache_entry(&the_index, ...), and call the other non-permanent one with a longer and more cumbersome name, perhapsmake_transient_cache_entry(...). Avoid saying "index" in the former name, as the design decision this series is making to allocate memory for a cache-entry from a pool associated to anindex_stateis already seen by what its first parameter is.
I like this suggestion - I will make this change in the next version of this series.
Quoted 38 lines
Quoted 27 lines
cache.h +17
-0@@ -339,6 +339,29 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce) extern void free_name_hash(structindex_state *istate); +/* Cache entry creation and freeing */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +extern struct cache_entry *make_index_cache_entry (struct index_state +*istate,unsigned int mode,const unsigned char *sha1,const char +*path,int stage,unsigned int refresh_options);extern struct +cache_entry *make_empty_index_cache_entry (struct index_state *istate, +size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. cache.h extern void remove_name_hash(struct index_state · line 363
Quoted 10 lines
cache.h +10
-0+*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int +mode,const unsigned char *sha1,const char *path,int stage);extern +struct cache_entry *make_empty_transient_cache_entry (size_t +name_len); + +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce);
Quoted 7 lines
I am not yet convinced that it is a good idea to require each istate to hold a separate pool. Anything that uses
unpack_trees()can do "starting from thissrc_index, perform various mergy operations and deposit the result indst_index". Sometimes the two logical indices point at the same istate, sometimes different. When src and dst are different istates, the code that used to simply add another pointer to the same ce to the dst index now needs to duplicate it out of the pool associated with dst?
I did not see any instances in unpack_trees() where it copied just the cache_entry pointer from src to dst, but I will check again.
You are correct, all the cache_entries need to be duplicated before being added to the destination index, which is what I think the code already does. We tried to make this more explicity by converting the inline xcalloc/memcpy instances to an actual function.
In the existing code (before this patch series), the index implicitly "owns" its cache_entry instances. This can be seen in the discard_index function, where the index will discard any cache entries that it has a reference to (that are not contained in the split index). If there is code that just copies the pointer to an unrelated index, then this cache entry would be freed when the source index is freed (unless the cache entry is removed from the src index).
With memory pools, the same pattern is followed, but this relationship is a bit more explicit.
Quoted 3 lines
In any case, perhaps it will become clearer why it is a good idea as we read on, so let's do so.
Thank you for your review so far. I look forward to your thoughts on the overall change and if it is a change you are interested in.
PATCH v3 3/7 mem-pool: only search head block for available space
In reply to Jameson MillerInstead of searching all memory blocks for available space to fulfill a memory request, only search the head block. If the head block does not have space, assume that previous block would most likely not be able to fulfill request either. This could potentially lead to more memory fragmentation, but also avoids searching memory blocks that probably will not be able to fulfill request.
This pattern will benefit consumers that are able to generate a good estimate for how much memory will be needed, or if they are performing fixed sized allocations, so that once a block is exhausted it will never be able to fulfill a future request.
--- mem-pool.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
mem-pool.c +4 -4
@@ -21,16 +21,16 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t bvoid *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) { -struct mp_block *p; +struct mp_block *p =NULL ;void *r;/* round up to a 'uintmax_t' alignment */ if (len & (sizeof (uintmax_t) -1 )) len +=sizeof (uintmax_t) - (len & (sizeof (uintmax_t) -1 )); -for (p = mem_pool->mp_block; p; p = p->next_block) -if (p->end - p->next_free >= len) -break ; +if (mem_pool->mp_block && + mem_pool->mp_block->end - mem_pool->mp_block->next_free >= len) + p = mem_pool->mp_block;if (!p) {if (len >= (mem_pool->block_alloc /2 )) {
Signature · 1 line
2.14.3
PATCH v3 4/7 mem-pool: add lifecycle management functions
In reply to Jameson MillerAdd initialization and discard functions to mem-pool type. As part of this, we now also track "large" allocations of memory so that these can also be cleaned up when discarding the memory pool.
These changes are in preparation for a future commit that will utilize creating and discarding memory pool.
--- fast-import.c | 2 +- mem-pool.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- mem-pool.h | 12 +++++++++++- 3 files changed, 71 insertions(+), 6 deletions(-)
fast-import.c +1 -1
@@ -300,7 +300,7 @@ static int global_argc;static const char **global_argv;/* Memory pools */ -static struct mem_pool fi_mem_pool = {NULL ,2 *1024 *1024 - +static struct mem_pool fi_mem_pool = {NULL ,NULL ,2 *1024 *1024 -sizeof (struct mp_block ),0 };/* Atom management */
mem-pool.c +59 -4
89 lines not on this page · raw message
mem-pool.h +11 -1
@@ -9,7 +9,7 @@ struct mp_block { };struct mem_pool { -struct mp_block *mp_block; +struct mp_block *mp_block, *mp_block_tail;/* * The amount of available memory to grow the pool by.
@@ -21,6 +21,16 @@ struct mem_pool {size_t pool_alloc; }; +/* +* Initialize mem_pool with specified initial size. +*/ +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size); + +/* +* Discard a memory pool and free all the memory it is responsible for. +*/ +void mem_pool_discard (struct mem_pool *mem_pool); +/* * Alloc memory from the mem_pool. */
Signature · 1 line
2.14.3
PATCH v3 5/7 mem-pool: fill out functionality
In reply to Jameson MillerAdd functions for:
- combining two memory pools
- determining if a memory address is within the range managed by a memory pool
These functions will be used by future commits.
--- mem-pool.c | 40 ++++++++++++++++++++++++++++++++++++++++ mem-pool.h | 13 +++++++++++++ 2 files changed, 53 insertions(+)
mem-pool.c +40 -0
@@ -108,3 +108,43 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +struct mp_block *p; + +/* Check if memory is allocated in a block */ +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +/* Append the blocks from src to dst */ +if (dst->mp_block && src->mp_block) { +/* +* src and dst have blocks, append +* blocks from src to dst. +*/ + dst->mp_block_tail->next_block = src->mp_block; + dst->mp_block_tail = src->mp_block_tail; + }else if (src->mp_block) { +/* +* src has blocks, dst is empty +* use pointers from src to set up dst. +*/ + dst->mp_block = src->mp_block; + dst->mp_block_tail = src->mp_block_tail; + }else { +// src is empty, nothing to do. + } + + dst->pool_alloc += src->pool_alloc; + src->pool_alloc =0 ; + src->mp_block =NULL ; + src->mp_block_tail =NULL ; +}
mem-pool.h +13 -0
@@ -41,4 +41,17 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len);*/ void *mem_pool_calloc (struct mem_pool *pool,size_t count,size_t size); +/* +* Move the memory associated with the 'src' pool to the 'dst' pool. The 'src' +* pool will be empty and not contain any memory. It still needs to be free'd +* with a call to `mem_pool_discard`. +*/ +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src); + +/* +* Check if a memory pointed at by 'mem' is part of the range of +* memory managed by the specified mem_pool. +*/ +int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +#endif
Signature · 1 line
2.14.3
Re: [PATCH v3 5/7] mem-pool: fill out functionality
Quoted 27 lines
mem-pool.c +23
-0@@ -108,3 +108,43 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +struct mp_block *p; + +/* Check if memory is allocated in a block */ +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +/* Append the blocks from src to dst */ +if (dst->mp_block && src->mp_block) { +/* +* src and dst have blocks, append +* blocks from src to dst. +*/ + dst->mp_block_tail->next_block = src->mp_block;
mem-pool.c void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size) · line 143
Quoted 10 lines
mem-pool.c +10
-0+ dst->mp_block_tail = src->mp_block_tail; + }else if (src->mp_block) { +/* +* src has blocks, dst is empty +* use pointers from src to set up dst. +*/ + dst->mp_block = src->mp_block; + dst->mp_block_tail = src->mp_block_tail; + }else { +// src is empty, nothing to do.
comment style.
PATCH v3 6/7 block alloc: allocate cache entries from mem_pool
In reply to Jameson MillerWhen reading large indexes from disk, a portion of the time is dominated in malloc() calls. This can be mitigated by allocating a large block of memory and manage it ourselves via memory pools.
This change moves the cache entry allocation to be on top of memory pools.
Design:
The index_state struct will gain a notion of an associated memory_pool from which cache_entries will be allocated from. When reading in the index from disk, we have information on the number of entries and their size, which can guide us in deciding how large our initial memory allocation should be. When an index is discarded, the associated memory_pool will be discarded as well - so the lifetime of a cache_entry is tied to the lifetime of the index_state that it was allocated for.
In the case of a Split Index, the following rules are followed. 1st, some terminology is defined:
Terminology:
- 'the_index': represents the logical view of the index
- 'split_index': represents the "base" cache entries. Read from the split index file.
'the_index' can reference a single split_index, as well as cache_entries from the split_index. the_index will be discarded before the split_index is. This means that when we are allocating cache_entries in the presence of a split index, we need to allocate the entries from the split_index's memory pool. This allows us to follow the pattern that the_index can reference cache_entries from the split_index, and that the cache_entries will not be freed while they are still being referenced.
--- cache.h | 21 ++++++++++ read-cache.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++--------- split-index.c | 50 ++++++++++++++++++++---- unpack-trees.c | 13 +------ 4 files changed, 165 insertions(+), 38 deletions(-)
cache.h +21 -0
@@ -15,6 +15,7 @@#include "path.h" #include "sha1-array.h" #include "repository.h" +#include "mem-pool.h" #include <zlib.h> typedef struct git_zstream {
@@ -156,6 +157,7 @@ struct cache_entry {struct stat_data ce_stat_data;unsigned int ce_mode;unsigned int ce_flags; +unsigned int mem_pool_allocated;unsigned int ce_namelen;unsigned int index;/* for link extension */ struct object_id oid;
@@ -227,6 +229,7 @@ static inline void copy_cache_entry(struct cache_entry *dst, const struct cache_entry *src) {unsigned int state = dst->ce_flags &CE_HASHED ; +int mem_pool_allocated = dst->mem_pool_allocated;/* Don't copy hash chain and name */ memcpy(&dst->ce_stat_data, &src->ce_stat_data,
@@ -235,6 +238,9 @@ static inline void copy_cache_entry(struct cache_entry *dst,/* Restore the hash state */ dst->ce_flags = (dst->ce_flags & ~CE_HASHED ) | state; + +/* Restore the mem_pool_allocated flag */ + dst->mem_pool_allocated = mem_pool_allocated; }static inline unsigned create_ce_flags (unsigned stage)
@@ -328,6 +334,7 @@ struct index_state {struct untracked_cache *untracked;uint64_t fsmonitor_last_update;struct ewah_bitmap *fsmonitor_dirty; +struct mem_pool *ce_mem_pool; };extern struct index_state the_index;
@@ -362,6 +369,20 @@ extern struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Duplicate a cache_entry. Allocate memory for the new entry from a +* memory_pool. Takes into account cache_entry fields that are meant +* for managing the underlying memory allocation of the cache_entry. +*/ +struct cache_entry *dup_cache_entry (const struct cache_entry *ce,struct index_state *istate); + +/* +* Validate the cache entries in the index. This is an internal +* consistency check that the cache_entry structs are allocated from +* the expected memory pool. +*/ +void validate_cache_entries (const struct index_state *istate); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
read-cache.c +100 -19
202 lines not on this page · raw message
split-index.c +42 -8
70 lines not on this page · raw message
unpack-trees.c +2 -11
@@ -192,20 +192,11 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) -{ -unsigned int size =ce_size (ce); -struct cache_entry *new_entry =make_empty_index_cache_entry (istate,ce_namelen (ce)); - -memcpy (new_entry, ce, size); -return new_entry; -} -static void add_entry (struct unpack_trees_options *o,const struct cache_entry *ce,unsigned int set,unsigned int clear) { -do_add_entry (o,dup_entry (ce, &o->result), set, clear); +do_add_entry (o,dup_cache_entry (ce, &o->result), set, clear); }/*
@@ -1778,7 +1769,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce, &o->result); +struct cache_entry *merge =dup_cache_entry (ce, &o->result);if (!old) {/*
Signature · 1 line
2.14.3
PATCH v3 7/7 block alloc: add validations around cache_entry lifecyle
In reply to Jameson MillerAdd an option (controlled by an environment variable) perform extra validations on mem_pool allocated cache entries. When set:
1) Invalidate cache_entry memory when discarding cache_entry.
2) When discarding index_state struct, verify that all cache_entries were allocated from expected mem_pool.
3) When discarding mem_pools, invalidate mem_pool memory.
This should provide extra checks that mem_pools and their allocated cache_entries are being used as expected.
--- cache.h | 6 ++++++ git.c | 3 +++ mem-pool.c | 7 ++++++- mem-pool.h | 2 +- read-cache.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 69 insertions(+), 4 deletions(-)
cache.h +6 -0
@@ -369,6 +369,12 @@ extern struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Check configuration if we should perform extra validation on cache +* entries. +*/ +int should_validate_cache_entries (void ); +/* * Duplicate a cache_entry. Allocate memory for the new entry from a * memory_pool. Takes into account cache_entry fields that are meant
git.c +3 -0
@@ -347,7 +347,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)trace_argv_printf (argv,"trace: built-in: git" ); +validate_cache_entries (&the_index); status = p->fn (argc, argv, prefix); +validate_cache_entries (&the_index); +if (status)return status;
mem-pool.c +6 -1
@@ -63,13 +63,18 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size) *mem_pool = pool; } -void mem_pool_discard (struct mem_pool *mem_pool) +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory) {struct mp_block *block, *block_to_free; +for (block = mem_pool->mp_block; block;) { block_to_free = block; block = block->next_block; + +if (invalidate_memory) +memset (block_to_free->space,0xDD , ((char *)block_to_free->end) - ((char *)block_to_free->space)); +free (block_to_free); }
mem-pool.h +1 -1
@@ -29,7 +29,7 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size);/* * Discard a memory pool and free all the memory it is responsible for. */ -void mem_pool_discard (struct mem_pool *mem_pool); +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory);/* * Alloc memory from the mem_pool.
read-cache.c +53 -2
82 lines not on this page · raw message
Signature · 1 line
2.14.3
Re: [PATCH v3 0/7] allocate cache entries from memory pool
In reply to Jameson MillerQuoted 6 lines · Jameson Miller
Changes from V2:
- Tweak logic of finding available memory block for memory allocation
- Only search head block
Hmph. Is that because we generally do not free() a lot so once a block is filled, there is not much chance that we have reclaimed space in the block later?
Quoted 7 lines
- Tweaked handling of large memory allocations.
- Large blocks now tracked in same manner as "regular" blocks
- Large blocks are placed at end of linked list of memory blocks
If we are only carving out of the most recently allocated block, it seems that there is no point looking for "the end", no?
Quoted 5 lines
-
Cache_entrytype gains notion of whether it was allocated from memory pool or not- Collapsed
cache_entrydiscard logic into single function. This should make code easier to maintain
That certainly should be safer to have a back-pointer pointing to which pool each entry came from, but doesn't it result in memory bloat?
RE: [PATCH v3 0/7] allocate cache entries from memory pool
Quoted 14 lines
-----Original Message-----
From: Junio C Hamano <[email protected]> On Behalf Of Junio C Hamano
Sent: Thursday, May 24, 2018 12:55 AM
To: Jameson Miller <[email protected]>
Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Subject: Re: [PATCH v3 0/7] allocate cache entries from memory poolQuoted 2 lines · Jameson Miller
Changes from V2:
Quoted 8 lines
Quoted 4 lines
- Tweak logic of finding available memory block for memory allocation
- Only search head block
Hmph. Is that because we generally do not
free()a lot so once a block is filled, there is not much chance that we have reclaimed space in the block later?
The design of the memory pool is that once the memory is claimed from the pool, it is not reused until the containing pool is discarded. Individual entries are not freed, only the entire memory pool is freed, and only after we are sure that there are no references to any of the entries in the pool.
The memory pool design makes some tradeoffs. It is not meant to be completely replace malloc / free as a general purpose allocator, but rather used in scenarios where the benefit (faster allocations, lower bookkeeping overhead) is worth the tradeoffs (not able to free individual allocations). The access patterns around cache entries are well matched with the memory pool to get the benefits - the majority of cache entries are allocated up front when reading the index from disk, and are then discarded in bulk when the index is freed (if the index is freed at all (rather than just existing)).
Quoted 10 lines
Quoted 7 lines
- Tweaked handling of large memory allocations.
- Large blocks now tracked in same manner as "regular" blocks
- Large blocks are placed at end of linked list of memory blocks
If we are only carving out of the most recently allocated block, it seems that there is no point looking for "the end", no?
Right. If we are not searching the list, then there isn't any point in Appending odd large items to the end vs sticking it immediately past the head block. I will remove the usage of the tail pointer in the next version.
Yes, this is true. I can remove the usage of the tail pointer here, as it is not really leveraged. I will make this change in the next version.
Quoted 10 lines
Quoted 7 lines
-
Cache_entrytype gains notion of whether it was allocated from memory pool or not- Collapsed
cache_entrydiscard logic into single function. This should make code easier to maintainThat certainly should be safer to have a back-pointer pointing to which pool each entry came from, but doesn't it result in memory bloat?
Currently, entries claimed from a memory pool are not freed, so we only need to know whether the entry came from a memory pool or not. This has less memory impact than a full pointer but is also a bit more restrictive.
We debated several approaches for what to do here and landed on using a simple bit for this rather than the full pointer. In the current code we use a full integer field for this, but we can convert this into a bit or bit field. The current flags word is full, so this would require a second flags field.
Re: [PATCH v3 0/7] allocate cache entries from memory pool
Quoted 6 lines
The memory pool design makes some tradeoffs. It is not meant to be completely replace malloc / free as a general purpose allocator, but rather used in scenarios where the benefit (faster allocations, lower bookkeeping overhead) is worth the tradeoffs (not able to free individual allocations).
So this is the actual stated design goal of this memory pool? Fast&cheap allocation with little overhead for giving up individual frees?
Quoted 1 line
We debated several approaches for what to do here
it would be awesome if the list could participate in the discussion even if only read-only.
RE: [PATCH v3 0/7] allocate cache entries from memory pool
Quoted 5 lines
Quoted 2 lines
We debated several approaches for what to do here
it would be awesome if the list could participate in the discussion even if only read-only.
A bit of delay in my response here, but I like the suggestion. here is a summary of some approaches I considered:
1) Do not include any information about where the cache_entry was allocated.
Pros: No extra memory overhead
Cons: Caller is responsible for freeing the cache entry correctly
This was our initial approach. We were hoping that all cache entries could be allocated from a memory pool, and we would not have to worry about non-pool allocated entries. There are still a couple of places that need "temporary" cache entries at the moment, so we couldn't move completely to only memory pool allocated cache_entries. This would have resulted in the code allocating many "temporary" cache_entries from a pool, and the memory would not be eligible to be reclaimed until the entire memory pool was freed - and this was a tradeoff we didn't want to make.
2) Include an extra field encoding whether the cache_entry was allocated from a memory pool
Pro: the discard function can now make a decision regarding how
to free the cache_entry
Con: each cache entry grows by an extra int field.
The bits in the existing ce_flags field are all claimed, so we need an extra field to track this bit of information. We could claim just a bit in the field now, which would result in the cache_entry still growing by the same amount, but any future bits would not require extra space. This pushes off the work for an actual bit field off into future work.
3) Encode whether the cache_entry was allocated from a memory pool as a bit in a field.
Pro: only a single bit is required to encode this information.
Con: All the existings bits in the existing ce_flags field are
claimed. We need to add an extra bit field and take the same memory growth.
I considered this approach (and am still open to it), but I went for a simpler initial approach to make sure the overall change is acceptable. There is no difference in the memory footprint with this change, so it is only to enable future changes more easily.
4) Include pointer to the memory pool from which this cache_entry was created from
Pro: Could (potentially) do some extra bookkeeping, such as
automatically cleaning up the memory_pool when all allocated cache_entries are freed.
Con: extra complexity, larger growth to cache_entry struct to
accommodate mem_pool pointer
In the end, we didn't see a tangible benefit to this option at this point.
Given the tradeoffs, I went with option #2 for now.
Re: [PATCH v3 0/7] allocate cache entries from memory pool
In reply to Jameson MillerQuoted 18 lines · Jameson Miller
Changes from V2:
- Tweak logic of finding available memory block for memory allocation
- Only search head block
- Tweaked handling of large memory allocations.
- Large blocks now tracked in same manner as "regular" blocks
- Large blocks are placed at end of linked list of memory blocks
-
Cache_entrytype gains notion of whether it was allocated from memory pool or not
Quoted 7 lines
- Collapsed
cache_entrydiscard logic into single function. This should make code easier to maintain- Small tweaks based on V1 feedback
Base Ref: master
Web-Diff: [email protected]:jamill/git.git/commit/d608515f9e
Unrelated to this series, but to the tool you use to generate the cover-letter:
I think the Web-Diff only works when you give the http[s] address, git@ won't work here?
v4 cover Allocate cache entries from mem_pool
In reply to Jameson MillerChanges from V3:
Mainly changes from last round of feedback:
- Rename make_index_cache_entry -> make_cache_entry
- Rename make_empty_index_cache_entry -> make_empty-cache_entry
- Remove tail pointer in mem_pool
- Small code tweaks
- More accurately calculate mp_block size for platforms that do not support flexible arrays
One thing that came up with my testing is that the current automated tests do not fully cover the code path of "large" allocations from a memory pool. I was able to force this condition by manually tweaking some variables and then running the automated tests, but this is not ideal for preventing regressions in the future.
One way I can think of testing this is to add a test-helper and directly test the memory pool struct. This will allow me to control the parameters and different conditions. I was hoping for some guidance before I actually implemented these tests.
Either way, I would like to do the additional tests in a separate patch series to have a more focused discussion. I am not sure if these tests would prevent inclusion of this patch series - I am open to guidance here.
Base Ref: master
Web-Diff: https://github.com/jamill/git/compare/242ba98e44...667b8de06c
Jameson Miller (8):
read-cache: teach refresh_cache_entry() to take istate block alloc: add lifecycle APIs for cache_entry structs
mem-pool: only search head block for available space
mem-pool: tweak math on mp_block allocation size
mem-pool: add lifecycle management functions
mem-pool: fill out functionality
block alloc: allocate cache entries from mem_pool block alloc: add validations around cache_entry lifecyle
apply.c | 24 +++-- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 6 +- builtin/reset.c | 2 +- builtin/update-index.c | 26 +++-- cache.h | 53 +++++++++- git.c | 3 + mem-pool.c | 124 +++++++++++++++++++---- mem-pool.h | 23 +++++ merge-recursive.c | 4 +- read-cache.c | 259 ++++++++++++++++++++++++++++++++++++++++--------- resolve-undo.c | 4 +- split-index.c | 58 ++++++++--- tree.c | 4 +- unpack-trees.c | 40 ++++---- 16 files changed, 504 insertions(+), 139 deletions(-)
base-commit: 242ba98e44d8314fb184d240939614a3c9b424db
Signature · 1 line
2.14.3
PATCH v4 1/8 read-cache: teach refresh_cache_entry() to take istate
Refactor refresh_cache_entry() to work on a specific index, instead of implicitly using the_index. This is in preparation for making the make_cache_entry function apply to a specific index.
--- cache.h | 2 +- merge-recursive.c | 2 +- read-cache.c | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-)
cache.h +1 -1
@@ -751,7 +751,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);#define REFRESH_IGNORE_SUBMODULES 0x0010/* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020/* user friendly output, not "needs update" */ extern int refresh_index (struct index_state *,unsigned int flags,const struct pathspec *pathspec,char *seen,const char *header_msg); -extern struct cache_entry *refresh_cache_entry (struct cache_entry *,unsigned int ); +extern struct cache_entry *refresh_cache_entry (struct index_state *,struct cache_entry *,unsigned int );/* * Opportunistically update the index but do not complain if we can't.
merge-recursive.c +1 -1
@@ -323,7 +323,7 @@ static int add_cacheinfo(struct merge_options *o,if (refresh) {struct cache_entry *nce; - nce =refresh_cache_entry (ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING ); + nce =refresh_cache_entry (&the_index, ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING );if (!nce)return err (o,_ ("add_cacheinfo failed to refresh for path '%s'; merge aborting." ), path);if (nce != ce)
read-cache.c +5 -4
@@ -767,7 +767,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, ce->ce_namelen = len; ce->ce_mode =create_ce_mode (mode); - ret =refresh_cache_entry (ce, refresh_options); + ret =refresh_cache_entry (&the_index, ce, refresh_options);if (ret != ce)free (ce);return ret;
@@ -1473,10 +1473,11 @@ int refresh_index(struct index_state *istate, unsigned int flags,return has_errors; } -struct cache_entry *refresh_cache_entry (struct cache_entry *ce, -unsigned int options) +struct cache_entry *refresh_cache_entry (struct index_state *istate, +struct cache_entry *ce, +unsigned int options) { -return refresh_cache_ent (&the_index, ce, options,NULL ,NULL ); +return refresh_cache_ent (istate, ce, options,NULL ,NULL ); }
Signature · 1 line
2.14.3
PATCH v4 2/8 block alloc: add lifecycle APIs for cache_entry structs
In reply to Jameson MillerAdd an API around managing the lifetime of cache_entry structs. Abstracting memory management details behind this API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated or freed. A later commit in this series will allocate cache entries from memory pools as appropriate.
Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated by malloc() calls. This change is in preparation for using memory pools to reduce the number of malloc() calls made when loading an index.
This API makes a distinction between cache entries that are intended for use with a particular index and cache entries that are not. This enables us to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
--- apply.c | 24 ++++++------- blame.c | 5 ++- builtin/checkout.c | 8 ++--- builtin/difftool.c | 6 ++-- builtin/reset.c | 2 +- builtin/update-index.c | 26 ++++++-------- cache.h | 24 ++++++++++++- merge-recursive.c | 2 +- read-cache.c | 96 ++++++++++++++++++++++++++++++++++---------------- resolve-undo.c | 4 ++- split-index.c | 8 ++--- tree.c | 4 +-- unpack-trees.c | 35 ++++++++++++------ 13 files changed, 155 insertions(+), 89 deletions(-)
apply.c +11 -13
@@ -4090,12 +4090,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, oid.hash, name,0 ,0 ); + ce =make_cache_entry (&result, patch->old_mode, oid.hash, name,0 ,0 );if (!ce)return error (_ ("make_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +discard_cache_entry (ce);return error (_ ("could not add %s to temporary index" ), name); }
@@ -4263,12 +4263,11 @@ static int add_index_file(struct apply_state *state,struct stat st;struct cache_entry *ce;int namelen =strlen (path); -unsigned ce_size =cache_entry_size (namelen);if (!state->update_index)return 0 ; - ce =xcalloc (1 , ce_size); + ce =make_empty_cache_entry (&the_index, namelen);memcpy (ce->name, path, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (0 );
@@ -4278,13 +4277,13 @@ static int add_index_file(struct apply_state *state,if (!skip_prefix (buf,"Subproject commit " , &s) ||get_oid_hex (s, &ce->oid)) { -free (ce); -return error (_ ("corrupt patch for submodule %s" ), path); +discard_cache_entry (ce); +return error (_ ("corrupt patch for submodule %s" ), path); } }else {if (!state->cached) {if (lstat (path, &st) <0 ) { -free (ce); +discard_cache_entry (ce);return error_errno (_ ("unable to stat newly " "created file '%s'" ), path);
@@ -4292,13 +4291,13 @@ static int add_index_file(struct apply_state *state,fill_stat_cache_info (ce, &st); }if (write_object_file (buf, size, blob_type, &ce->oid) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to create backing store " "for newly created file %s" ), path); } }if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), path); }
@@ -4422,27 +4421,26 @@ static int add_conflicted_stages_file(struct apply_state *state, struct patch *patch) {int stage, namelen; -unsigned ce_size, mode; +unsigned mode;struct cache_entry *ce;if (!state->update_index)return 0 ; namelen =strlen (patch->new_name); - ce_size =cache_entry_size (namelen); mode = patch->new_mode ? patch->new_mode : (S_IFREG |0644 );remove_file_from_cache (patch->new_name);for (stage =1 ; stage <4 ; stage++) {if (is_null_oid (&patch->threeway_stage[stage -1 ]))continue ; - ce =xcalloc (1 , ce_size); + ce =make_empty_cache_entry (&the_index, namelen);memcpy (ce->name, patch->new_name, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = namelen;oidcpy (&ce->oid, &patch->threeway_stage[stage -1 ]);if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), patch->new_name); }
blame.c +2 -3
@@ -154,7 +154,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,struct strbuf buf =STRBUF_INIT ;const char *ident;time_t now; -int size, len; +int len;struct cache_entry *ce;unsigned mode;struct strbuf msg =STRBUF_INIT ;
@@ -252,8 +252,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,/* Let's not bother reading from HEAD tree */ mode =S_IFREG |0644 ; } - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, &origin->blob_oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 );
builtin/checkout.c +4 -4
@@ -77,7 +77,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,return READ_TREE_RECURSIVE ; len = base->len +strlen (pathname); - ce =xcalloc (1 ,cache_entry_size (len)); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, base->buf, base->len);memcpy (ce->name + base->len, pathname, len - base->len);
@@ -96,7 +96,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,if (ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { old->ce_flags |=CE_UPDATE ; -free (ce); +discard_cache_entry (ce);return 0 ; } }
@@ -230,11 +230,11 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, oid.hash, path,2 ,0 ); + ce =make_transient_cache_entry (mode, oid.hash, path,2 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return status; }
builtin/difftool.c +3 -3
@@ -321,10 +321,10 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid->hash, path,0 ,0 ); + ce =make_transient_cache_entry (mode, oid->hash, path,0 ); ret =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return ret; }
@@ -488,7 +488,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, roid.hash, +make_cache_entry (&wtindex, rmode, roid.hash, dst_path,0 ,0 ); add_index_entry(&wtindex, ce2,
builtin/reset.c +1 -1
@@ -134,7 +134,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, one->oid.hash, one->path, + ce =make_cache_entry (&the_index, one->mode, one->oid.hash, one->path,0 ,0 );if (!ce) die(_("make_cache_entry failed for path '%s'"),
builtin/update-index.c +11 -15
@@ -268,15 +268,14 @@ static int process_lstat_error(const char *path, int err)static int add_one_path (const struct cache_entry *old,const char *path,int len,struct stat *st) { -int option, size; +int option;struct cache_entry *ce;/* Was the old index entry already up-to-date? */ if (old && !ce_stage (old) && !ce_match_stat (old, st,0 ))return 0 ; - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 ); ce->ce_namelen = len;
@@ -285,13 +284,13 @@ static int add_one_path(const struct cache_entry *old, const char *path, int lenif (index_path (&ce->oid, path, st, info_only ?0 :HASH_WRITE_OBJECT )) { -free (ce); +discard_cache_entry (ce);return -1 ; } option = allow_add ?ADD_CACHE_OK_TO_ADD :0 ; option |= allow_replace ?ADD_CACHE_OK_TO_REPLACE :0 ;if (add_cache_entry (ce, option)) { -free (ce); +discard_cache_entry (ce);return error ("%s: cannot add to the index - missing --add option?" , path); }return 0 ;
@@ -402,15 +401,14 @@ static int process_path(const char *path, struct stat *st, int stat_errno)static int add_cacheinfo (unsigned int mode,const struct object_id *oid,const char *path,int stage) { -int size, len, option; +int len, option;struct cache_entry *ce;if (!verify_path (path, mode))return error ("Invalid path '%s'" , path); len =strlen (path); - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, path, len);
@@ -599,7 +597,6 @@ static struct cache_entry *read_one_ent(const char *which, {unsigned mode;struct object_id oid; -int size;struct cache_entry *ce;if (get_tree_entry (ent, path, &oid, &mode)) {
@@ -612,8 +609,7 @@ static struct cache_entry *read_one_ent(const char *which,error ("%s: not a blob in %s branch." , path, which);return NULL ; } - size =cache_entry_size (namelen); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, namelen);oidcpy (&ce->oid, &oid);memcpy (ce->name, path, namelen);
@@ -690,8 +686,8 @@ static int unresolve_one(const char *path)error ("%s: cannot add their version to the index." , path); ret =-1 ; free_return: -free (ce_2); -free (ce_3); +discard_cache_entry (ce_2); +discard_cache_entry (ce_3);return ret; }
@@ -758,7 +754,7 @@ static int do_reupdate(int ac, const char **av, ce->name, ce_namelen(ce), 0);if (old && ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { -free (old); +discard_cache_entry (old);continue ;/* unchanged */ }/* Be careful. The working tree may not have the
@@ -769,7 +765,7 @@ static int do_reupdate(int ac, const char **av, path =xstrdup (ce->name);update_one (path);free (path); -free (old); +discard_cache_entry (old);if (save_nr != active_nr) goto redo; }
cache.h +23 -1
@@ -339,6 +339,29 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and cleanup */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +extern struct cache_entry *make_cache_entry (struct index_state *istate,unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options); +extern struct cache_entry *make_empty_cache_entry (struct index_state *istate,size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage); +extern struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
@@ -698,7 +721,6 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options);extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -315,7 +315,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid->hash : null_sha1, path, stage,0 ); + ce =make_cache_entry (&the_index, mode, oid ? oid->hash : null_sha1, path, stage,0 );if (!ce)return err (o,_ ("add_cacheinfo failed for path '%s'; merge aborting." ), path);
read-cache.c +66 -30
232 lines not on this page · raw message
resolve-undo.c +3 -1
@@ -146,7 +146,9 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], ru->oid[i].hash, + nce =make_cache_entry (istate, + ru->mode[i], + ru->oid[i].hash, name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;
split-index.c +4 -4
@@ -123,7 +123,7 @@ static void replace_entry(size_t pos, void *data) src->ce_flags |=CE_UPDATE_IN_BASE ; src->ce_namelen = dst->ce_namelen;copy_cache_entry (dst, src); -free (src); +discard_cache_entry (src); si->nr_replacements++; }
@@ -224,7 +224,7 @@ void prepare_to_write_split_index(struct index_state *istate) base->ce_flags = base_flags;if (ret) ce->ce_flags |=CE_UPDATE_IN_BASE ; -free (base); +discard_cache_entry (base); si->base->cache[ce->index -1 ] = ce; }for (i =0 ; i < si->base->cache_nr; i++) {
@@ -301,7 +301,7 @@ void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce ce == istate->split_index->base->cache[ce->index - 1]) ce->ce_flags |= CE_REMOVE; else -free (ce ); +discard_cache_entry (ce ); } void replace_index_entry_in_base(struct index_state *istate,
@@ -314,7 +314,7 @@ void replace_index_entry_in_base(struct index_state *istate, old_entry->index <= istate->split_index->base->cache_nr) { new_entry->index = old_entry->index;if (old_entry != istate->split_index->base->cache[new_entry->index -1 ]) -free (istate->split_index->base->cache[new_entry->index -1 ]); +discard_cache_entry (istate->split_index->base->cache[new_entry->index -1 ]); istate->split_index->base->cache[new_entry->index -1 ] = new_entry; } }
tree.c +1 -3
@@ -16,15 +16,13 @@ static int read_one_entry_opt(struct index_state *istate, unsigned mode, int stage, int opt) { int len; -unsigned int size;struct cache_entry *ce;if (S_ISDIR (mode))return READ_TREE_RECURSIVE ; len =strlen (pathname); - size =cache_entry_size (baselen + len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (istate, baselen + len); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage);
unpack-trees.c +25 -10
@@ -203,10 +203,10 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce) +static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) {unsigned int size =ce_size (ce); -struct cache_entry *new_entry =xmalloc (size); +struct cache_entry *new_entry =make_empty_cache_entry (istate,ce_namelen (ce));memcpy (new_entry, ce, size);return new_entry;
@@ -216,7 +216,7 @@ static void add_entry(struct unpack_trees_options *o,const struct cache_entry *ce, unsigned int set, unsigned int clear) { -do_add_entry (o ,dup_entry (ce ),set ,clear ); + do_add_entry(o, dup_entry(ce, &o->result), set, clear); }/*
@@ -797,10 +797,17 @@ static int ce_in_traverse_path(const struct cache_entry *ce,return (info->pathlen <ce_namelen (ce)); } -static struct cache_entry *create_ce_entry (const struct traverse_info *info,const struct name_entry *n,int stage) +static struct cache_entry *create_ce_entry (const struct traverse_info *info, +const struct name_entry *n, +int stage, +struct index_state *istate, +int is_transient) {int len =traverse_path_len (info, n); -struct cache_entry *ce =xcalloc (1 ,cache_entry_size (len)); +struct cache_entry *ce = + is_transient ? +make_empty_transient_cache_entry (len) : +make_empty_cache_entry (istate, len); ce->ce_mode =create_ce_mode (n->mode); ce->ce_flags =create_ce_flags (stage);
@@ -846,7 +853,15 @@ static int unpack_nondirectories(int n, unsigned long mask, stage =3 ; else stage =2 ; - src[i + o->merge] =create_ce_entry (info, names + i, stage); + +/* +* If the merge bit is set, then the cache entries are +* discarded in the following block. In this case, +* construct "transient" cache_entries, as they are +* not stored in the index. otherwise construct the +* cache entry from the index aware logic. +*/ + src[i + o->merge] =create_ce_entry (info, names + i, stage, &o->result, o->merge); }if (o->merge) {
@@ -855,7 +870,7 @@ static int unpack_nondirectories(int n, unsigned long mask,for (i =0 ; i < n; i++) {struct cache_entry *ce = src[i + o->merge];if (ce != o->df_conflict_entry) -free (ce); +discard_cache_entry (ce); }return rc; }
@@ -1787,7 +1802,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce); +struct cache_entry *merge =dup_entry (ce, &o->result);if (!old) {/*
@@ -1807,7 +1822,7 @@ static int merged_entry(const struct cache_entry *ce,if (verify_absent (merge,ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN , o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }invalidate_ce_path (merge, o);
@@ -1833,7 +1848,7 @@ static int merged_entry(const struct cache_entry *ce, update =0 ; }else {if (verify_uptodate (old, o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }/* Migrate old flags over */
Signature · 1 line
2.14.3
Re: [PATCH v4 2/8] block alloc: add lifecycle APIs for cache_entry structs
Quoted 4 lines
cache.h +0
-0@@ -339,6 +339,29 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate);
cache.h extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce) · line 349
Quoted 10 lines
cache.h +8
-0+/* Cache entry creation and cleanup */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +extern struct cache_entry *make_cache_entry (struct index_state *istate,unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options);
How much work is it to convert these to object_id while at it instead of char *sha1?
(I think we really dislike introducing new sha1 based code; If it doesn't sound easy
maybe we can have a static inline wrapper here that just converts oid to sha1 and
then calls this function?)
Is it possible to line break these functions (c.f. refs.h which I think has one of
the best styles in the git project. It has long parameter lists, but still manages
to stay below a reasonable line length) ?
Quoted 5 lines
cache.h +4
-0+extern struct cache_entry *make_empty_cache_entry (struct index_state *istate,size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index.
cache.h extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce) · line 363
Quoted 10 lines
cache.h +10
-0+* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage); +extern struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce);
Please be consistent in the use of the extern keyword and omit them at all times here and above.
RE: [PATCH v4 2/8] block alloc: add lifecycle APIs for cache_entry structs
Quoted 5 lines
How much work is it to convert these to
object_idwhile at it instead of char *sha1?
(I think we really dislike introducing new sha1 based code; If it doesn't sound easy maybe we can have a static inline wrapper here that just converts oid to sha1 and then calls this function?)
I have made this change in my latest patch series with 0ac48f3af7 ("read-cache: make_cache_entry should take object_id struct", 2018-06-22)
Quoted 8 lines
Is it possible to line break these functions (c.f.
refs.hwhich I think has one of the best styles in the git project. It has long parameter lists, but still manages to stay below a reasonable line length) ?Review
Quoted 2 lines
(quoted diff) +2
-0+extern struct cache_entry *make_empty_cache_entry(struct index_state +*istate, size_t name_len);
I have formatted the function declarations to break them up
Quoted 10 lines
Quoted 10 lines
cache.h +10
-0+ +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +extern struct cache_entry *make_transient_cache_entry (unsigned int +mode,const unsigned char *sha1,const char *path,int stage);extern +struct cache_entry *make_empty_transient_cache_entry (size_t +name_len);
Quoted 8 lines
cache.h extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce) · line 363
Quoted 5 lines
cache.h +5
-0+ +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce);Please be consistent in the use of the extern keyword and omit them at all times here and above.
The header file is a bit inconsistent overall, but I switched my declarations to not use the extern keyword.
PATCH v4 3/8 mem-pool: only search head block for available space
In reply to Jameson MillerInstead of searching all memory blocks for available space to fulfill a memory request, only search the head block. If the head block does not have space, assume that previous block would most likely not be able to fulfill request either. This could potentially lead to more memory fragmentation, but also avoids searching memory blocks that probably will not be able to fulfill request.
This pattern will benefit consumers that are able to generate a good estimate for how much memory will be needed, or if they are performing fixed sized allocations, so that once a block is exhausted it will never be able to fulfill a future request.
--- mem-pool.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
mem-pool.c +4 -4
@@ -21,16 +21,16 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t bvoid *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) { -struct mp_block *p; +struct mp_block *p =NULL ;void *r;/* round up to a 'uintmax_t' alignment */ if (len & (sizeof (uintmax_t) -1 )) len +=sizeof (uintmax_t) - (len & (sizeof (uintmax_t) -1 )); -for (p = mem_pool->mp_block; p; p = p->next_block) -if (p->end - p->next_free >= len) -break ; +if (mem_pool->mp_block && + mem_pool->mp_block->end - mem_pool->mp_block->next_free >= len) + p = mem_pool->mp_block;if (!p) {if (len >= (mem_pool->block_alloc /2 )) {
Signature · 1 line
2.14.3
Re: [PATCH v4 3/8] mem-pool: only search head block for available space
Quoted 7 lines · Jameson Miller
Instead of searching all memory blocks for available space to fulfill a memory request, only search the head block. If the head block does not have space, assume that previous block would most likely not be able to fulfill request either. This could potentially lead to more memory fragmentation, but also avoids searching memory blocks that probably will not be able to fulfill request.
Do we have any numbers on performance or memory pressure here? (I would think benchmarking fast-import would suffice as that is where the mem pool originated)
Quoted 4 lines
This pattern will benefit consumers that are able to generate a good estimate for how much memory will be needed, or if they are performing fixed sized allocations, so that once a block is exhausted it will never be able to fulfill a future request.
Would this be a good candidate to contain parts of
https://public-inbox.org/git/DM5PR21MB07803E8D2627676788659E63CE770@DM5PR21MB0780.namprd21.prod.outlook.com/
?
RE: [PATCH v4 3/8] mem-pool: only search head block for available space
Quoted 3 lines
Do we have any numbers on performance or memory pressure here?
(I would think benchmarking fast-import would suffice as that is where the mem pool originated)
I ran fast-import on the git.git and linux.git repositories - this action reports the overall memory allocated to pools. The overall memory used by pools did not change in my tests (git.git used 2 blocks, and linux.git used 12 blocks). I included this information in the corresponding commit message.
Quoted 7 lines
Quoted 5 lines
This pattern will benefit consumers that are able to generate a good estimate for how much memory will be needed, or if they are performing fixed sized allocations, so that once a block is exhausted it will never be able to fulfill a future request.
Would this be a good candidate to contain parts of
I included these details in the commit message that adds the field indicating the cache_entry was allocated from a mem-pool.
PATCH v4 4/8 mem-pool: tweak math on mp_block allocation size
In reply to Jameson MillerThe amount of memory to allocate for mp_blocks was off by a sizeof(uintmax_t) on platforms that do not support flexible arrays. To account for this, use the offset of the space field when calculating the total amount of memory to allocate for a mp_block.
--- mem-pool.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
mem-pool.c +4 -2
@@ -8,9 +8,11 @@static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc) {struct mp_block *p; +size_t total_alloc =st_add (offsetof(struct mp_block , space), block_alloc); + + mem_pool->pool_alloc =st_add (mem_pool->pool_alloc, total_alloc); + p =xmalloc (total_alloc); - mem_pool->pool_alloc +=sizeof (struct mp_block ) + block_alloc; - p =xmalloc (st_add (sizeof (struct mp_block ), block_alloc)); p->next_block = mem_pool->mp_block; p->next_free = (char *)p->space; p->end = p->next_free + block_alloc;
Signature · 1 line
2.14.3
PATCH v4 5/8 mem-pool: add lifecycle management functions
In reply to Jameson MillerAdd initialization and discard functions to mem_pool type. As the memory allocated by mem_pool can now be freed, we also track the large memory alllocations so they can be freed as well.
If the there are existing mp_blocks in the mem_pool's linked list of mp_blocks, then the mp_block for a large allocation is inserted after the head block. This is because only the head mp_block is considered when searching for availble space. This results in the following desirable properties:
1) The mp_block allocated for the large request will not be included in the search for available space in future requests, as the large mp_block is sized for the specific request and does not contain any spare space.
2) The head mp_block will not bumped from considation for future memory requests just because a request for a large chunk of memory came in.
These changes are in preparation for a future commit that will utilize creating and discarding memory pool.
--- mem-pool.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ mem-pool.h | 10 ++++++++++ 2 files changed, 61 insertions(+), 12 deletions(-)
mem-pool.c +51 -12
@@ -5,7 +5,14 @@#include "cache.h" #include "mem-pool.h" -static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc) +#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block); + +/* +* Allocate a new mp_block and insert it after the block specified in +* `insert_after`. If `insert_after` is NULL, then insert block at the +* head of the linked list. +*/ +static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc,struct mp_block *insert_after) {struct mp_block *p;size_t total_alloc =st_add (offsetof(struct mp_block , space), block_alloc);
@@ -13,14 +20,51 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t b mem_pool->pool_alloc =st_add (mem_pool->pool_alloc, total_alloc); p =xmalloc (total_alloc); - p->next_block = mem_pool->mp_block; p->next_free = (char *)p->space; p->end = p->next_free + block_alloc; - mem_pool->mp_block = p; + +if (insert_after) { + p->next_block = insert_after->next_block; + insert_after->next_block = p; + }else { + p->next_block = mem_pool->mp_block; + mem_pool->mp_block = p; + }return p; } +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size) +{ +struct mem_pool *pool; + +if (*mem_pool) +return ; + + pool =xcalloc (1 ,sizeof (*pool)); + + pool->block_alloc =BLOCK_GROWTH_SIZE ; + +if (initial_size >0 ) +mem_pool_alloc_block (pool, initial_size,NULL ); + + *mem_pool = pool; +} + +void mem_pool_discard (struct mem_pool *mem_pool) +{ +struct mp_block *block, *block_to_free; + +while ((block = mem_pool->mp_block)) + { + block_to_free = block; + block = block->next_block; +free (block_to_free); + } + +free (mem_pool); +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p =NULL ;
@@ -33,15 +77,10 @@ void *mem_pool_alloc(struct mem_pool *mem_pool, size_t len)if (mem_pool->mp_block && mem_pool->mp_block->end - mem_pool->mp_block->next_free >= len) p = mem_pool->mp_block; - -if (!p) { -if (len >= (mem_pool->block_alloc /2 )) { - mem_pool->pool_alloc += len; -return xmalloc (len); - } - - p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc); - } +else if (len >= (mem_pool->block_alloc /2 )) + p =mem_pool_alloc_block (mem_pool, len, mem_pool->mp_block); +else + p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc,NULL ); r = p->next_free; p->next_free += len;
mem-pool.h +10 -0
@@ -21,6 +21,16 @@ struct mem_pool {size_t pool_alloc; }; +/* +* Initialize mem_pool with specified initial size. +*/ +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size); + +/* +* Discard a memory pool and free all the memory it is responsible for. +*/ +void mem_pool_discard (struct mem_pool *mem_pool); +/* * Alloc memory from the mem_pool. */
Signature · 1 line
2.14.3
PATCH v4 6/8 mem-pool: fill out functionality
In reply to Jameson MillerAdd functions for:
- combining two memory pools
- determining if a memory address is within the range managed by a memory pool
These functions will be used by future commits.
--- mem-pool.c | 42 ++++++++++++++++++++++++++++++++++++++++++ mem-pool.h | 13 +++++++++++++ 2 files changed, 55 insertions(+)
mem-pool.c +42 -0
@@ -94,3 +94,45 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +struct mp_block *p; + +/* Check if memory is allocated in a block */ +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +struct mp_block *p; + +/* Append the blocks from src to dst */ +if (dst->mp_block && src->mp_block) { +/* +* src and dst have blocks, append +* blocks from src to dst. +*/ + p = dst->mp_block; +while (p->next_block) + p = p->next_block; + + p->next_block = src->mp_block; + }else if (src->mp_block) { +/* +* src has blocks, dst is empty. +*/ + dst->mp_block = src->mp_block; + }else { +/* src is empty, nothing to do. */ + } + + dst->pool_alloc += src->pool_alloc; + src->pool_alloc =0 ; + src->mp_block =NULL ; +}
mem-pool.h +13 -0
@@ -41,4 +41,17 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len);*/ void *mem_pool_calloc (struct mem_pool *pool,size_t count,size_t size); +/* +* Move the memory associated with the 'src' pool to the 'dst' pool. The 'src' +* pool will be empty and not contain any memory. It still needs to be free'd +* with a call to `mem_pool_discard`. +*/ +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src); + +/* +* Check if a memory pointed at by 'mem' is part of the range of +* memory managed by the specified mem_pool. +*/ +int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +#endif
Signature · 1 line
2.14.3
PATCH v4 7/8 block alloc: allocate cache entries from mem_pool
In reply to Jameson MillerWhen reading large indexes from disk, a portion of the time is dominated in malloc() calls. This can be mitigated by allocating a large block of memory and manage it ourselves via memory pools.
This change moves the cache entry allocation to be on top of memory pools.
Design:
The index_state struct will gain a notion of an associated memory_pool from which cache_entries will be allocated from. When reading in the index from disk, we have information on the number of entries and their size, which can guide us in deciding how large our initial memory allocation should be. When an index is discarded, the associated memory_pool will be discarded as well - so the lifetime of a cache_entry is tied to the lifetime of the index_state that it was allocated for.
In the case of a Split Index, the following rules are followed. 1st, some terminology is defined:
Terminology:
- 'the_index': represents the logical view of the index
- 'split_index': represents the "base" cache entries. Read from the split index file.
'the_index' can reference a single split_index, as well as cache_entries from the split_index. the_index will be discarded before the split_index is. This means that when we are allocating cache_entries in the presence of a split index, we need to allocate the entries from the split_index's memory pool. This allows us to follow the pattern that the_index can reference cache_entries from the split_index, and that the cache_entries will not be freed while they are still being referenced.
--- cache.h | 21 ++++++++++ mem-pool.c | 3 +- read-cache.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++--------- split-index.c | 50 ++++++++++++++++++++---- unpack-trees.c | 13 +------ 5 files changed, 167 insertions(+), 39 deletions(-)
cache.h +21 -0
@@ -15,6 +15,7 @@#include "path.h" #include "sha1-array.h" #include "repository.h" +#include "mem-pool.h" #include <zlib.h> typedef struct git_zstream {
@@ -156,6 +157,7 @@ struct cache_entry {struct stat_data ce_stat_data;unsigned int ce_mode;unsigned int ce_flags; +unsigned int mem_pool_allocated;unsigned int ce_namelen;unsigned int index;/* for link extension */ struct object_id oid;
@@ -227,6 +229,7 @@ static inline void copy_cache_entry(struct cache_entry *dst, const struct cache_entry *src) {unsigned int state = dst->ce_flags &CE_HASHED ; +int mem_pool_allocated = dst->mem_pool_allocated;/* Don't copy hash chain and name */ memcpy(&dst->ce_stat_data, &src->ce_stat_data,
@@ -235,6 +238,9 @@ static inline void copy_cache_entry(struct cache_entry *dst,/* Restore the hash state */ dst->ce_flags = (dst->ce_flags & ~CE_HASHED ) | state; + +/* Restore the mem_pool_allocated flag */ + dst->mem_pool_allocated = mem_pool_allocated; }static inline unsigned create_ce_flags (unsigned stage)
@@ -328,6 +334,7 @@ struct index_state {struct untracked_cache *untracked;uint64_t fsmonitor_last_update;struct ewah_bitmap *fsmonitor_dirty; +struct mem_pool *ce_mem_pool; };extern struct index_state the_index;
@@ -362,6 +369,20 @@ extern struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Duplicate a cache_entry. Allocate memory for the new entry from a +* memory_pool. Takes into account cache_entry fields that are meant +* for managing the underlying memory allocation of the cache_entry. +*/ +struct cache_entry *dup_cache_entry (const struct cache_entry *ce,struct index_state *istate); + +/* +* Validate the cache entries in the index. This is an internal +* consistency check that the cache_entry structs are allocated from +* the expected memory pool. +*/ +void validate_cache_entries (const struct index_state *istate); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
mem-pool.c +2 -1
@@ -55,7 +55,8 @@ void mem_pool_discard(struct mem_pool *mem_pool) {struct mp_block *block, *block_to_free; -while ((block = mem_pool->mp_block)) + block = mem_pool->mp_block; +while (block) { block_to_free = block; block = block->next_block;
read-cache.c +100 -19
199 lines not on this page · raw message
split-index.c +42 -8
70 lines not on this page · raw message
unpack-trees.c +2 -11
@@ -203,20 +203,11 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) -{ -unsigned int size =ce_size (ce); -struct cache_entry *new_entry =make_empty_cache_entry (istate,ce_namelen (ce)); - -memcpy (new_entry, ce, size); -return new_entry; -} -static void add_entry (struct unpack_trees_options *o,const struct cache_entry *ce,unsigned int set,unsigned int clear) { -do_add_entry (o,dup_entry (ce, &o->result), set, clear); +do_add_entry (o,dup_cache_entry (ce, &o->result), set, clear); }/*
@@ -1802,7 +1793,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce, &o->result); +struct cache_entry *merge =dup_cache_entry (ce, &o->result);if (!old) {/*
Signature · 1 line
2.14.3
PATCH v4 8/8 block alloc: add validations around cache_entry lifecyle
In reply to Jameson MillerAdd an option (controlled by an environment variable) perform extra validations on mem_pool allocated cache entries. When set:
1) Invalidate cache_entry memory when discarding cache_entry.
2) When discarding index_state struct, verify that all cache_entries were allocated from expected mem_pool.
3) When discarding mem_pools, invalidate mem_pool memory.
This should provide extra checks that mem_pools and their allocated cache_entries are being used as expected.
--- cache.h | 6 ++++++ git.c | 3 +++ mem-pool.c | 6 +++++- mem-pool.h | 2 +- read-cache.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 68 insertions(+), 4 deletions(-)
cache.h +6 -0
@@ -369,6 +369,12 @@ extern struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Check configuration if we should perform extra validation on cache +* entries. +*/ +int should_validate_cache_entries (void ); +/* * Duplicate a cache_entry. Allocate memory for the new entry from a * memory_pool. Takes into account cache_entry fields that are meant
git.c +3 -0
@@ -414,7 +414,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)trace_argv_printf (argv,"trace: built-in: git" ); +validate_cache_entries (&the_index); status = p->fn (argc, argv, prefix); +validate_cache_entries (&the_index); +if (status)return status;
mem-pool.c +5 -1
@@ -51,7 +51,7 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size) *mem_pool = pool; } -void mem_pool_discard (struct mem_pool *mem_pool) +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory) {struct mp_block *block, *block_to_free;
@@ -60,6 +60,10 @@ void mem_pool_discard(struct mem_pool *mem_pool) { block_to_free = block; block = block->next_block; + +if (invalidate_memory) +memset (block_to_free->space,0xDD , ((char *)block_to_free->end) - ((char *)block_to_free->space)); +free (block_to_free); }
mem-pool.h +1 -1
@@ -29,7 +29,7 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size);/* * Discard a memory pool and free all the memory it is responsible for. */ -void mem_pool_discard (struct mem_pool *mem_pool); +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory);/* * Alloc memory from the mem_pool.
read-cache.c +53 -2
82 lines not on this page · raw message
Signature · 1 line
2.14.3
v5 cover Allocate cache entries from mem_pool
In reply to Jameson MillerChanges from v4 are minor code review feedback items:
- Remove extern keyword from new function definitions in cache.h
- Make_cache_entry(..) functions work with object_id instead of sha
- Add details to commit message for "block alloc: allocate cache entries from mem_pool"
- Add details to commit message for "mem-pool: only search head block for available space"
The largest change is the new commit "read-cache: make_cache_entry should take object_id struct"
Base Ref: master
Web-Diff: https://github.com/jamill/git/compare/ed843436dd...55c9b9008f
Jameson Miller (8):
read-cache: teach refresh_cache_entry() to take istate
read-cache: make_cache_entry should take object_id struct block alloc: add lifecycle APIs for cache_entry structs
mem-pool: only search head block for available space
mem-pool: add life cycle management functions
mem-pool: fill out functionality
block alloc: allocate cache entries from mem-pool block alloc: add validations around cache_entry lifecyle
apply.c | 24 ++-- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 6 +- builtin/reset.c | 2 +- builtin/update-index.c | 26 ++-- cache.h | 64 +++++++++- git.c | 3 + mem-pool.c | 114 ++++++++++++++++-- mem-pool.h | 23 ++++ merge-recursive.c | 4 +- read-cache.c | 264 ++++++++++++++++++++++++++++++++++------- resolve-undo.c | 4 +- split-index.c | 58 +++++++-- tree.c | 4 +- unpack-trees.c | 40 ++++--- 16 files changed, 515 insertions(+), 134 deletions(-)
base-commit: ed843436dd4924c10669820cc73daf50f0b4dabd
Signature · 1 line
2.17.1
PATCH v5 1/8 read-cache: teach refresh_cache_entry() to take istate
Refactor refresh_cache_entry() to work on a specific index, instead of implicitly using the_index. This is in preparation for making the make_cache_entry function apply to a specific index.
--- cache.h | 2 +- merge-recursive.c | 2 +- read-cache.c | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-)
cache.h +1 -1
@@ -751,7 +751,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);#define REFRESH_IGNORE_SUBMODULES 0x0010/* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020/* user friendly output, not "needs update" */ extern int refresh_index (struct index_state *,unsigned int flags,const struct pathspec *pathspec,char *seen,const char *header_msg); -extern struct cache_entry *refresh_cache_entry (struct cache_entry *,unsigned int ); +extern struct cache_entry *refresh_cache_entry (struct index_state *,struct cache_entry *,unsigned int );/* * Opportunistically update the index but do not complain if we can't.
merge-recursive.c +1 -1
@@ -326,7 +326,7 @@ static int add_cacheinfo(struct merge_options *o,if (refresh) {struct cache_entry *nce; - nce =refresh_cache_entry (ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING ); + nce =refresh_cache_entry (&the_index, ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING );if (!nce)return err (o,_ ("add_cacheinfo failed to refresh for path '%s'; merge aborting." ), path);if (nce != ce)
read-cache.c +5 -4
@@ -767,7 +767,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, ce->ce_namelen = len; ce->ce_mode =create_ce_mode (mode); - ret =refresh_cache_entry (ce, refresh_options); + ret =refresh_cache_entry (&the_index, ce, refresh_options);if (ret != ce)free (ce);return ret;
@@ -1473,10 +1473,11 @@ int refresh_index(struct index_state *istate, unsigned int flags,return has_errors; } -struct cache_entry *refresh_cache_entry (struct cache_entry *ce, -unsigned int options) +struct cache_entry *refresh_cache_entry (struct index_state *istate, +struct cache_entry *ce, +unsigned int options) { -return refresh_cache_ent (&the_index, ce, options,NULL ,NULL ); +return refresh_cache_ent (istate, ce, options,NULL ,NULL ); }
Signature · 1 line
2.17.1
PATCH v5 2/8 read-cache: make_cache_entry should take object_id struct
In reply to Jameson MillerThe make_cache_entry function should take an object_id struct instead of sha.
--- apply.c | 2 +- builtin/checkout.c | 2 +- builtin/difftool.c | 4 ++-- builtin/reset.c | 2 +- cache.h | 7 ++++++- merge-recursive.c | 2 +- read-cache.c | 8 +++++--- resolve-undo.c | 2 +- 8 files changed, 18 insertions(+), 11 deletions(-)
apply.c +1 -1
@@ -4092,7 +4092,7 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, oid.hash, name,0 ,0 ); + ce =make_cache_entry (patch->old_mode, &oid, name,0 ,0 );if (!ce)return error (_ ("make_cache_entry failed for path '%s'" ), name);
builtin/checkout.c +1 -1
@@ -230,7 +230,7 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, oid.hash, path,2 ,0 ); + ce =make_cache_entry (mode, &oid, path,2 ,0 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL );
builtin/difftool.c +2 -2
@@ -321,7 +321,7 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid->hash, path,0 ,0 ); + ce =make_cache_entry (mode, oid, path,0 ,0 ); ret =checkout_entry (ce, state,NULL );free (ce);
@@ -488,7 +488,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, roid.hash, +make_cache_entry (rmode, &roid, dst_path,0 ,0 ); add_index_entry(&wtindex, ce2,
builtin/reset.c +1 -1
@@ -134,7 +134,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, one->oid.hash, one->path, + ce =make_cache_entry (one->mode, &one->oid, one->path,0 ,0 );if (!ce) die(_("make_cache_entry failed for path '%s'"),
cache.h +6 -1
@@ -698,7 +698,12 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options); +extern struct cache_entry *make_cache_entry (unsigned int mode, +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options); +extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -318,7 +318,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid->hash : null_sha1, path, stage,0 ); + ce =make_cache_entry (mode, oid ? oid : &null_oid, path, stage,0 );if (!ce)return err (o,_ ("add_cacheinfo failed for path '%s'; merge aborting." ), path);
read-cache.c +5 -3
@@ -746,8 +746,10 @@ int add_file_to_index(struct index_state *istate, const char *path, int flags) }struct cache_entry *make_cache_entry (unsigned int mode, -const unsigned char *sha1,const char *path,int stage, -unsigned int refresh_options) +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options) {int size, len;struct cache_entry *ce, *ret;
@@ -761,7 +763,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, size =cache_entry_size (len); ce =xcalloc (1 , size); -hashcpy (ce->oid.hash, sha1); +hashcpy (ce->oid.hash, oid->hash);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = len;
resolve-undo.c +1 -1
@@ -146,7 +146,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], ru->oid[i].hash, + nce =make_cache_entry (ru->mode[i], &ru->oid[i], name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;
Signature · 1 line
2.17.1
Re: [PATCH v5 2/8] read-cache: make_cache_entry should take object_id struct
Quoted 2 lines · Jameson Miller
The
make_cache_entryfunction should take anobject_idstruct instead of sha.
The name of the hash is SHA-1, not sha ;-)
It is not worth a reroll, but I do not think "should" is a particularly good thing to say in the title or justification in the log message in this case. It is more like you (or somebody else who commented) _want_ to make it take an oid for _some_ reason. "teach make_cache_entry() to take object_id" is probably a better title that admits that we do not explicitly say _why_ we are doing so, than saying "it should", which equally is not explicit ;-)
Quoted 18 lines
read-cache.c +4
-2@@ -746,8 +746,10 @@ int add_file_to_index(struct index_state *istate, const char *path, int flags) }struct cache_entry *make_cache_entry (unsigned int mode, -const unsigned char *sha1,const char *path,int stage, -unsigned int refresh_options) +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options) {int size, len;struct cache_entry *ce, *ret;
read-cache.c struct cache_entry *make_cache_entry(unsigned int mode, · line 769
Quoted 8 lines
read-cache.c +1
-1@@ -761,7 +763,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, size =cache_entry_size (len); ce =xcalloc (1 , size); -hashcpy (ce->oid.hash, sha1); +hashcpy (ce->oid.hash, oid->hash);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = len;
The patch itself looks good.
Thanks.
Re: [PATCH v5 2/8] read-cache: make_cache_entry should take object_id struct
In reply to Jameson MillerQuoted 22 lines
The
make_cache_entryfunction should take anobject_idstruct instead of sha.read-cache.c +4
-2@@ -746,8 +746,10 @@ int add_file_to_index(struct index_state *istate, const char *path, int flags) }struct cache_entry *make_cache_entry (unsigned int mode, -const unsigned char *sha1,const char *path,int stage, -unsigned int refresh_options) +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options) {int size, len;struct cache_entry *ce, *ret;
read-cache.c struct cache_entry *make_cache_entry(unsigned int mode, · line 766
Quoted 5 lines
read-cache.c +1
-1@@ -761,7 +763,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, size =cache_entry_size (len); ce =xcalloc (1 , size); -hashcpy (ce->oid.hash, sha1); +hashcpy (ce->oid.hash, oid->hash);
Speaking of using struct object_id instead of sha, please use oidcpy() here.
PATCH v5 3/8 block alloc: add lifecycle APIs for cache_entry structs
In reply to Jameson MillerAdd an API around managing the lifetime of cache_entry structs. Abstracting memory management details behind this API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated or freed. A later commit in this series will allocate cache entries from memory pools as appropriate.
Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated by malloc() calls. This change is in preparation for using memory pools to reduce the number of malloc() calls made when loading an index.
This API makes a distinction between cache entries that are intended for use with a particular index and cache entries that are not. This enables us to use the knowledge about how a cache entry will be used to make informed decisions about how to handle the corresponding memory.
--- apply.c | 24 +++++------ blame.c | 5 +-- builtin/checkout.c | 8 ++-- builtin/difftool.c | 6 +-- builtin/reset.c | 2 +- builtin/update-index.c | 26 +++++------- cache.h | 40 +++++++++++++++--- merge-recursive.c | 2 +- read-cache.c | 93 +++++++++++++++++++++++++++++------------- resolve-undo.c | 4 +- split-index.c | 8 ++-- tree.c | 4 +- unpack-trees.c | 35 +++++++++++----- 13 files changed, 165 insertions(+), 92 deletions(-)
apply.c +11 -13
@@ -4092,12 +4092,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, &oid, name,0 ,0 ); + ce =make_cache_entry (&result, patch->old_mode, &oid, name,0 ,0 );if (!ce)return error (_ ("make_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +discard_cache_entry (ce);return error (_ ("could not add %s to temporary index" ), name); }
@@ -4265,9 +4265,8 @@ static int add_index_file(struct apply_state *state,struct stat st;struct cache_entry *ce;int namelen =strlen (path); -unsigned ce_size =cache_entry_size (namelen); - ce =xcalloc (1 , ce_size); + ce =make_empty_cache_entry (&the_index, namelen);memcpy (ce->name, path, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (0 );
@@ -4280,13 +4279,13 @@ static int add_index_file(struct apply_state *state,if (!skip_prefix (buf,"Subproject commit " , &s) ||get_oid_hex (s, &ce->oid)) { -free (ce); -return error (_ ("corrupt patch for submodule %s" ), path); +discard_cache_entry (ce); +return error (_ ("corrupt patch for submodule %s" ), path); } }else {if (!state->cached) {if (lstat (path, &st) <0 ) { -free (ce); +discard_cache_entry (ce);return error_errno (_ ("unable to stat newly " "created file '%s'" ), path);
@@ -4294,13 +4293,13 @@ static int add_index_file(struct apply_state *state,fill_stat_cache_info (ce, &st); }if (write_object_file (buf, size, blob_type, &ce->oid) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to create backing store " "for newly created file %s" ), path); } }if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), path); }
@@ -4424,27 +4423,26 @@ static int add_conflicted_stages_file(struct apply_state *state, struct patch *patch) {int stage, namelen; -unsigned ce_size, mode; +unsigned mode;struct cache_entry *ce;if (!state->update_index)return 0 ; namelen =strlen (patch->new_name); - ce_size =cache_entry_size (namelen); mode = patch->new_mode ? patch->new_mode : (S_IFREG |0644 );remove_file_from_cache (patch->new_name);for (stage =1 ; stage <4 ; stage++) {if (is_null_oid (&patch->threeway_stage[stage -1 ]))continue ; - ce =xcalloc (1 , ce_size); + ce =make_empty_cache_entry (&the_index, namelen);memcpy (ce->name, patch->new_name, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = namelen;oidcpy (&ce->oid, &patch->threeway_stage[stage -1 ]);if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), patch->new_name); }
blame.c +2 -3
@@ -173,7 +173,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,struct strbuf buf =STRBUF_INIT ;const char *ident;time_t now; -int size, len; +int len;struct cache_entry *ce;unsigned mode;struct strbuf msg =STRBUF_INIT ;
@@ -271,8 +271,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,/* Let's not bother reading from HEAD tree */ mode =S_IFREG |0644 ; } - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, &origin->blob_oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 );
builtin/checkout.c +4 -4
@@ -77,7 +77,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,return READ_TREE_RECURSIVE ; len = base->len +strlen (pathname); - ce =xcalloc (1 ,cache_entry_size (len)); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, base->buf, base->len);memcpy (ce->name + base->len, pathname, len - base->len);
@@ -96,7 +96,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,if (ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { old->ce_flags |=CE_UPDATE ; -free (ce); +discard_cache_entry (ce);return 0 ; } }
@@ -230,11 +230,11 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, &oid, path,2 ,0 ); + ce =make_transient_cache_entry (mode, &oid, path,2 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return status; }
builtin/difftool.c +3 -3
@@ -321,10 +321,10 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid, path,0 ,0 ); + ce =make_transient_cache_entry (mode, oid, path,0 ); ret =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return ret; }
@@ -488,7 +488,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, &roid, +make_cache_entry (&wtindex, rmode, &roid, dst_path,0 ,0 ); add_index_entry(&wtindex, ce2,
builtin/reset.c +1 -1
@@ -134,7 +134,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, &one->oid, one->path, + ce =make_cache_entry (&the_index, one->mode, &one->oid, one->path,0 ,0 );if (!ce) die(_("make_cache_entry failed for path '%s'"),
builtin/update-index.c +11 -15
@@ -268,15 +268,14 @@ static int process_lstat_error(const char *path, int err)static int add_one_path (const struct cache_entry *old,const char *path,int len,struct stat *st) { -int option, size; +int option;struct cache_entry *ce;/* Was the old index entry already up-to-date? */ if (old && !ce_stage (old) && !ce_match_stat (old, st,0 ))return 0 ; - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 ); ce->ce_namelen = len;
@@ -285,13 +284,13 @@ static int add_one_path(const struct cache_entry *old, const char *path, int lenif (index_path (&ce->oid, path, st, info_only ?0 :HASH_WRITE_OBJECT )) { -free (ce); +discard_cache_entry (ce);return -1 ; } option = allow_add ?ADD_CACHE_OK_TO_ADD :0 ; option |= allow_replace ?ADD_CACHE_OK_TO_REPLACE :0 ;if (add_cache_entry (ce, option)) { -free (ce); +discard_cache_entry (ce);return error ("%s: cannot add to the index - missing --add option?" , path); }return 0 ;
@@ -402,15 +401,14 @@ static int process_path(const char *path, struct stat *st, int stat_errno)static int add_cacheinfo (unsigned int mode,const struct object_id *oid,const char *path,int stage) { -int size, len, option; +int len, option;struct cache_entry *ce;if (!verify_path (path, mode))return error ("Invalid path '%s'" , path); len =strlen (path); - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, path, len);
@@ -599,7 +597,6 @@ static struct cache_entry *read_one_ent(const char *which, {unsigned mode;struct object_id oid; -int size;struct cache_entry *ce;if (get_tree_entry (ent, path, &oid, &mode)) {
@@ -612,8 +609,7 @@ static struct cache_entry *read_one_ent(const char *which,error ("%s: not a blob in %s branch." , path, which);return NULL ; } - size =cache_entry_size (namelen); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, namelen);oidcpy (&ce->oid, &oid);memcpy (ce->name, path, namelen);
@@ -690,8 +686,8 @@ static int unresolve_one(const char *path)error ("%s: cannot add their version to the index." , path); ret =-1 ; free_return: -free (ce_2); -free (ce_3); +discard_cache_entry (ce_2); +discard_cache_entry (ce_3);return ret; }
@@ -758,7 +754,7 @@ static int do_reupdate(int ac, const char **av, ce->name, ce_namelen(ce), 0);if (old && ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { -free (old); +discard_cache_entry (old);continue ;/* unchanged */ }/* Be careful. The working tree may not have the
@@ -769,7 +765,7 @@ static int do_reupdate(int ac, const char **av, path =xstrdup (ce->name);update_one (path);free (path); -free (old); +discard_cache_entry (old);if (save_nr != active_nr) goto redo; }
cache.h +34 -6
@@ -339,6 +339,40 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and cleanup */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +struct cache_entry *make_cache_entry (struct index_state *istate, +unsigned int mode, +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options); + +struct cache_entry *make_empty_cache_entry (struct index_state *istate, +size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +struct cache_entry *make_transient_cache_entry (unsigned int mode, +const struct object_id *oid, +const char *path, +int stage); + +struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
@@ -698,12 +732,6 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode, -const struct object_id *oid, -const char *path, -int stage, -unsigned int refresh_options); -extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -318,7 +318,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid : &null_oid, path, stage,0 ); + ce =make_cache_entry (&the_index, mode, oid ? oid : &null_oid, path, stage,0 );if (!ce)return err (o,_ ("add_cacheinfo failed for path '%s'; merge aborting." ), path);
read-cache.c +65 -28
233 lines not on this page · raw message
resolve-undo.c +3 -1
@@ -146,7 +146,9 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], &ru->oid[i], + nce =make_cache_entry (istate, + ru->mode[i], + &ru->oid[i], name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;
split-index.c +4 -4
@@ -123,7 +123,7 @@ static void replace_entry(size_t pos, void *data) src->ce_flags |=CE_UPDATE_IN_BASE ; src->ce_namelen = dst->ce_namelen;copy_cache_entry (dst, src); -free (src); +discard_cache_entry (src); si->nr_replacements++; }
@@ -224,7 +224,7 @@ void prepare_to_write_split_index(struct index_state *istate) base->ce_flags = base_flags;if (ret) ce->ce_flags |=CE_UPDATE_IN_BASE ; -free (base); +discard_cache_entry (base); si->base->cache[ce->index -1 ] = ce; }for (i =0 ; i < si->base->cache_nr; i++) {
@@ -301,7 +301,7 @@ void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce ce == istate->split_index->base->cache[ce->index - 1]) ce->ce_flags |= CE_REMOVE; else -free (ce ); +discard_cache_entry (ce ); } void replace_index_entry_in_base(struct index_state *istate,
@@ -314,7 +314,7 @@ void replace_index_entry_in_base(struct index_state *istate, old_entry->index <= istate->split_index->base->cache_nr) { new_entry->index = old_entry->index;if (old_entry != istate->split_index->base->cache[new_entry->index -1 ]) -free (istate->split_index->base->cache[new_entry->index -1 ]); +discard_cache_entry (istate->split_index->base->cache[new_entry->index -1 ]); istate->split_index->base->cache[new_entry->index -1 ] = new_entry; } }
tree.c +1 -3
@@ -17,15 +17,13 @@ static int read_one_entry_opt(struct index_state *istate, unsigned mode, int stage, int opt) { int len; -unsigned int size;struct cache_entry *ce;if (S_ISDIR (mode))return READ_TREE_RECURSIVE ; len =strlen (pathname); - size =cache_entry_size (baselen + len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (istate, baselen + len); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage);
unpack-trees.c +25 -10
@@ -203,10 +203,10 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce) +static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) {unsigned int size =ce_size (ce); -struct cache_entry *new_entry =xmalloc (size); +struct cache_entry *new_entry =make_empty_cache_entry (istate,ce_namelen (ce));memcpy (new_entry, ce, size);return new_entry;
@@ -216,7 +216,7 @@ static void add_entry(struct unpack_trees_options *o,const struct cache_entry *ce, unsigned int set, unsigned int clear) { -do_add_entry (o ,dup_entry (ce ),set ,clear ); + do_add_entry(o, dup_entry(ce, &o->result), set, clear); }/*
@@ -797,10 +797,17 @@ static int ce_in_traverse_path(const struct cache_entry *ce,return (info->pathlen <ce_namelen (ce)); } -static struct cache_entry *create_ce_entry (const struct traverse_info *info,const struct name_entry *n,int stage) +static struct cache_entry *create_ce_entry (const struct traverse_info *info, +const struct name_entry *n, +int stage, +struct index_state *istate, +int is_transient) {int len =traverse_path_len (info, n); -struct cache_entry *ce =xcalloc (1 ,cache_entry_size (len)); +struct cache_entry *ce = + is_transient ? +make_empty_transient_cache_entry (len) : +make_empty_cache_entry (istate, len); ce->ce_mode =create_ce_mode (n->mode); ce->ce_flags =create_ce_flags (stage);
@@ -846,7 +853,15 @@ static int unpack_nondirectories(int n, unsigned long mask, stage =3 ; else stage =2 ; - src[i + o->merge] =create_ce_entry (info, names + i, stage); + +/* +* If the merge bit is set, then the cache entries are +* discarded in the following block. In this case, +* construct "transient" cache_entries, as they are +* not stored in the index. otherwise construct the +* cache entry from the index aware logic. +*/ + src[i + o->merge] =create_ce_entry (info, names + i, stage, &o->result, o->merge); }if (o->merge) {
@@ -855,7 +870,7 @@ static int unpack_nondirectories(int n, unsigned long mask,for (i =0 ; i < n; i++) {struct cache_entry *ce = src[i + o->merge];if (ce != o->df_conflict_entry) -free (ce); +discard_cache_entry (ce); }return rc; }
@@ -1787,7 +1802,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce); +struct cache_entry *merge =dup_entry (ce, &o->result);if (!old) {/*
@@ -1807,7 +1822,7 @@ static int merged_entry(const struct cache_entry *ce,if (verify_absent (merge,ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN , o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }invalidate_ce_path (merge, o);
@@ -1833,7 +1848,7 @@ static int merged_entry(const struct cache_entry *ce, update =0 ; }else {if (verify_uptodate (old, o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }/* Migrate old flags over */
Signature · 1 line
2.17.1
Re: [PATCH v5 3/8] block alloc: add lifecycle APIs for cache_entry structs
Quoted 7 lines · Jameson Miller
Add an API around managing the lifetime of
cache_entrystructs. Abstracting memory management details behind this API will allow for alternative memory management strategies without affecting all the call sites. This commit does not change how memory is allocated or freed. A later commit in this series will allocate cache entries from memory pools as appropriate.
Quoted 5 lines
Motivation:
It has been observed that the time spent loading an index with a large number of entries is partly dominated bymalloc()calls. This change is in preparation for using memory pools to reduce the number ofmalloc()calls made when loading an index.
Not worth a reroll, but having these four lines at the very beginning, dropping the line "Motivation:", and then following that with "Add an API around ..." as the second paragraph, would make the above easier to read, without stutter-causing "Motivation:" in the middle.
Quoted 9 lines
apply.c +0
-1@@ -4092,12 +4092,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, &oid, name,0 ,0 );
apply.c static int build_fake_ancestor(struct apply_state *state, struct patch *list) · line 4103
Quoted 10 lines
apply.c +2
-1+ ce =make_cache_entry (&result, patch->old_mode, &oid, name,0 ,0 );if (!ce)return error (_ ("make_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +discard_cache_entry (ce);return error (_ ("could not add %s to temporary index" ), name); }
So..., even though it wasn't clear in the proposed log message, two large part of the lifecycle management API is (1) make_cache_entry() knows for which istate it is creating the entry and (2) discarding the entry may not be just a simple matter of free()ing. Both of which makes perfect sense, but if the changes are that easily enumeratable, it would have been nicer for readers if the commit did so in the proposed log message.
Quoted 10 lines
apply.c +1
-1@@ -4424,27 +4423,26 @@ static int add_conflicted_stages_file(struct apply_state *state, struct patch *patch) {int stage, namelen; -unsigned ce_size, mode; +unsigned mode;struct cache_entry *ce;if (!state->update_index)return 0 ;
apply.c static int add_conflicted_stages_file(struct apply_state *state, · line 4438
Quoted 10 lines
apply.c +1
-2namelen =strlen (patch->new_name); - ce_size =cache_entry_size (namelen); mode = patch->new_mode ? patch->new_mode : (S_IFREG |0644 );remove_file_from_cache (patch->new_name);for (stage =1 ; stage <4 ; stage++) {if (is_null_oid (&patch->threeway_stage[stage -1 ]))continue ; - ce =xcalloc (1 , ce_size); + ce =make_empty_cache_entry (&the_index, namelen);
... and another one in the enumeration is make_empty_cache_entry() which is somehow different. And the readers are forced to read its implementation to find out how it is different, but the use of the same discard_cache_entry() suggests that the liftime rule of an entry allcoated by it may be similar to those created by make_cache_entry().
Quoted 9 lines
...
apply.c +1
-1if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), patch->new_name); }
apply.c static int checkout_merged(int pos, const struct checkout *state) · line 233
Quoted 5 lines
apply.c +1
-1@@ -230,11 +230,11 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, &oid, path,2 ,0 ); + ce =make_transient_cache_entry (mode, &oid, path,2 );
... and then yet another, which is "transient". An intelligent reader can guess from the lack of istate parameter (and from the word "transient") that the resulting one would not belong to any in-core index.
apply.c static int checkout_merged(int pos, const struct checkout *state) · line 237
Quoted 5 lines
apply.c +1
-1if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);
... but discovers that it is discarded the same way, realizes that ce knows how it was allocated to allow discard() different way to discard it, and his/her earlier conjecture about make_empty() does not hold at all and gets somewhat disappointed.
Quoted 26 lines
cache.h +18
-0@@ -339,6 +339,40 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and cleanup */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +struct cache_entry *make_cache_entry (struct index_state *istate, +unsigned int mode, +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options); + +struct cache_entry *make_empty_cache_entry (struct index_state *istate, +size_t name_len); + +/*
cache.h extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce) · line 369
Quoted 10 lines
cache.h +10
-0+* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +struct cache_entry *make_transient_cache_entry (unsigned int mode, +const struct object_id *oid, +const char *path, +int stage); + +struct cache_entry *make_empty_transient_cache_entry (size_t name_len);
OK, finally it becomes clear that we have per-istate and transient sets of two (i.e. one that takes the path, stage and mode pfront, and the other that only takes the length of the name), and ...
cache.h extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce) · line 374
Quoted 4 lines
cache.h +4
-0+/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce);
... a single function that knows how to discard each kind. It would have really helped the reader to talk about them in the proposed log message, as they are only 5 functions. Another way to make it easier for readers would have been to show the diff for cache.h first before diffs for all the others.
Re: [PATCH v5 3/8] block alloc: add lifecycle APIs for cache_entry structs
In reply to Jameson MillerQuoted 11 lines
read-cache.c +5
-0+struct cache_entry *make_transient_cache_entry (unsigned int mode,const struct object_id *oid, +const char *path,int stage) +{ +struct cache_entry *ce; +int len;
read-cache.c struct cache_entry *make_cache_entry(unsigned int mode, · line 801
Quoted 10 lines
read-cache.c +10
-0+ +if (!verify_path (path, mode)) { +error ("Invalid path '%s'" , path); +return NULL ; + } + + len =strlen (path); + ce =make_empty_transient_cache_entry (len); + +hashcpy (ce->oid.hash, oid->hash);
Please use oidcpy() here, too.
PATCH v5 4/8 mem-pool: only search head block for available space
In reply to Jameson MillerInstead of searching all memory blocks for available space to fulfill a memory request, only search the head block. If the head block does not have space, assume that previous block would most likely not be able to fulfill request either. This could potentially lead to more memory fragmentation, but also avoids searching memory blocks that probably will not be able to fulfill request.
This pattern will benefit consumers that are able to generate a good estimate for how much memory will be needed, or if they are performing fixed sized allocations, so that once a block is exhausted it will never be able to fulfill a future request.
The impact of this change on memory was tested by running fast-import on the git.git and linux.git repositories. The total amount of memory allocated to the mem-pools did not change with this approach, indicating that there is not much memory benefit to searching all nodes in the linked list.
--- mem-pool.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
mem-pool.c +4 -4
@@ -21,16 +21,16 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t bvoid *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) { -struct mp_block *p; +struct mp_block *p =NULL ;void *r;/* round up to a 'uintmax_t' alignment */ if (len & (sizeof (uintmax_t) -1 )) len +=sizeof (uintmax_t) - (len & (sizeof (uintmax_t) -1 )); -for (p = mem_pool->mp_block; p; p = p->next_block) -if (p->end - p->next_free >= len) -break ; +if (mem_pool->mp_block && + mem_pool->mp_block->end - mem_pool->mp_block->next_free >= len) + p = mem_pool->mp_block;if (!p) {if (len >= (mem_pool->block_alloc /2 )) {
Signature · 1 line
2.17.1
PATCH v5 5/8 mem-pool: add life cycle management functions
In reply to Jameson MillerAdd initialization and discard functions to mem_pool type. As the memory allocated by mem_pool can now be freed, we also track the large allocations.
If the there are existing mp_blocks in the mem_poo's linked list of mp_blocksl, then the mp_block for a large allocation is inserted behind the head block. This is because only the head mp_block is considered when searching for availble space. This results in the following desirable properties:
1) The mp_block allocated for the large request will not be included not included in the search for available in future requests, the large mp_block is sized for the specific request and does not contain any spare space.
2) The head mp_block will not bumped from considation for future memory requests just because a request for a large chunk of memory came in.
These changes are in preparation for a future commit that will utilize creating and discarding memory pool.
--- mem-pool.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++-------- mem-pool.h | 10 +++++++++ 2 files changed, 61 insertions(+), 8 deletions(-)
mem-pool.c +51 -8
@@ -5,20 +5,65 @@#include "cache.h" #include "mem-pool.h" -static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc) +#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block); + +/* +* Allocate a new mp_block and insert it after the block specified in +* `insert_after`. If `insert_after` is NULL, then insert block at the +* head of the linked list. +*/ +static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc,struct mp_block *insert_after) {struct mp_block *p; mem_pool->pool_alloc +=sizeof (struct mp_block ) + block_alloc; p =xmalloc (st_add (sizeof (struct mp_block ), block_alloc)); - p->next_block = mem_pool->mp_block; + p->next_free = (char *)p->space; p->end = p->next_free + block_alloc; - mem_pool->mp_block = p; + +if (insert_after) { + p->next_block = insert_after->next_block; + insert_after->next_block = p; + }else { + p->next_block = mem_pool->mp_block; + mem_pool->mp_block = p; + }return p; } +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size) +{ +struct mem_pool *pool; + +if (*mem_pool) +return ; + + pool =xcalloc (1 ,sizeof (*pool)); + + pool->block_alloc =BLOCK_GROWTH_SIZE ; + +if (initial_size >0 ) +mem_pool_alloc_block (pool, initial_size,NULL ); + + *mem_pool = pool; +} + +void mem_pool_discard (struct mem_pool *mem_pool) +{ +struct mp_block *block, *block_to_free; + +while ((block = mem_pool->mp_block)) + { + block_to_free = block; + block = block->next_block; +free (block_to_free); + } + +free (mem_pool); +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p =NULL ;
@@ -33,12 +78,10 @@ void *mem_pool_alloc(struct mem_pool *mem_pool, size_t len) p = mem_pool->mp_block;if (!p) { -if (len >= (mem_pool->block_alloc /2 )) { - mem_pool->pool_alloc += len; -return xmalloc (len); - } +if (len >= (mem_pool->block_alloc /2 )) +return mem_pool_alloc_block (mem_pool, len, mem_pool->mp_block); - p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc); + p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc,NULL ); } r = p->next_free;
mem-pool.h +10 -0
@@ -21,6 +21,16 @@ struct mem_pool {size_t pool_alloc; }; +/* +* Initialize mem_pool with specified initial size. +*/ +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size); + +/* +* Discard a memory pool and free all the memory it is responsible for. +*/ +void mem_pool_discard (struct mem_pool *mem_pool); +/* * Alloc memory from the mem_pool. */
Signature · 1 line
2.17.1
Re: [PATCH v5 5/8] mem-pool: add life cycle management functions
Quoted 5 lines · Jameson Miller
Add initialization and discard functions to
mem_pooltype. As the memory allocated bymem_poolcan now be freed, we also track the large allocations.If the there are existing
mp_blocksin the mem_poo's linked list of
mem_poo?
Quoted 1 line
mp_blocksl, then themp_blockfor a large allocation is inserted
mp_blocksl?
PATCH v5 6/8 mem-pool: fill out functionality
In reply to Jameson MillerAdd functions for:
- combining two memory pools
- determining if a memory address is within the range managed by a memory pool
These functions will be used by future commits.
--- mem-pool.c | 42 ++++++++++++++++++++++++++++++++++++++++++ mem-pool.h | 13 +++++++++++++ 2 files changed, 55 insertions(+)
mem-pool.c +42 -0
@@ -96,3 +96,45 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +struct mp_block *p; + +/* Check if memory is allocated in a block */ +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +struct mp_block *p; + +/* Append the blocks from src to dst */ +if (dst->mp_block && src->mp_block) { +/* +* src and dst have blocks, append +* blocks from src to dst. +*/ + p = dst->mp_block; +while (p->next_block) + p = p->next_block; + + p->next_block = src->mp_block; + }else if (src->mp_block) { +/* +* src has blocks, dst is empty. +*/ + dst->mp_block = src->mp_block; + }else { +/* src is empty, nothing to do. */ + } + + dst->pool_alloc += src->pool_alloc; + src->pool_alloc =0 ; + src->mp_block =NULL ; +}
mem-pool.h +13 -0
@@ -41,4 +41,17 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len);*/ void *mem_pool_calloc (struct mem_pool *pool,size_t count,size_t size); +/* +* Move the memory associated with the 'src' pool to the 'dst' pool. The 'src' +* pool will be empty and not contain any memory. It still needs to be free'd +* with a call to `mem_pool_discard`. +*/ +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src); + +/* +* Check if a memory pointed at by 'mem' is part of the range of +* memory managed by the specified mem_pool. +*/ +int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +#endif
Signature · 1 line
2.17.1
Re: [PATCH v5 6/8] mem-pool: fill out functionality
Quoted 5 lines · Jameson Miller
mem-pool.c +5
-0+void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +struct mp_block *p; + +/* Append the blocks from src to dst */
mem-pool.c void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size) · line 127
Quoted 10 lines
mem-pool.c +10
-0+if (dst->mp_block && src->mp_block) { +/* +* src and dst have blocks, append +* blocks from src to dst. +*/ + p = dst->mp_block; +while (p->next_block) + p = p->next_block; + + p->next_block = src->mp_block;
Just being curious, but does this interact with the "we carve out only from the first block" done in step 4/8? The remaining unused space in the first block in the src pool would be wasted, which may not be such a big deal and may not even be worth comparing the available space in two blocks and picking a larger one. But we do want to decide _after_ thinking things through nevertheless.
RE: [PATCH v5 6/8] mem-pool: fill out functionality
Quoted 12 lines
Quoted 12 lines
mem-pool.c +12
-0+void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) { +struct mp_block *p; + +/* Append the blocks from src to dst */ +if (dst->mp_block && src->mp_block) { +/* +* src and dst have blocks, append +* blocks from src to dst. +*/ + p = dst->mp_block; +while (p->next_block) + p = p->next_block;
Quoted 8 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ + p->next_block = src->mp_block;Just being curious, but does this interact with the "we carve out only from the first block" done in step 4/8? The remaining unused space in the first block in the src pool would be wasted, which may not be such a big deal and may not even be worth comparing the available space in two blocks and picking a larger one. But we do want to decide _after_ thinking things through nevertheless.
Good question - and this is something I had thought about.
In the context of current usage, this function is currently not used frequently. It is only used in split index with move_cache_to_base_index and remove_split_index. In either case, the cache_entries should already be loaded into memory, and I don't expect a big enough difference in the amount of left over space to make a meaningful difference.
In the general case I could see this being a bigger case.
For now, I thought the logic as is was an appropriate tradeoff. I don't think it would be complicated to do something smarter here, but I also didn't see much benefit in current usage. If we prefer something else here, I can update this logic.
PATCH v5 7/8 block alloc: allocate cache entries from mem-pool
In reply to Jameson MillerWhen reading large indexes from disk, a portion of the time is dominated in malloc() calls. This can be mitigated by allocating a large block of memory and manage it ourselves via memory pools.
This change moves the cache entry allocation to be on top of memory pools.
Design:
The index_state struct will gain a notion of an associated memory_pool from which cache_entries will be allocated from. When reading in the index from disk, we have information on the number of entries and their size, which can guide us in deciding how large our initial memory allocation should be. When an index is discarded, the associated memory_pool will be discarded as well - so the lifetime of a cache_entry is tied to the lifetime of the index_state that it was allocated for.
In the case of a Split Index, the following rules are followed. 1st, some terminology is defined:
Terminology:
- 'the_index': represents the logical view of the index
- 'split_index': represents the "base" cache entries. Read from the split index file.
'the_index' can reference a single split_index, as well as cache_entries from the split_index. the_index will be discarded before the split_index is. This means that when we are allocating cache_entries in the presence of a split index, we need to allocate the entries from the split_index's memory pool. This allows us to follow the pattern that the_index can reference cache_entries from the split_index, and that the cache_entries will not be freed while they are still being referenced.
Managing transient cache_entry structs:
Cache entries are usually allocated for an index, but this is not always the case. Cache entries are sometimes allocated because this is the type that the existing checkout_entry function works with. Because of this, the existing code needs to handle cache entries associated with an index / memory pool, and those that only exist transiently. Several strategies were contemplated around how to handle this:
Chosen approach:
An extra field was added to the cache_entry type to track whether the cache_entry was allocated from a memory pool or not. This is currently an int field, as there are no more available bits in the existing ce_flags bit field. If / when more bits are needed, this new field can be turned into a proper bit field.
Alternatives:
1) Do not include any information about how the cache_entry was allocated. Calling code would be responsible for tracking whether the cache_entry needed to be freed or not.
Pro: No extra memory overhead to track this state
Con: Extra complexity in callers to handle this correctly.
The extra complexity and burden to not regress this behavior in the future was more than we wanted.
2) cache_entry would gain knowledge about which mem_pool allocated it
Pro: Could (potentially) do extra logic to know when a mem_pool no
longer had references to any cache_entry
Con: cache_entry would grow heavier by a pointer, instead of int
We didn't see a tangible benefit to this approach
3) Do not add any extra information to a cache_entry, but when freeing a cache entry, check if the memory exists in a region managed by existing mem_pools.
Pro: No extra memory overhead to track state
Con: Extra computation is performed when freeing cache entries
We decided tracking and iterating over known memory pool regions was less desirable than adding an extra field to track this stae.
--- cache.h | 21 +++++++++ mem-pool.c | 3 +- read-cache.c | 119 +++++++++++++++++++++++++++++++++++++++++-------- split-index.c | 50 +++++++++++++++++---- unpack-trees.c | 13 +----- 5 files changed, 167 insertions(+), 39 deletions(-)
cache.h +21 -0
@@ -15,6 +15,7 @@#include "path.h" #include "sha1-array.h" #include "repository.h" +#include "mem-pool.h" #include <zlib.h> typedef struct git_zstream {
@@ -156,6 +157,7 @@ struct cache_entry {struct stat_data ce_stat_data;unsigned int ce_mode;unsigned int ce_flags; +unsigned int mem_pool_allocated;unsigned int ce_namelen;unsigned int index;/* for link extension */ struct object_id oid;
@@ -227,6 +229,7 @@ static inline void copy_cache_entry(struct cache_entry *dst, const struct cache_entry *src) {unsigned int state = dst->ce_flags &CE_HASHED ; +int mem_pool_allocated = dst->mem_pool_allocated;/* Don't copy hash chain and name */ memcpy(&dst->ce_stat_data, &src->ce_stat_data,
@@ -235,6 +238,9 @@ static inline void copy_cache_entry(struct cache_entry *dst,/* Restore the hash state */ dst->ce_flags = (dst->ce_flags & ~CE_HASHED ) | state; + +/* Restore the mem_pool_allocated flag */ + dst->mem_pool_allocated = mem_pool_allocated; }static inline unsigned create_ce_flags (unsigned stage)
@@ -328,6 +334,7 @@ struct index_state {struct untracked_cache *untracked;uint64_t fsmonitor_last_update;struct ewah_bitmap *fsmonitor_dirty; +struct mem_pool *ce_mem_pool; };extern struct index_state the_index;
@@ -373,6 +380,20 @@ struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Duplicate a cache_entry. Allocate memory for the new entry from a +* memory_pool. Takes into account cache_entry fields that are meant +* for managing the underlying memory allocation of the cache_entry. +*/ +struct cache_entry *dup_cache_entry (const struct cache_entry *ce,struct index_state *istate); + +/* +* Validate the cache entries in the index. This is an internal +* consistency check that the cache_entry structs are allocated from +* the expected memory pool. +*/ +void validate_cache_entries (const struct index_state *istate); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
mem-pool.c +2 -1
@@ -54,7 +54,8 @@ void mem_pool_discard(struct mem_pool *mem_pool) {struct mp_block *block, *block_to_free; -while ((block = mem_pool->mp_block)) + block = mem_pool->mp_block; +while (block) { block_to_free = block; block = block->next_block;
read-cache.c +100 -19
199 lines not on this page · raw message
split-index.c +42 -8
70 lines not on this page · raw message
unpack-trees.c +2 -11
@@ -203,20 +203,11 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) -{ -unsigned int size =ce_size (ce); -struct cache_entry *new_entry =make_empty_cache_entry (istate,ce_namelen (ce)); - -memcpy (new_entry, ce, size); -return new_entry; -} -static void add_entry (struct unpack_trees_options *o,const struct cache_entry *ce,unsigned int set,unsigned int clear) { -do_add_entry (o,dup_entry (ce, &o->result), set, clear); +do_add_entry (o,dup_cache_entry (ce, &o->result), set, clear); }/*
@@ -1802,7 +1793,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce, &o->result); +struct cache_entry *merge =dup_cache_entry (ce, &o->result);if (!old) {/*
Signature · 1 line
2.17.1
PATCH v5 8/8 block alloc: add validations around cache_entry lifecyle
In reply to Jameson MillerAdd an option (controlled by an environment variable) perform extra validations on mem_pool allocated cache entries. When set:
1) Invalidate cache_entry memory when discarding cache_entry.
2) When discarding index_state struct, verify that all cache_entries were allocated from expected mem_pool.
3) When discarding mem_pools, invalidate mem_pool memory.
This should provide extra checks that mem_pools and their allocated cache_entries are being used as expected.
--- cache.h | 6 ++++++ git.c | 3 +++ mem-pool.c | 6 +++++- mem-pool.h | 2 +- read-cache.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 68 insertions(+), 4 deletions(-)
cache.h +6 -0
@@ -380,6 +380,12 @@ struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Check configuration if we should perform extra validation on cache +* entries. +*/ +int should_validate_cache_entries (void ); +/* * Duplicate a cache_entry. Allocate memory for the new entry from a * memory_pool. Takes into account cache_entry fields that are meant
git.c +3 -0
@@ -414,7 +414,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)trace_argv_printf (argv,"trace: built-in: git" ); +validate_cache_entries (&the_index); status = p->fn (argc, argv, prefix); +validate_cache_entries (&the_index); +if (status)return status;
mem-pool.c +5 -1
@@ -50,7 +50,7 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size) *mem_pool = pool; } -void mem_pool_discard (struct mem_pool *mem_pool) +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory) {struct mp_block *block, *block_to_free;
@@ -59,6 +59,10 @@ void mem_pool_discard(struct mem_pool *mem_pool) { block_to_free = block; block = block->next_block; + +if (invalidate_memory) +memset (block_to_free->space,0xDD , ((char *)block_to_free->end) - ((char *)block_to_free->space)); +free (block_to_free); }
mem-pool.h +1 -1
@@ -29,7 +29,7 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size);/* * Discard a memory pool and free all the memory it is responsible for. */ -void mem_pool_discard (struct mem_pool *mem_pool); +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory);/* * Alloc memory from the mem_pool.
read-cache.c +53 -2
82 lines not on this page · raw message
Signature · 1 line
2.17.1
v6 cover Allocate cache entries from mem_pool
In reply to Jameson MillerChanges since v5:
- Updated commit messages for:
7581156e8a read-cache: teach refresh_cache_entry to take istate 2c962aa0cd block alloc: add lifecycle APIs for cache_entry structs
- Use oidcpy function instead of hashcpy function
### Interdiff (v5..v6):
read-cache.c +2 -2
@@ -814,7 +814,7 @@ struct cache_entry *make_cache_entry(struct index_state *istate, len =strlen (path); ce =make_empty_cache_entry (istate, len); -hashcpy (ce->oid.hash, oid->hash); +oidcpy (&ce->oid, oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = len;
@@ -840,7 +840,7 @@ struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct o len =strlen (path); ce =make_empty_transient_cache_entry (len); -hashcpy (ce->oid.hash, oid->hash); +oidcpy (&ce->oid, oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = len;
### Patches
Jameson Miller (8):
read-cache: teach refresh_cache_entry to take istate
read-cache: teach make_cache_entry to take object_id block alloc: add lifecycle APIs for cache_entry structs
mem-pool: only search head block for available space
mem-pool: add life cycle management functions
mem-pool: fill out functionality
block alloc: allocate cache entries from mem_pool block alloc: add validations around cache_entry lifecyle
apply.c | 24 ++-- blame.c | 5 +- builtin/checkout.c | 8 +- builtin/difftool.c | 6 +- builtin/reset.c | 2 +- builtin/update-index.c | 26 ++-- cache.h | 64 +++++++++- git.c | 3 + mem-pool.c | 114 ++++++++++++++++-- mem-pool.h | 23 ++++ merge-recursive.c | 4 +- read-cache.c | 264 ++++++++++++++++++++++++++++++++++------- resolve-undo.c | 4 +- split-index.c | 58 +++++++-- tree.c | 4 +- unpack-trees.c | 40 ++++--- 16 files changed, 515 insertions(+), 134 deletions(-)
base-commit: ed843436dd4924c10669820cc73daf50f0b4dabd
Signature · 1 line
2.17.1
PATCH v6 1/8 read-cache: teach refresh_cache_entry to take istate
Refactor refresh_cache_entry() to work on a specific index, instead of implicitly using the_index. This is in preparation for making the make_cache_entry function apply to a specific index.
--- cache.h | 2 +- merge-recursive.c | 2 +- read-cache.c | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-)
cache.h +1 -1
@@ -751,7 +751,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);#define REFRESH_IGNORE_SUBMODULES 0x0010/* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020/* user friendly output, not "needs update" */ extern int refresh_index (struct index_state *,unsigned int flags,const struct pathspec *pathspec,char *seen,const char *header_msg); -extern struct cache_entry *refresh_cache_entry (struct cache_entry *,unsigned int ); +extern struct cache_entry *refresh_cache_entry (struct index_state *,struct cache_entry *,unsigned int );/* * Opportunistically update the index but do not complain if we can't.
merge-recursive.c +1 -1
@@ -326,7 +326,7 @@ static int add_cacheinfo(struct merge_options *o,if (refresh) {struct cache_entry *nce; - nce =refresh_cache_entry (ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING ); + nce =refresh_cache_entry (&the_index, ce,CE_MATCH_REFRESH |CE_MATCH_IGNORE_MISSING );if (!nce)return err (o,_ ("add_cacheinfo failed to refresh for path '%s'; merge aborting." ), path);if (nce != ce)
read-cache.c +5 -4
@@ -767,7 +767,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, ce->ce_namelen = len; ce->ce_mode =create_ce_mode (mode); - ret =refresh_cache_entry (ce, refresh_options); + ret =refresh_cache_entry (&the_index, ce, refresh_options);if (ret != ce)free (ce);return ret;
@@ -1473,10 +1473,11 @@ int refresh_index(struct index_state *istate, unsigned int flags,return has_errors; } -struct cache_entry *refresh_cache_entry (struct cache_entry *ce, -unsigned int options) +struct cache_entry *refresh_cache_entry (struct index_state *istate, +struct cache_entry *ce, +unsigned int options) { -return refresh_cache_ent (&the_index, ce, options,NULL ,NULL ); +return refresh_cache_ent (istate, ce, options,NULL ,NULL ); }
Signature · 1 line
2.17.1
PATCH v6 2/8 read-cache: teach make_cache_entry to take object_id
In reply to Jameson MillerTeach make_cache_entry function to take object_id instead of a SHA-1.
--- apply.c | 2 +- builtin/checkout.c | 2 +- builtin/difftool.c | 4 ++-- builtin/reset.c | 2 +- cache.h | 7 ++++++- merge-recursive.c | 2 +- read-cache.c | 8 +++++--- resolve-undo.c | 2 +- 8 files changed, 18 insertions(+), 11 deletions(-)
apply.c +1 -1
@@ -4092,7 +4092,7 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, oid.hash, name,0 ,0 ); + ce =make_cache_entry (patch->old_mode, &oid, name,0 ,0 );if (!ce)return error (_ ("make_cache_entry failed for path '%s'" ), name);
builtin/checkout.c +1 -1
@@ -230,7 +230,7 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, oid.hash, path,2 ,0 ); + ce =make_cache_entry (mode, &oid, path,2 ,0 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL );
builtin/difftool.c +2 -2
@@ -321,7 +321,7 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid->hash, path,0 ,0 ); + ce =make_cache_entry (mode, oid, path,0 ,0 ); ret =checkout_entry (ce, state,NULL );free (ce);
@@ -488,7 +488,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, roid.hash, +make_cache_entry (rmode, &roid, dst_path,0 ,0 ); add_index_entry(&wtindex, ce2,
builtin/reset.c +1 -1
@@ -134,7 +134,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, one->oid.hash, one->path, + ce =make_cache_entry (one->mode, &one->oid, one->path,0 ,0 );if (!ce) die(_("make_cache_entry failed for path '%s'"),
cache.h +6 -1
@@ -698,7 +698,12 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode,const unsigned char *sha1,const char *path,int stage,unsigned int refresh_options); +extern struct cache_entry *make_cache_entry (unsigned int mode, +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options); +extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -318,7 +318,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid->hash : null_sha1, path, stage,0 ); + ce =make_cache_entry (mode, oid ? oid : &null_oid, path, stage,0 );if (!ce)return err (o,_ ("add_cacheinfo failed for path '%s'; merge aborting." ), path);
read-cache.c +5 -3
@@ -746,8 +746,10 @@ int add_file_to_index(struct index_state *istate, const char *path, int flags) }struct cache_entry *make_cache_entry (unsigned int mode, -const unsigned char *sha1,const char *path,int stage, -unsigned int refresh_options) +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options) {int size, len;struct cache_entry *ce, *ret;
@@ -761,7 +763,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, size =cache_entry_size (len); ce =xcalloc (1 , size); -hashcpy (ce->oid.hash, sha1); +oidcpy (&ce->oid, oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = len;
resolve-undo.c +1 -1
@@ -146,7 +146,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], ru->oid[i].hash, + nce =make_cache_entry (ru->mode[i], &ru->oid[i], name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;
Signature · 1 line
2.17.1
Re: [PATCH v6 2/8] read-cache: teach make_cache_entry to take object_id
Quoted 2 lines · Jameson Miller
Teach
make_cache_entryfunction to takeobject_idinstead of a SHA-1.
This repeats the subject line?
Sign off missing.
RE: [PATCH v6 2/8] read-cache: teach make_cache_entry to take object_id
Quoted 6 lines
Quoted 2 lines
Teach
make_cache_entryfunction to takeobject_idinstead of a SHA-1.This repeats the subject line?
Sign off missing.
Thank you Stefan for pointing this out. This does not add much information to the subject line. I could clean it up if I re-roll to fix up the missing sign off.
Junio - would you like me to re-roll this patch series and include the correct sign off, or would you be able to correct this? I can do whichever is easiest for you.
PATCH v6 3/8 block alloc: add lifecycle APIs for cache_entry structs
In reply to Jameson MillerIt has been observed that the time spent loading an index with a large number of entries is partly dominated by malloc() calls. This change is in preparation for using memory pools to reduce the number of malloc() calls made to allocate cahce entries when loading an index.
Add an API to allocate and discard cache entries, abstracting the details of managing the memory backing the cache entries. This commit does actually change how memory is managed - this will be done in a later commit in the series.
This change makes the distinction between cache entries that are associated with an index and cache entries that are not associated with an index. A main use of cache entries is with an index, and we can optimize the memory management around this. We still have other cases where a cache entry is not persisted with an index, and so we need to handle the "transient" use case as well.
To keep the congnitive overhead of managing the cache entries, there will only be a single discard function. This means there must be enough information kept with the cache entry so that we know how to discard them.
A summary of the main functions in the API is:
make_cache_entry: create cache entry for use in an index. Uses specified
parameters to populate cache_entry fields.
make_empty_cache_entry: Create an empty cache entry for use in an index.
Returns cache entry with empty fields.
make_transient_cache_entry: create cache entry that is not used in an
index. Uses specified parameters to populate cache_entry fields.
make_empty_transient_cache_entry: create cache entry that is not used in
an index. Returns cache entry with empty fields.
discard_cache_entry: A single function that knows how to discard a cache
entry regardless of how it was allocated.
--- apply.c | 24 +++++------ blame.c | 5 +-- builtin/checkout.c | 8 ++-- builtin/difftool.c | 6 +-- builtin/reset.c | 2 +- builtin/update-index.c | 26 +++++------- cache.h | 40 +++++++++++++++--- merge-recursive.c | 2 +- read-cache.c | 93 +++++++++++++++++++++++++++++------------- resolve-undo.c | 4 +- split-index.c | 8 ++-- tree.c | 4 +- unpack-trees.c | 35 +++++++++++----- 13 files changed, 165 insertions(+), 92 deletions(-)
apply.c +11 -13
@@ -4092,12 +4092,12 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list)return error (_ ("sha1 information is lacking or useless " "(%s)." ), name); - ce =make_cache_entry (patch->old_mode, &oid, name,0 ,0 ); + ce =make_cache_entry (&result, patch->old_mode, &oid, name,0 ,0 );if (!ce)return error (_ ("make_cache_entry failed for path '%s'" ), name);if (add_index_entry (&result, ce,ADD_CACHE_OK_TO_ADD )) { -free (ce); +discard_cache_entry (ce);return error (_ ("could not add %s to temporary index" ), name); }
@@ -4265,9 +4265,8 @@ static int add_index_file(struct apply_state *state,struct stat st;struct cache_entry *ce;int namelen =strlen (path); -unsigned ce_size =cache_entry_size (namelen); - ce =xcalloc (1 , ce_size); + ce =make_empty_cache_entry (&the_index, namelen);memcpy (ce->name, path, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (0 );
@@ -4280,13 +4279,13 @@ static int add_index_file(struct apply_state *state,if (!skip_prefix (buf,"Subproject commit " , &s) ||get_oid_hex (s, &ce->oid)) { -free (ce); -return error (_ ("corrupt patch for submodule %s" ), path); +discard_cache_entry (ce); +return error (_ ("corrupt patch for submodule %s" ), path); } }else {if (!state->cached) {if (lstat (path, &st) <0 ) { -free (ce); +discard_cache_entry (ce);return error_errno (_ ("unable to stat newly " "created file '%s'" ), path);
@@ -4294,13 +4293,13 @@ static int add_index_file(struct apply_state *state,fill_stat_cache_info (ce, &st); }if (write_object_file (buf, size, blob_type, &ce->oid) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to create backing store " "for newly created file %s" ), path); } }if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), path); }
@@ -4424,27 +4423,26 @@ static int add_conflicted_stages_file(struct apply_state *state, struct patch *patch) {int stage, namelen; -unsigned ce_size, mode; +unsigned mode;struct cache_entry *ce;if (!state->update_index)return 0 ; namelen =strlen (patch->new_name); - ce_size =cache_entry_size (namelen); mode = patch->new_mode ? patch->new_mode : (S_IFREG |0644 );remove_file_from_cache (patch->new_name);for (stage =1 ; stage <4 ; stage++) {if (is_null_oid (&patch->threeway_stage[stage -1 ]))continue ; - ce =xcalloc (1 , ce_size); + ce =make_empty_cache_entry (&the_index, namelen);memcpy (ce->name, patch->new_name, namelen); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage); ce->ce_namelen = namelen;oidcpy (&ce->oid, &patch->threeway_stage[stage -1 ]);if (add_cache_entry (ce,ADD_CACHE_OK_TO_ADD ) <0 ) { -free (ce); +discard_cache_entry (ce);return error (_ ("unable to add cache entry for %s" ), patch->new_name); }
blame.c +2 -3
@@ -173,7 +173,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,struct strbuf buf =STRBUF_INIT ;const char *ident;time_t now; -int size, len; +int len;struct cache_entry *ce;unsigned mode;struct strbuf msg =STRBUF_INIT ;
@@ -271,8 +271,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,/* Let's not bother reading from HEAD tree */ mode =S_IFREG |0644 ; } - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, &origin->blob_oid);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 );
builtin/checkout.c +4 -4
@@ -77,7 +77,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,return READ_TREE_RECURSIVE ; len = base->len +strlen (pathname); - ce =xcalloc (1 ,cache_entry_size (len)); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, base->buf, base->len);memcpy (ce->name + base->len, pathname, len - base->len);
@@ -96,7 +96,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,if (ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { old->ce_flags |=CE_UPDATE ; -free (ce); +discard_cache_entry (ce);return 0 ; } }
@@ -230,11 +230,11 @@ static int checkout_merged(int pos, const struct checkout *state)if (write_object_file (result_buf.ptr, result_buf.size, blob_type, &oid))die (_ ("Unable to add merge result for '%s'" ), path);free (result_buf.ptr); - ce =make_cache_entry (mode, &oid, path,2 ,0 ); + ce =make_transient_cache_entry (mode, &oid, path,2 );if (!ce)die (_ ("make_cache_entry failed for path '%s'" ), path); status =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return status; }
builtin/difftool.c +3 -3
@@ -321,10 +321,10 @@ static int checkout_path(unsigned mode, struct object_id *oid,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid, path,0 ,0 ); + ce =make_transient_cache_entry (mode, oid, path,0 ); ret =checkout_entry (ce, state,NULL ); -free (ce); +discard_cache_entry (ce);return ret; }
@@ -488,7 +488,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,* index. */ struct cache_entry *ce2 = -make_cache_entry (rmode, &roid, +make_cache_entry (&wtindex, rmode, &roid, dst_path,0 ,0 ); add_index_entry(&wtindex, ce2,
builtin/reset.c +1 -1
@@ -134,7 +134,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,continue ; } - ce =make_cache_entry (one->mode, &one->oid, one->path, + ce =make_cache_entry (&the_index, one->mode, &one->oid, one->path,0 ,0 );if (!ce) die(_("make_cache_entry failed for path '%s'"),
builtin/update-index.c +11 -15
@@ -268,15 +268,14 @@ static int process_lstat_error(const char *path, int err)static int add_one_path (const struct cache_entry *old,const char *path,int len,struct stat *st) { -int option, size; +int option;struct cache_entry *ce;/* Was the old index entry already up-to-date? */ if (old && !ce_stage (old) && !ce_match_stat (old, st,0 ))return 0 ; - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);memcpy (ce->name, path, len); ce->ce_flags =create_ce_flags (0 ); ce->ce_namelen = len;
@@ -285,13 +284,13 @@ static int add_one_path(const struct cache_entry *old, const char *path, int lenif (index_path (&ce->oid, path, st, info_only ?0 :HASH_WRITE_OBJECT )) { -free (ce); +discard_cache_entry (ce);return -1 ; } option = allow_add ?ADD_CACHE_OK_TO_ADD :0 ; option |= allow_replace ?ADD_CACHE_OK_TO_REPLACE :0 ;if (add_cache_entry (ce, option)) { -free (ce); +discard_cache_entry (ce);return error ("%s: cannot add to the index - missing --add option?" , path); }return 0 ;
@@ -402,15 +401,14 @@ static int process_path(const char *path, struct stat *st, int stat_errno)static int add_cacheinfo (unsigned int mode,const struct object_id *oid,const char *path,int stage) { -int size, len, option; +int len, option;struct cache_entry *ce;if (!verify_path (path, mode))return error ("Invalid path '%s'" , path); len =strlen (path); - size =cache_entry_size (len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, len);oidcpy (&ce->oid, oid);memcpy (ce->name, path, len);
@@ -599,7 +597,6 @@ static struct cache_entry *read_one_ent(const char *which, {unsigned mode;struct object_id oid; -int size;struct cache_entry *ce;if (get_tree_entry (ent, path, &oid, &mode)) {
@@ -612,8 +609,7 @@ static struct cache_entry *read_one_ent(const char *which,error ("%s: not a blob in %s branch." , path, which);return NULL ; } - size =cache_entry_size (namelen); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (&the_index, namelen);oidcpy (&ce->oid, &oid);memcpy (ce->name, path, namelen);
@@ -690,8 +686,8 @@ static int unresolve_one(const char *path)error ("%s: cannot add their version to the index." , path); ret =-1 ; free_return: -free (ce_2); -free (ce_3); +discard_cache_entry (ce_2); +discard_cache_entry (ce_3);return ret; }
@@ -758,7 +754,7 @@ static int do_reupdate(int ac, const char **av, ce->name, ce_namelen(ce), 0);if (old && ce->ce_mode == old->ce_mode && !oidcmp (&ce->oid, &old->oid)) { -free (old); +discard_cache_entry (old);continue ;/* unchanged */ }/* Be careful. The working tree may not have the
@@ -769,7 +765,7 @@ static int do_reupdate(int ac, const char **av, path =xstrdup (ce->name);update_one (path);free (path); -free (old); +discard_cache_entry (old);if (save_nr != active_nr) goto redo; }
cache.h +34 -6
@@ -339,6 +339,40 @@ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce)extern void free_name_hash (struct index_state *istate); +/* Cache entry creation and cleanup */ + +/* +* Create cache_entry intended for use in the specified index. Caller +* is responsible for discarding the cache_entry with +* `discard_cache_entry`. +*/ +struct cache_entry *make_cache_entry (struct index_state *istate, +unsigned int mode, +const struct object_id *oid, +const char *path, +int stage, +unsigned int refresh_options); + +struct cache_entry *make_empty_cache_entry (struct index_state *istate, +size_t name_len); + +/* +* Create a cache_entry that is not intended to be added to an index. +* Caller is responsible for discarding the cache_entry +* with `discard_cache_entry`. +*/ +struct cache_entry *make_transient_cache_entry (unsigned int mode, +const struct object_id *oid, +const char *path, +int stage); + +struct cache_entry *make_empty_transient_cache_entry (size_t name_len); + +/* +* Discard cache entry. +*/ +void discard_cache_entry (struct cache_entry *ce); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
@@ -698,12 +732,6 @@ extern int remove_file_from_index(struct index_state *, const char *path);extern int add_to_index (struct index_state *,const char *path,struct stat *,int flags);extern int add_file_to_index (struct index_state *,const char *path,int flags); -extern struct cache_entry *make_cache_entry (unsigned int mode, -const struct object_id *oid, -const char *path, -int stage, -unsigned int refresh_options); -extern int chmod_index_entry (struct index_state *,struct cache_entry *ce,char flip);extern int ce_same_name (const struct cache_entry *a,const struct cache_entry *b);extern void set_object_name_for_intent_to_add_entry (struct cache_entry *ce);
merge-recursive.c +1 -1
@@ -318,7 +318,7 @@ static int add_cacheinfo(struct merge_options *o,struct cache_entry *ce;int ret; - ce =make_cache_entry (mode, oid ? oid : &null_oid, path, stage,0 ); + ce =make_cache_entry (&the_index, mode, oid ? oid : &null_oid, path, stage,0 );if (!ce)return err (o,_ ("add_cacheinfo failed for path '%s'; merge aborting." ), path);
read-cache.c +65 -28
233 lines not on this page · raw message
resolve-undo.c +3 -1
@@ -146,7 +146,9 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)struct cache_entry *nce;if (!ru->mode[i])continue ; - nce =make_cache_entry (ru->mode[i], &ru->oid[i], + nce =make_cache_entry (istate, + ru->mode[i], + &ru->oid[i], name, i +1 ,0 );if (matched) nce->ce_flags |=CE_MATCHED ;
split-index.c +4 -4
@@ -123,7 +123,7 @@ static void replace_entry(size_t pos, void *data) src->ce_flags |=CE_UPDATE_IN_BASE ; src->ce_namelen = dst->ce_namelen;copy_cache_entry (dst, src); -free (src); +discard_cache_entry (src); si->nr_replacements++; }
@@ -224,7 +224,7 @@ void prepare_to_write_split_index(struct index_state *istate) base->ce_flags = base_flags;if (ret) ce->ce_flags |=CE_UPDATE_IN_BASE ; -free (base); +discard_cache_entry (base); si->base->cache[ce->index -1 ] = ce; }for (i =0 ; i < si->base->cache_nr; i++) {
@@ -301,7 +301,7 @@ void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce ce == istate->split_index->base->cache[ce->index - 1]) ce->ce_flags |= CE_REMOVE; else -free (ce ); +discard_cache_entry (ce ); } void replace_index_entry_in_base(struct index_state *istate,
@@ -314,7 +314,7 @@ void replace_index_entry_in_base(struct index_state *istate, old_entry->index <= istate->split_index->base->cache_nr) { new_entry->index = old_entry->index;if (old_entry != istate->split_index->base->cache[new_entry->index -1 ]) -free (istate->split_index->base->cache[new_entry->index -1 ]); +discard_cache_entry (istate->split_index->base->cache[new_entry->index -1 ]); istate->split_index->base->cache[new_entry->index -1 ] = new_entry; } }
tree.c +1 -3
@@ -17,15 +17,13 @@ static int read_one_entry_opt(struct index_state *istate, unsigned mode, int stage, int opt) { int len; -unsigned int size;struct cache_entry *ce;if (S_ISDIR (mode))return READ_TREE_RECURSIVE ; len =strlen (pathname); - size =cache_entry_size (baselen + len); - ce =xcalloc (1 , size); + ce =make_empty_cache_entry (istate, baselen + len); ce->ce_mode =create_ce_mode (mode); ce->ce_flags =create_ce_flags (stage);
unpack-trees.c +25 -10
@@ -203,10 +203,10 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce) +static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) {unsigned int size =ce_size (ce); -struct cache_entry *new_entry =xmalloc (size); +struct cache_entry *new_entry =make_empty_cache_entry (istate,ce_namelen (ce));memcpy (new_entry, ce, size);return new_entry;
@@ -216,7 +216,7 @@ static void add_entry(struct unpack_trees_options *o,const struct cache_entry *ce, unsigned int set, unsigned int clear) { -do_add_entry (o ,dup_entry (ce ),set ,clear ); + do_add_entry(o, dup_entry(ce, &o->result), set, clear); }/*
@@ -797,10 +797,17 @@ static int ce_in_traverse_path(const struct cache_entry *ce,return (info->pathlen <ce_namelen (ce)); } -static struct cache_entry *create_ce_entry (const struct traverse_info *info,const struct name_entry *n,int stage) +static struct cache_entry *create_ce_entry (const struct traverse_info *info, +const struct name_entry *n, +int stage, +struct index_state *istate, +int is_transient) {int len =traverse_path_len (info, n); -struct cache_entry *ce =xcalloc (1 ,cache_entry_size (len)); +struct cache_entry *ce = + is_transient ? +make_empty_transient_cache_entry (len) : +make_empty_cache_entry (istate, len); ce->ce_mode =create_ce_mode (n->mode); ce->ce_flags =create_ce_flags (stage);
@@ -846,7 +853,15 @@ static int unpack_nondirectories(int n, unsigned long mask, stage =3 ; else stage =2 ; - src[i + o->merge] =create_ce_entry (info, names + i, stage); + +/* +* If the merge bit is set, then the cache entries are +* discarded in the following block. In this case, +* construct "transient" cache_entries, as they are +* not stored in the index. otherwise construct the +* cache entry from the index aware logic. +*/ + src[i + o->merge] =create_ce_entry (info, names + i, stage, &o->result, o->merge); }if (o->merge) {
@@ -855,7 +870,7 @@ static int unpack_nondirectories(int n, unsigned long mask,for (i =0 ; i < n; i++) {struct cache_entry *ce = src[i + o->merge];if (ce != o->df_conflict_entry) -free (ce); +discard_cache_entry (ce); }return rc; }
@@ -1787,7 +1802,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce); +struct cache_entry *merge =dup_entry (ce, &o->result);if (!old) {/*
@@ -1807,7 +1822,7 @@ static int merged_entry(const struct cache_entry *ce,if (verify_absent (merge,ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN , o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }invalidate_ce_path (merge, o);
@@ -1833,7 +1848,7 @@ static int merged_entry(const struct cache_entry *ce, update =0 ; }else {if (verify_uptodate (old, o)) { -free (merge); +discard_cache_entry (merge);return -1 ; }/* Migrate old flags over */
Signature · 1 line
2.17.1
Re: [PATCH v6 3/8] block alloc: add lifecycle APIs for cache_entry structs
read-cache.c struct cache_entry *make_cache_entry(unsigned int mode, · line 794
Quoted 8 lines · Jameson Miller
read-cache.c +8
-0+struct cache_entry *make_transient_cache_entry (unsigned int mode,const struct object_id *oid, +const char *path,int stage) +{ +struct cache_entry *ce; +int len; + +if (!verify_path (path, mode)) { +error ("Invalid path '%s'" , path);
Please wrap all new user-visible strings in _().
Quoted 2 lines
(quoted diff) +2
-0+ return NULL; + }
Signature · 1 line
Duy
PATCH v6 4/8 mem-pool: only search head block for available space
In reply to Jameson MillerInstead of searching all memory blocks for available space to fulfill a memory request, only search the head block. If the head block does not have space, assume that previous block would most likely not be able to fulfill request either. This could potentially lead to more memory fragmentation, but also avoids searching memory blocks that probably will not be able to fulfill request.
This pattern will benefit consumers that are able to generate a good estimate for how much memory will be needed, or if they are performing fixed sized allocations, so that once a block is exhausted it will never be able to fulfill a future request.
--- mem-pool.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
mem-pool.c +4 -4
@@ -21,16 +21,16 @@ static struct mp_block *mem_pool_alloc_block(struct mem_pool *mem_pool, size_t bvoid *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) { -struct mp_block *p; +struct mp_block *p =NULL ;void *r;/* round up to a 'uintmax_t' alignment */ if (len & (sizeof (uintmax_t) -1 )) len +=sizeof (uintmax_t) - (len & (sizeof (uintmax_t) -1 )); -for (p = mem_pool->mp_block; p; p = p->next_block) -if (p->end - p->next_free >= len) -break ; +if (mem_pool->mp_block && + mem_pool->mp_block->end - mem_pool->mp_block->next_free >= len) + p = mem_pool->mp_block;if (!p) {if (len >= (mem_pool->block_alloc /2 )) {
Signature · 1 line
2.17.1
PATCH v6 5/8 mem-pool: add life cycle management functions
In reply to Jameson MillerAdd initialization and discard functions to mem_pool type. As the memory allocated by mem_pool can now be freed, we also track the large allocations.
If the there are existing mp_blocks in the mem_poo's linked list of mp_blocksl, then the mp_block for a large allocation is inserted behind the head block. This is because only the head mp_block is considered when searching for availble space. This results in the following desirable properties:
1) The mp_block allocated for the large request will not be included not included in the search for available in future requests, the large mp_block is sized for the specific request and does not contain any spare space.
2) The head mp_block will not bumped from considation for future memory requests just because a request for a large chunk of memory came in.
These changes are in preparation for a future commit that will utilize creating and discarding memory pool.
--- mem-pool.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++-------- mem-pool.h | 10 +++++++++ 2 files changed, 61 insertions(+), 8 deletions(-)
mem-pool.c +51 -8
@@ -5,20 +5,65 @@#include "cache.h" #include "mem-pool.h" -static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc) +#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block); + +/* +* Allocate a new mp_block and insert it after the block specified in +* `insert_after`. If `insert_after` is NULL, then insert block at the +* head of the linked list. +*/ +static struct mp_block *mem_pool_alloc_block (struct mem_pool *mem_pool,size_t block_alloc,struct mp_block *insert_after) {struct mp_block *p; mem_pool->pool_alloc +=sizeof (struct mp_block ) + block_alloc; p =xmalloc (st_add (sizeof (struct mp_block ), block_alloc)); - p->next_block = mem_pool->mp_block; + p->next_free = (char *)p->space; p->end = p->next_free + block_alloc; - mem_pool->mp_block = p; + +if (insert_after) { + p->next_block = insert_after->next_block; + insert_after->next_block = p; + }else { + p->next_block = mem_pool->mp_block; + mem_pool->mp_block = p; + }return p; } +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size) +{ +struct mem_pool *pool; + +if (*mem_pool) +return ; + + pool =xcalloc (1 ,sizeof (*pool)); + + pool->block_alloc =BLOCK_GROWTH_SIZE ; + +if (initial_size >0 ) +mem_pool_alloc_block (pool, initial_size,NULL ); + + *mem_pool = pool; +} + +void mem_pool_discard (struct mem_pool *mem_pool) +{ +struct mp_block *block, *block_to_free; + +while ((block = mem_pool->mp_block)) + { + block_to_free = block; + block = block->next_block; +free (block_to_free); + } + +free (mem_pool); +} +void *mem_pool_alloc (struct mem_pool *mem_pool,size_t len) {struct mp_block *p =NULL ;
@@ -33,12 +78,10 @@ void *mem_pool_alloc(struct mem_pool *mem_pool, size_t len) p = mem_pool->mp_block;if (!p) { -if (len >= (mem_pool->block_alloc /2 )) { - mem_pool->pool_alloc += len; -return xmalloc (len); - } +if (len >= (mem_pool->block_alloc /2 )) +return mem_pool_alloc_block (mem_pool, len, mem_pool->mp_block); - p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc); + p =mem_pool_alloc_block (mem_pool, mem_pool->block_alloc,NULL ); } r = p->next_free;
mem-pool.h +10 -0
@@ -21,6 +21,16 @@ struct mem_pool {size_t pool_alloc; }; +/* +* Initialize mem_pool with specified initial size. +*/ +void mem_pool_init (struct mem_pool **mem_pool,size_t initial_size); + +/* +* Discard a memory pool and free all the memory it is responsible for. +*/ +void mem_pool_discard (struct mem_pool *mem_pool); +/* * Alloc memory from the mem_pool. */
Signature · 1 line
2.17.1
PATCH v6 6/8 mem-pool: fill out functionality
In reply to Jameson MillerAdd functions for:
- combining two memory pools
- determining if a memory address is within the range managed by a memory pool
These functions will be used by future commits.
--- mem-pool.c | 42 ++++++++++++++++++++++++++++++++++++++++++ mem-pool.h | 13 +++++++++++++ 2 files changed, 55 insertions(+)
mem-pool.c +42 -0
@@ -96,3 +96,45 @@ void *mem_pool_calloc(struct mem_pool *mem_pool, size_t count, size_t size)memset (r,0 , len);return r; } + +int mem_pool_contains (struct mem_pool *mem_pool,void *mem) +{ +struct mp_block *p; + +/* Check if memory is allocated in a block */ +for (p = mem_pool->mp_block; p; p = p->next_block) +if ((mem >= ((void *)p->space)) && + (mem < ((void *)p->end))) +return 1 ; + +return 0 ; +} + +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src) +{ +struct mp_block *p; + +/* Append the blocks from src to dst */ +if (dst->mp_block && src->mp_block) { +/* +* src and dst have blocks, append +* blocks from src to dst. +*/ + p = dst->mp_block; +while (p->next_block) + p = p->next_block; + + p->next_block = src->mp_block; + }else if (src->mp_block) { +/* +* src has blocks, dst is empty. +*/ + dst->mp_block = src->mp_block; + }else { +/* src is empty, nothing to do. */ + } + + dst->pool_alloc += src->pool_alloc; + src->pool_alloc =0 ; + src->mp_block =NULL ; +}
mem-pool.h +13 -0
@@ -41,4 +41,17 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len);*/ void *mem_pool_calloc (struct mem_pool *pool,size_t count,size_t size); +/* +* Move the memory associated with the 'src' pool to the 'dst' pool. The 'src' +* pool will be empty and not contain any memory. It still needs to be free'd +* with a call to `mem_pool_discard`. +*/ +void mem_pool_combine (struct mem_pool *dst,struct mem_pool *src); + +/* +* Check if a memory pointed at by 'mem' is part of the range of +* memory managed by the specified mem_pool. +*/ +int mem_pool_contains (struct mem_pool *mem_pool,void *mem); +#endif
Signature · 1 line
2.17.1
PATCH v6 7/8 block alloc: allocate cache entries from mem_pool
In reply to Jameson MillerWhen reading large indexes from disk, a portion of the time is dominated in malloc() calls. This can be mitigated by allocating a large block of memory and manage it ourselves via memory pools.
This change moves the cache entry allocation to be on top of memory pools.
Design:
The index_state struct will gain a notion of an associated memory_pool from which cache_entries will be allocated from. When reading in the index from disk, we have information on the number of entries and their size, which can guide us in deciding how large our initial memory allocation should be. When an index is discarded, the associated memory_pool will be discarded as well - so the lifetime of a cache_entry is tied to the lifetime of the index_state that it was allocated for.
In the case of a Split Index, the following rules are followed. 1st, some terminology is defined:
Terminology:
- 'the_index': represents the logical view of the index
- 'split_index': represents the "base" cache entries. Read from the split index file.
'the_index' can reference a single split_index, as well as cache_entries from the split_index. the_index will be discarded before the split_index is. This means that when we are allocating cache_entries in the presence of a split index, we need to allocate the entries from the split_index's memory pool. This allows us to follow the pattern that the_index can reference cache_entries from the split_index, and that the cache_entries will not be freed while they are still being referenced.
Managing transient cache_entry structs:
Cache entries are usually allocated for an index, but this is not always the case. Cache entries are sometimes allocated because this is the type that the existing checkout_entry function works with. Because of this, the existing code needs to handle cache entries associated with an index / memory pool, and those that only exist transiently. Several strategies were contemplated around how to handle this:
Chosen approach:
An extra field was added to the cache_entry type to track whether the cache_entry was allocated from a memory pool or not. This is currently an int field, as there are no more available bits in the existing ce_flags bit field. If / when more bits are needed, this new field can be turned into a proper bit field.
Alternatives:
1) Do not include any information about how the cache_entry was allocated. Calling code would be responsible for tracking whether the cache_entry needed to be freed or not.
Pro: No extra memory overhead to track this state
Con: Extra complexity in callers to handle this correctly.
The extra complexity and burden to not regress this behavior in the future was more than we wanted.
2) cache_entry would gain knowledge about which mem_pool allocated it
Pro: Could (potentially) do extra logic to know when a mem_pool no
longer had references to any cache_entry
Con: cache_entry would grow heavier by a pointer, instead of int
We didn't see a tangible benefit to this approach
3) Do not add any extra information to a cache_entry, but when freeing a cache entry, check if the memory exists in a region managed by existing mem_pools.
Pro: No extra memory overhead to track state
Con: Extra computation is performed when freeing cache entries
We decided tracking and iterating over known memory pool regions was less desirable than adding an extra field to track this stae.
--- cache.h | 21 +++++++++ mem-pool.c | 3 +- read-cache.c | 119 +++++++++++++++++++++++++++++++++++++++++-------- split-index.c | 50 +++++++++++++++++---- unpack-trees.c | 13 +----- 5 files changed, 167 insertions(+), 39 deletions(-)
cache.h +21 -0
@@ -15,6 +15,7 @@#include "path.h" #include "sha1-array.h" #include "repository.h" +#include "mem-pool.h" #include <zlib.h> typedef struct git_zstream {
@@ -156,6 +157,7 @@ struct cache_entry {struct stat_data ce_stat_data;unsigned int ce_mode;unsigned int ce_flags; +unsigned int mem_pool_allocated;unsigned int ce_namelen;unsigned int index;/* for link extension */ struct object_id oid;
@@ -227,6 +229,7 @@ static inline void copy_cache_entry(struct cache_entry *dst, const struct cache_entry *src) {unsigned int state = dst->ce_flags &CE_HASHED ; +int mem_pool_allocated = dst->mem_pool_allocated;/* Don't copy hash chain and name */ memcpy(&dst->ce_stat_data, &src->ce_stat_data,
@@ -235,6 +238,9 @@ static inline void copy_cache_entry(struct cache_entry *dst,/* Restore the hash state */ dst->ce_flags = (dst->ce_flags & ~CE_HASHED ) | state; + +/* Restore the mem_pool_allocated flag */ + dst->mem_pool_allocated = mem_pool_allocated; }static inline unsigned create_ce_flags (unsigned stage)
@@ -328,6 +334,7 @@ struct index_state {struct untracked_cache *untracked;uint64_t fsmonitor_last_update;struct ewah_bitmap *fsmonitor_dirty; +struct mem_pool *ce_mem_pool; };extern struct index_state the_index;
@@ -373,6 +380,20 @@ struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Duplicate a cache_entry. Allocate memory for the new entry from a +* memory_pool. Takes into account cache_entry fields that are meant +* for managing the underlying memory allocation of the cache_entry. +*/ +struct cache_entry *dup_cache_entry (const struct cache_entry *ce,struct index_state *istate); + +/* +* Validate the cache entries in the index. This is an internal +* consistency check that the cache_entry structs are allocated from +* the expected memory pool. +*/ +void validate_cache_entries (const struct index_state *istate); +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache)#define active_nr (the_index.cache_nr)
mem-pool.c +2 -1
@@ -54,7 +54,8 @@ void mem_pool_discard(struct mem_pool *mem_pool) {struct mp_block *block, *block_to_free; -while ((block = mem_pool->mp_block)) + block = mem_pool->mp_block; +while (block) { block_to_free = block; block = block->next_block;
read-cache.c +100 -19
199 lines not on this page · raw message
split-index.c +42 -8
70 lines not on this page · raw message
unpack-trees.c +2 -11
@@ -203,20 +203,11 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } -static struct cache_entry *dup_entry (const struct cache_entry *ce,struct index_state *istate) -{ -unsigned int size =ce_size (ce); -struct cache_entry *new_entry =make_empty_cache_entry (istate,ce_namelen (ce)); - -memcpy (new_entry, ce, size); -return new_entry; -} -static void add_entry (struct unpack_trees_options *o,const struct cache_entry *ce,unsigned int set,unsigned int clear) { -do_add_entry (o,dup_entry (ce, &o->result), set, clear); +do_add_entry (o,dup_cache_entry (ce, &o->result), set, clear); }/*
@@ -1802,7 +1793,7 @@ static int merged_entry(const struct cache_entry *ce, struct unpack_trees_options *o) {int update =CE_UPDATE ; -struct cache_entry *merge =dup_entry (ce, &o->result); +struct cache_entry *merge =dup_cache_entry (ce, &o->result);if (!old) {/*
Signature · 1 line
2.17.1
PATCH v6 8/8 block alloc: add validations around cache_entry lifecyle
In reply to Jameson MillerAdd an option (controlled by an environment variable) perform extra validations on mem_pool allocated cache entries. When set:
1) Invalidate cache_entry memory when discarding cache_entry.
2) When discarding index_state struct, verify that all cache_entries were allocated from expected mem_pool.
3) When discarding mem_pools, invalidate mem_pool memory.
This should provide extra checks that mem_pools and their allocated cache_entries are being used as expected.
--- cache.h | 6 ++++++ git.c | 3 +++ mem-pool.c | 6 +++++- mem-pool.h | 2 +- read-cache.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 68 insertions(+), 4 deletions(-)
cache.h +6 -0
@@ -380,6 +380,12 @@ struct cache_entry *make_empty_transient_cache_entry(size_t name_len);*/ void discard_cache_entry (struct cache_entry *ce); +/* +* Check configuration if we should perform extra validation on cache +* entries. +*/ +int should_validate_cache_entries (void ); +/* * Duplicate a cache_entry. Allocate memory for the new entry from a * memory_pool. Takes into account cache_entry fields that are meant
git.c +3 -0
@@ -414,7 +414,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)trace_argv_printf (argv,"trace: built-in: git" ); +validate_cache_entries (&the_index); status = p->fn (argc, argv, prefix); +validate_cache_entries (&the_index); +if (status)return status;
mem-pool.c +5 -1
@@ -50,7 +50,7 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size) *mem_pool = pool; } -void mem_pool_discard (struct mem_pool *mem_pool) +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory) {struct mp_block *block, *block_to_free;
@@ -59,6 +59,10 @@ void mem_pool_discard(struct mem_pool *mem_pool) { block_to_free = block; block = block->next_block; + +if (invalidate_memory) +memset (block_to_free->space,0xDD , ((char *)block_to_free->end) - ((char *)block_to_free->space)); +free (block_to_free); }
mem-pool.h +1 -1
@@ -29,7 +29,7 @@ void mem_pool_init(struct mem_pool **mem_pool, size_t initial_size);/* * Discard a memory pool and free all the memory it is responsible for. */ -void mem_pool_discard (struct mem_pool *mem_pool); +void mem_pool_discard (struct mem_pool *mem_pool,int invalidate_memory);/* * Alloc memory from the mem_pool.
read-cache.c +53 -2
82 lines not on this page · raw message
Signature · 1 line
2.17.1