Re: [PATCH v2 1/4] odb: decouple source path comparisons from `the_repository`

Karthik Nayak <[email protected]>
Newsgroups org.kernel.vger.git
Message-ID <CAOLa=ZTsumAT6U8+pJQmNjYL6Rt=JkvTJ0V7KQ7MvLYkThTFYA@mail.gmail.com>
Patrick Steinhardt <[email protected]> writes:

> When registering alternates we deduplicate object database sources by
> their path so that the same source won't be added twice. Ever since
> cf2dc1c238 (speed up alt_odb_usable() with many alternates, 2021-07-07)
> this duplicate check is backed by a map keyed by the source's path,
> using `fspathhash()` and `fspatheq()` as hash and equality functions,
> respectively.
>
> These functions are problematic in this context for two reasons:
>
>   - They implicitly depend on `the_repository` instead of the
>     repository that owns the object database.
>
>   - They derive case-sensitivity from `repo_ignore_case()`, which
>     returns a default value in case the repository's configuration has
>     not been parsed yet. Object database sources may be registered
>     before that is the case, so the answer may flip depending on when a
>     source gets registered.
>
> Fix this by making the comparison self-contained in the object
> database. Instead of using `fspathhash()` and `fspatheq()` we resolve
> "core.ignoreCase" manually and then use the correct comparison function
> based on the result. This requires us to migrate to a `struct hashmap`,
> as the khash interface does not give us the ability to change these
> functions.
>
> Note that we can unconditionally use `strihash()` to compute entry
> hashes regardless of case sensitivity: a hash function only needs to
> guarantee that equal keys have equal hashes, and a case-insensitive
> hash satisfies this requirement for both case-sensitive and
> case-insensitive equality.
>
> Overall it's quite debatable whether all of this complexity really is
> worth it, or whether we should just linearly search through all sources
> to find duplicates. But the mentioned commit cares about cases with
> thousands of alternates, and a linear search would of course regress
> performance quite a bit. This doesn't really feel like a reasonable case
> to care about though, but I don't feel comfortable regressing it anyway.
>
> Signed-off-by: Patrick Steinhardt <[email protected]>
> ---
>  odb.c        | 63 ++++++++++++++++++++++++++++++++++++++++--------------------
>  odb.h        | 15 ++++++++++++++-
>  odb/source.h |  7 +++++++
>  3 files changed, 63 insertions(+), 22 deletions(-)
>
> diff --git a/odb.c b/odb.c
> index bd02d8ad54..51da386f22 100644
> --- a/odb.c
> +++ b/odb.c
> @@ -2,11 +2,10 @@
>  #include "abspath.h"
>  #include "commit-graph.h"
>  #include "config.h"
> -#include "dir.h"
>  #include "environment.h"
>  #include "gettext.h"
> +#include "hashmap.h"
>  #include "hex.h"
> -#include "khash.h"
>  #include "lockfile.h"
>  #include "loose.h"
>  #include "midx.h"
> @@ -29,8 +28,32 @@
>  #include "trace2.h"
>  #include "write-or-die.h"
>
> -KHASH_INIT(odb_path_map, const char * /* key: odb_path */,
> -	struct odb_source *, 1, fspathhash, fspatheq)
> +static int odb_source_paths_cmp(struct object_database *o,
> +				const char *a, const char *b)
> +{
> +	if (o->source_paths_icase < 0) {
> +		int icase = 0;
> +		repo_config_get_bool(o->repo, "core.ignorecase", &icase);
> +		o->source_paths_icase = icase;
> +	}
> +

Nit: couldn't this be simplified to

if (o->source_paths_icase < 0)
   repo_config_get_bool(o->repo, "core.ignorecase", &o->source_paths_icase);

> +	return o->source_paths_icase ? strcasecmp(a, b) : strcmp(a, b);
> +}
> +
> +static int odb_source_by_path_cmp(const void *cb_data,
> +				  const struct hashmap_entry *entry,
> +				  const struct hashmap_entry *entry_or_key,
> +				  const void *keydata)
> +{
> +	struct object_database *o = (struct object_database *)cb_data;
> +	const struct odb_source *source = container_of(entry, const struct odb_source, by_path_entry);
> +	const char *path = keydata;
> +
> +	if (!path)
> +		path = container_of(entry_or_key, const struct odb_source, by_path_entry)->path;
> +
> +	return odb_source_paths_cmp(o, source->path, path);
> +}
>
>  int odb_mkstemp(struct object_database *odb,
>  		struct strbuf *temp_filename, const char *pattern)
> @@ -58,8 +81,8 @@ int odb_mkstemp(struct object_database *odb,
>   */
>  static bool odb_is_source_usable(struct object_database *o, const char *path)
>  {
> -	int r;
>  	struct strbuf normalized_objdir = STRBUF_INIT;
> +	struct hashmap_entry key;
>  	bool usable = false;
>
>  	strbuf_realpath(&normalized_objdir, o->sources->path, 1);
> @@ -76,20 +99,18 @@ static bool odb_is_source_usable(struct object_database *o, const char *path)
>  	 * Prevent the common mistake of listing the same
>  	 * thing twice, or object directory itself.
>  	 */
> -	if (!o->source_by_path) {
> -		khiter_t p;
> -
> -		o->source_by_path = kh_init_odb_path_map();
> +	if (!hashmap_get_size(&o->source_by_path)) {
>  		assert(!o->sources->next);
> -		p = kh_put_odb_path_map(o->source_by_path, o->sources->path, &r);
> -		assert(r == 1); /* never used */
> -		kh_value(o->source_by_path, p) = o->sources;
> +		hashmap_entry_init(&o->sources->by_path_entry,
> +				   strihash(o->sources->path));
> +		hashmap_add(&o->source_by_path, &o->sources->by_path_entry);
>  	}
>
> -	if (fspatheq(path, normalized_objdir.buf))
> +	if (!odb_source_paths_cmp(o, path, normalized_objdir.buf))
>  		goto out;
>
> -	if (kh_get_odb_path_map(o->source_by_path, path) < kh_end(o->source_by_path))
> +	hashmap_entry_init(&key, strihash(path));
> +	if (hashmap_get(&o->source_by_path, &key, path))
>  		goto out;
>
>  	usable = true;
> @@ -172,8 +193,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
>  {
>  	struct odb_source *alternate = NULL;
>  	struct strvec sources = STRVEC_INIT;
> -	khiter_t pos;
> -	int ret;
>
>  	if (!odb_is_source_usable(odb, source))
>  		goto error;
> @@ -184,10 +203,11 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
>  	*odb->sources_tail = alternate;
>  	odb->sources_tail = &(alternate->next);
>
> -	pos = kh_put_odb_path_map(odb->source_by_path, alternate->path, &ret);
> -	if (!ret)
> +	hashmap_entry_init(&alternate->by_path_entry, strihash(alternate->path));
> +	if (hashmap_get(&odb->source_by_path, &alternate->by_path_entry,
> +			alternate->path))
>  		BUG("source must not yet exist");
> -	kh_value(odb->source_by_path, pos) = alternate;
> +	hashmap_add(&odb->source_by_path, &alternate->by_path_entry);
>
>  	/* recursively add alternates */
>  	odb_source_read_alternates(alternate, &sources);
> @@ -1056,6 +1076,8 @@ struct object_database *odb_new(struct repository *repo,
>  	o->repo = repo;
>  	pthread_mutex_init(&o->replace_mutex, NULL);
>  	string_list_init_dup(&o->submodule_source_paths);
> +	hashmap_init(&o->source_by_path, odb_source_by_path_cmp, o, 0);
> +	o->source_paths_icase = -1;
>
>  	if (flags & ODB_NEW_HONOR_ENV) {
>  		primary_source = xstrdup_or_null(getenv(DB_ENVIRONMENT));
> @@ -1094,8 +1116,7 @@ static void odb_free_sources(struct object_database *o)
>  	odb_source_free(o->inmemory_objects);
>  	o->inmemory_objects = NULL;
>
> -	kh_destroy_odb_path_map(o->source_by_path);
> -	o->source_by_path = NULL;
> +	hashmap_clear(&o->source_by_path);
>  }
>
>  void odb_free(struct object_database *o)
> diff --git a/odb.h b/odb.h
> index 8eb4e85d64..71af7450a9 100644
> --- a/odb.h
> +++ b/odb.h
> @@ -1,6 +1,7 @@
>  #ifndef ODB_H
>  #define ODB_H
>
> +#include "hashmap.h"
>  #include "object.h"
>  #include "oidset.h"
>  #include "oidmap.h"
> @@ -54,7 +55,19 @@ struct object_database {
>  	 */
>  	struct odb_source *sources;
>  	struct odb_source **sources_tail;
> -	struct kh_odb_path_map *source_by_path;
> +
> +	/*
> +	 * Map of object database sources, keyed by their respective paths.
> +	 * This map is used to detect the case where the same source is
> +	 * registered multiple times.
> +	 */
> +	struct hashmap source_by_path;
> +
> +	/*
> +	 * Whether source paths shall be compared case-insensitively, as
> +	 * determined by "core.ignoreCase".
> +	 */
> +	int source_paths_icase;
>
>  	int loaded_alternates;
>
> diff --git a/odb/source.h b/odb/source.h
> index 4bc037b8d6..82cda8ad75 100644
> --- a/odb/source.h
> +++ b/odb/source.h
> @@ -1,6 +1,7 @@
>  #ifndef ODB_SOURCE_H
>  #define ODB_SOURCE_H
>
> +#include "hashmap.h"
>  #include "object.h"
>  #include "odb.h"
>  #include "odb/transaction.h"
> @@ -50,6 +51,12 @@ struct strvec;
>  struct odb_source {
>  	struct odb_source *next;
>
> +	/*
> +	 * Entry in the object database's map of sources, keyed by this
> +	 * source's path.
> +	 */
> +	struct hashmap_entry by_path_entry;
> +
>  	/* Object database that owns this object source. */
>  	struct object_database *odb;
>
>
> --
> 2.55.0.679.g6767b8d81c.dirty

Apart from the nit, this patch looks good.
signature.asc (application/pgp-signature, 690 B)
-----BEGIN PGP SIGNATURE-----

iQHKBAEBCgA0FiEEV85Mf2N1cQ/LZcYGPtWfJI5GjH8FAmp9t0kWHGthcnRoaWsu
MTg4QGdtYWlsLmNvbQAKCRA+1Z8kjkaMf9XDC/4q2SU9wnysE2igHtUUiMa+vyMO
vUNMq5h9KajgsxZ7BYvMjqkF8tLsojuFbTtqjEdKEddmTTLDUbbze6iLJzTE+Lr6
HUWZYQnasmofINKNBDUikfeGHkfwAmf5yvDygIqOqu8tCqaHwHUAze3uxQbllFQH
zyf9JB2XWAcndX2YF+x8gv3Zp1zGX2iof8+lptHtUitqpPOGG+bKlQl7AVEoVdAe
qorA2yg2r9Y/1muKxyQ+svvAnZykgMozBqbsSzgIB3BjZknOQLnivyBiRgZsVibl
lPJVQZmhQVBFfqsf0Tk9CV8ypK4Nm9Edx+ZCse+Zz2vZHNrzRaThSluUZVmk079y
Si97RcLznZ1ytdhUuGqLO0YOKgYDyP5ojSwQk2mdBvsgDHyTO9dnt0EQ+8j2DTp1
JjdpOpvTC64KC45OxNlSAE0V/x7i10yQbcWsnoSZnFROcV71AY6x73S0GvGJsXLa
XFHFgjo6+jaBW6TQXDp7pzXsPMUpjWc3ii2ct5c=
=PJsC
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.