Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ty7c7-002gWk-US for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Mar 2025 11:11:04 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1ty7c5-0085Up-MX for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Mar 2025 11:11:01 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ty7c5-0085Ug-1W for pgsql-hackers@lists.postgresql.org; Fri, 28 Mar 2025 11:11:01 +0000 Received: from mail-lf1-x135.google.com ([2a00:1450:4864:20::135]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1ty7c2-001buM-1U for pgsql-hackers@postgresql.org; Fri, 28 Mar 2025 11:11:00 +0000 Received: by mail-lf1-x135.google.com with SMTP id 2adb3069b0e04-54b09cb06b0so845127e87.1 for ; Fri, 28 Mar 2025 04:10:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1743160257; x=1743765057; darn=postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=MD8DhZ+QmZvoSNWLR5PVahbOCOEavwCnMcGGK5C0uJs=; b=csledskerUf9XCEupk6RpLmj3c0qhq2qbXBx54garlWQiQ51rLm5mGA2FZeTVs7VDJ 1unNwLUQtdcfDwdn63G6ce5GEpmMocYToI3UJl6qJmwx2ci/uXBQXxMKYVSwhiTGETZB SPe04Ni/WYDrJnFIFpC/msRsYZ0ioiRVN3qnilFK1mHEeoYAIfnDEmUPukiFIKVlU4zI FmKnNOPproCMlRSff4HzAsAqJwDXCP692JBDhfqzlFwSkEKfhPTBR17Z8k+RUm4nStLL /XecQT2o4r5H0UaYIqk2IpFyTJXeq5A+m7pfr+i+Y/yBKtoVzNNadpNK0GLKV1zIQMpM ZkOQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1743160257; x=1743765057; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=MD8DhZ+QmZvoSNWLR5PVahbOCOEavwCnMcGGK5C0uJs=; b=U6TSWFBSt30Von00sSwjbyWm3KKHUEQGSTWRf3OC3daZsj70P8vvErvmuz4pYM6m5X 2c8ffn+lgjaW912tH1L3vwI3Suc/ZXZfUowMLL2OycolG/XnAP122rvdXBbrRvbgoLI+ lqFLfqLUs25VpNQgx5TGCecgPerbOb5Sm7hNWJB6YK0gE+5VShaN+kPziggeAx+gNw0B zonL37UuEBmOQpJFwtQnX/sjJBLM4xWnYK0A6HwZyXCOIltrjXT6t6Gu7hX5QIdZNTqe uOyg192BeYB06eHFGa95VvtU+8dYjLx3k1lBCFmu+NLITbLr9MZkAkmVPER8xLrlyQRo GGiA== X-Forwarded-Encrypted: i=1; AJvYcCW9/MK5jkHQefoHMlIIO8stsdModb+bW2GLPnWH/9FNt8faJaGVOebMHXwdFtlAr1yc+8JiNXl4jJr7xkCt@postgresql.org X-Gm-Message-State: AOJu0Yz2guVnhWWN01iy8INLGnOdYtih+L0Xort1in629h591c3r6Zqn Fbo55g7UxA2xj2dbo2PD7oENemDe0oynGYyYs4G/9aiAfRHAraSPsoo9Tzm/qpsXF6BsikwWwDu NkKfrBExFfai+qO3foEV8nsD0ZV0= X-Gm-Gg: ASbGncvR6UucxcCsP01anvM22gn9JSOlM8CkHSsRqXzrwXA34fafb9ewGYflxsOVDl2 ry5onmnXzHYbnaLTrPQwFBoFFpg8RQf0Mo0MLQF8E9QxjGl2m4SZz61K91YoPie1J6JlT9Wzg4Y ec2V79bkVZzEmcgc7j1V7ZftJZI6cNKOtcKs2CSkttNgUVbcATy0M1f2Yrg26P X-Google-Smtp-Source: AGHT+IHvCuQqgyRvt2JHvZg+ROXNC+gKwoYX0vBAKKD6I9UmLxgSPW3KJN6JSBXIRHya51/ZP8UjqHwVAA1rO4J8BWE= X-Received: by 2002:a05:6512:b98:b0:545:f69:1d10 with SMTP id 2adb3069b0e04-54b011cda31mr2569051e87.8.1743160256349; Fri, 28 Mar 2025 04:10:56 -0700 (PDT) MIME-Version: 1.0 References: <3e40eeec-d8bf-4496-854e-485dd901f6a2@vondra.me> <6bf7194e-4c34-4e6d-8215-f6acf8903974@vondra.me> <6705dbd2-060b-4f3c-9fcb-1c7f10880b26@vondra.me> In-Reply-To: <6705dbd2-060b-4f3c-9fcb-1c7f10880b26@vondra.me> From: Rahila Syed Date: Fri, 28 Mar 2025 16:40:44 +0530 X-Gm-Features: AQ5f1JrHKRIgThbWdkvIpNwHUArmK7tC2AtzvnEYaujWhe32nFcu2LRdcKfeh0Y Message-ID: Subject: Re: Improve monitoring of shared memory allocations To: Tomas Vondra Cc: Nazir Bilal Yavuz , Andres Freund , PostgreSQL-development Content-Type: multipart/alternative; boundary="000000000000ddd0510631651f64" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000ddd0510631651f64 Content-Type: text/plain; charset="UTF-8" Hi Tomas, 1) alignment > > There was a comment with a question whether we need to MAXALIGN the > chunks in dynahash.c, which were originally allocated by ShmemAlloc, but > now it's part of one large allocation, which is then cut into pieces > (using pointer arithmetics). > > I was not sure whether we need to enforce some alignment, we briefly > discussed that off-list. I realize you chose to add the alignment, but I > haven't noticed any comment in the patch why it's needed, and it seems > to me it may not be quite correct. > I have added MAXALIGN to specific allocations, such as HASHHDR and HASHSEGMENT, with the expectation that allocations in multiples of this, like dsize * HASHSEGMENT, would automatically align. > Let me explain what I had in mind, and why I think the way v5 doesn't > actually do that. It took me a while before I understood what alignment > is about, and for a while it was haunting my patches, so hopefully this > will help others ... > > The "alignment" is about pointers (or addresses), and when a pointer is > aligned it means the address is a multiple of some number. For example > 4B-aligned pointer is a multiple of 4B, so 0x00000100 is 4B-aligned, > while 0x00000101 is not. Sometimes we use data types to express the > alignment, e.g. int-aligned is 4B-aligned, but that's a detail. AFAIK > the alignment is always 2^k, so 1, 2, 4, 8, ... > > The primary reason for alignment is that some architectures require the > pointers to be well-aligned for a given data type. For example (int*) > needs to be int-aligned. If you have a pointer that's not 4B-aligned, > it'll trigger SIGBUS or maybe SIGSEGV. This was true for architectures > like powerpc, I don't think x86/arm64 have this restriction, i.e. it'd > work, even if there might be a minor performance impact. Anyway, we > still enforce/expect correct alignment, because we may still support > some of those alignment-sensitive platforms, and it's "tidy". > > The other reason is that we sometimes use alignment to add padding, to > reduce contention when accessing elements in hot arrays. We want to > align to cacheline boundaries, so that a struct does not require > accessing more cachelines than really necessary. And also to reduce > contention - the more cachelines, the higher the risk of contention. > > Thank you for your explanation. I had a similar understanding. However, I believed that MAXALIGN and CACHEALIGN are primarily performance optimizations that do not impact the correctness of the code. This assumption is based on the fact that I have not observed any failures on GitHub CI, even when changing the alignment in this part of the code. Now, back to the patch. The code originally did this in ShmemInitStruct > > hashp = ShmemInitStruct(...) > > to allocate the hctl, and then > > firstElement = (HASHELEMENT *) ShmemAlloc(nelem * elementSize); > > in element_alloc(). But this means the "elements" allocation is aligned > to PG_CACHE_LINE_SIZE, i.e. 128B, because ShmemAllocRaw() does this: > > size = CACHELINEALIGN(size); > > So it distributes memory in multiples of 128B, and I believe it starts > at a multiple of 128B. > > But the patch reworks this to allocate everything at once, and thus it > won't get this alignment automatically. AFAIK that's not intentional, > because no one explicitly mentioned this. And it's may not be quite > desirable, judging by the comment in ShmemAllocRaw(). > > Yes, the patch reworks this to allocate all the shared memory at once. It uses ShmemInitStruct which internally calls ShmemAllocRaw. So the whole chunk of memory allocated is still CACHEALIGNed. I mentioned v5 adds alignment, but I think it does not quite do that > quite correctly. It adds alignment by changing the macros from: > > +#define HASH_ELEMENTS_OFFSET(hctl, nsegs) \ > + (sizeof(HASHHDR) + \ > + ((hctl)->dsize * sizeof(HASHSEGMENT)) + \ > + ((hctl)->ssize * (nsegs) * sizeof(HASHBUCKET))) > > to > > +#define HASH_ELEMENTS_OFFSET(hctl, nsegs) \ > + (MAXALIGN(sizeof(HASHHDR)) + \ > + ((hctl)->dsize * MAXALIGN(sizeof(HASHSEGMENT))) + \ > + ((hctl)->ssize * (nsegs) * MAXALIGN(sizeof(HASHBUCKET)))) > > First, it uses MAXALIGN, but that's mostly my fault, because my comment > suggested that - the ShmemAllocRaw however and makes the case for using > CACHELINEALIGN. > Good catch. For a shared hash table, allocations need to be CACHELINEALIGNED. I think hash_get_init_size does not need to call CACHELINEALIGNED explicitly as ShmemInitStruct already does this. In that case, the size returned by hash_get_init_size just needs to MAXALIGN required structs as per hash_create() requirements and CACHELINEALIGN will be taken care of in ShmemInitStruct at the time of allocating the entire chunk. > But more importantly, it adds alignment to all hctl field, and to every > element of those arrays. But that's not what the alignment was supposed > to do - it was supposed to align arrays, not individual elements. Not > only would this waste memory, it would actually break direct access to > those array elements. > I think existing code has occurrences of both i,.e aligning individual elements and arrays. A similar precedent exists in the function hash_estimate_size(), which only applies maxalignment to the individual structs like HASHHDR, HASHELEMENT, entrysize, but also an array of HASHBUCKET headers. I agree with you that perhaps we don't need maxalignment for all of these structures. For ex, HASHBUCKET is a pointer to a linked list of elements, it might not require alignment if the elements it points to are already aligned. > But there's another detail - even before this patch, most of the stuff > was allocated at once by ShmemInitStruct(). Everything except for the > elements, so to replicate the alignment we only need to worry about that > last part. So I think this should do: > > +#define HASH_ELEMENTS_OFFSET(hctl, nsegs) \ > + CACHELINEALIGN(sizeof(HASHHDR) + \ > + ((hctl)->dsize * sizeof(HASHSEGMENT)) + \ > + ((hctl)->ssize * (nsegs) * sizeof(HASHBUCKET))) > > This is what the 0003 patch does. There's still one minor difference, in > that we used to align each segment independently - each element_alloc() > call allocated a new CACHELINEALIGN-ed chunk, while now have just a > single chunk. But I think that's OK. > > Before this patch, following structures were allocated separately using ShmemAllocRaw directory, each segment(seg_alloc) and a chunk of elements (element_alloc). Hence, I don't understand why v-0003* CACHEALIGNs in the manner it does. I think if we want to emulate the current behaviour we should do something like: CACHELINEALIGN(sizeof(HASHHDR) + dsize * sizeof(HASHSEGMENT)) + + CACHELINEALIGN(sizeof(HASHBUCKET) * ssize) * nsegs + CACHELINEALIGN(init_size * elementSize); Like you mentioned the only difference would be that we would be aligning all elements at once instead of aligning individual partitions of elements. 3) I find the comment before hash_get_init_size a bit unclear/confusing. > It says this: > > * init_size should match the total number of elements allocated during > * hash table creation, it could be zero for non-shared hash tables > * depending on the value of nelem_alloc. For more explanation see > * comments within this function. > * > * nelem_alloc parameter is not relevant for shared hash tables. > > What does "should match" mean here? Doesn't it *determine* the number of > elements allocated? What if it doesn't match? > by should match I mean - init_size here *is* equal to nelem in hash_create() . > > AFAICS it means the hash table is sized to expect init_size elements, > but only nelem_alloc elements are actually pre-allocated, right? No. All the init_size elements are pre-allocated for shared hash table irrespective of nelem_alloc value. For non-shared hash tables init_size elements are allocated only if it is less than nelem_alloc, otherwise they are allocated as part of expansion. > But the > comment says it's init_size which determines the number of elements > allocated during creation. Confusing. > > It says "it could be zero ... depending on the value of nelem_alloc". > Depending how? What's the relationship. > > The relationship is defined in this comment: /* * For a shared hash table, preallocate the requested number of elements. * This reduces problems with run-time out-of-shared-memory conditions. * * For a non-shared hash table, preallocate the requested number of * elements if it's less than our chosen nelem_alloc. This avoids wasting * space if the caller correctly estimates a small table size. */ hash_create code is confusing because the nelem_alloc named variable is used in two different cases, In the above case nelem_alloc refers to the one returned by choose_nelem_alloc function. The other nelem_alloc determines the number of elements in each partition for a partitioned hash table. This is not what is being referred to in the above comment. The bit "For more explanation see comments within this function" is not > great, if only because there are not many comments within the function, > so there's no "more explanation". But if there's something important, it > should be in the main comment, preferably. > > I will improve the comment in the next version. Thank you, Rahila Syed --000000000000ddd0510631651f64 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi Tomas,


1) alignment

There was a comment with a question whether we need to MAXALIGN the
chunks in dynahash.c, which were originally allocated by ShmemAlloc, but now it's part of one large allocation, which is then cut into pieces (using pointer arithmetics).

I was not sure whether we need to enforce some alignment, we briefly
discussed that off-list. I realize you chose to add the alignment, but I haven't noticed any comment in the patch why it's needed, and it se= ems
to me it may not be quite correct.
=C2=A0
I have added MAXALIGN to specific allocations, such as HASHHDR and
HA= SHSEGMENT, with the expectation that allocations in multiples of this,
= like dsize * HASHSEGMENT, would automatically align.


=

Let me explain what I had in mind, and why I think the way v5 doesn't actually do that. It took me a while before I understood what alignment
is about, and for a while it was haunting my patches, so hopefully this
will help others ...

The "alignment" is about pointers (or addresses), and when a poin= ter is
aligned it means the address is a multiple of some number. For example
4B-aligned pointer is a multiple of 4B, so 0x00000100 is 4B-aligned,
while 0x00000101 is not. Sometimes we use data types to express the
alignment, e.g. int-aligned is 4B-aligned, but that's a detail. AFAIK the alignment is always 2^k, so 1, 2, 4, 8, ...

The primary reason for alignment is that some architectures require the
pointers to be well-aligned for a given data type. For example (int*)
needs to be int-aligned. If you have a pointer that's not 4B-aligned, it'll trigger SIGBUS or maybe SIGSEGV. This was true for architectures<= br> like powerpc, I don't think x86/arm64 have this restriction, i.e. it= 9;d
work, even if there might be a minor performance impact. Anyway, we
still enforce/expect correct alignment, because we may still support
some of those alignment-sensitive platforms, and it's "tidy".=

The other reason is that we sometimes use alignment to add padding, to
reduce contention when accessing elements in hot arrays. We want to
align to cacheline boundaries, so that a struct does not require
accessing more cachelines than really necessary. And also to reduce
contention - the more cachelines, the higher the risk of contention.

=C2=A0
Thank you for your explanation. I ha= d a similar understanding. However,
I believed that MAXALIGN and CACHEA= LIGN are primarily performance optimizations
that do not impact the corr= ectness of the code. This assumption is based on the fact
that I have no= t observed any failures on GitHub CI, even when changing the alignment
i= n this part of the code.


Now, back to the patch. The code originally did this in ShmemInitStruct

=C2=A0 =C2=A0 hashp =3D ShmemInitStruct(...)

to allocate the hctl, and then

=C2=A0 =C2=A0 firstElement =3D (HASHELEMENT *) ShmemAlloc(nelem * elementSi= ze);

in element_alloc(). But this means the "elements" allocation is a= ligned
to PG_CACHE_LINE_SIZE, i.e. 128B, because ShmemAllocRaw() does this:

=C2=A0 =C2=A0 size =3D CACHELINEALIGN(size);

So it distributes memory in multiples of 128B, and I believe it starts
at a multiple of 128B.

But the patch reworks this to allocate everything at once, and thus it
won't get this alignment automatically. AFAIK that's not intentiona= l,
because no one explicitly mentioned this. And it's may not be quite
desirable, judging by the comment in ShmemAllocRaw().


Yes, the patch reworks this to allocate all the s= hared memory at once.
It uses ShmemInitStruct which internally calls Shm= emAllocRaw. So the whole chunk
of memory allocated is still CACHEALIGNe= d.=C2=A0

I mentioned v5 adds alignment, but I think it does not quite do that
quite correctly. It adds alignment by changing the macros from:

+#define HASH_ELEMENTS_OFFSET(hctl, nsegs) \
+=C2=A0 =C2=A0 =C2=A0 =C2=A0(sizeof(HASHHDR) + \
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((hctl)->dsize * sizeof(HASHSEGMENT)) + \ +=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((hctl)->ssize * (nsegs) * sizeof(HASHBUCKE= T)))

to

+#define HASH_ELEMENTS_OFFSET(hctl, nsegs) \
+=C2=A0 =C2=A0 =C2=A0 =C2=A0(MAXALIGN(sizeof(HASHHDR)) + \
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((hctl)->dsize * MAXALIGN(sizeof(HASHSEGMEN= T))) + \
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((hctl)->ssize * (nsegs) * MAXALIGN(sizeof(= HASHBUCKET))))

First, it uses MAXALIGN, but that's mostly my fault, because my comment=
suggested that - the ShmemAllocRaw however and makes the case for using
CACHELINEALIGN.

Good catch. For a shared hash tabl= e, allocations need to be
CACHELINEALIGNED.=C2=A0=C2=A0
I think hash_= get_init_size does not need to call CACHELINEALIGNED
explicitly as Shme= mInitStruct already does this.
In that case, the size returned by hash_= get_init_size just needs to
MAXALIGN required structs as per hash_create= () requirements and CACHELINEALIGN
will be taken care of in ShmemInitStr= uct at the time of allocating the entire chunk.


But more importantly, it adds alignment to all hctl field, and to every
element of those arrays. But that's not what the alignment was supposed=
to do - it was supposed to align arrays, not individual elements. Not
only would this waste memory, it would actually break direct access to
those array elements.

I think existing code has oc= currences of both i,.e aligning individual elements and=C2=A0
arrays.A similar precedent exists in the function hash_estimate_size(), which onl= y
applies maxalignment to the individual structs like HASHHDR, HASHELEME= NT,
entrysize, but also an array of HASHBUCKET headers.=C2=A0

I agree with you that perhaps we don't need maxalignmen= t for all of these structures.
For ex, HASHBUCKET is a pointer to a lin= ked list of elements, it might not require alignment
if the elements it= points=C2=A0to are already aligned.


But there's another detail - even before this patch, most of the stuff<= br> was allocated at once by ShmemInitStruct(). Everything except for the
elements, so to replicate the alignment we only need to worry about that last part. So I think this should do:
=C2=A0
+#define HASH_ELEMENTS_OFFSET(hctl, nsegs) \
+=C2=A0 =C2=A0 CACHELINEALIGN(sizeof(HASHHDR) + \
+=C2=A0 =C2=A0 =C2=A0((hctl)->dsize * sizeof(HASHSEGMENT)) + \
+=C2=A0 =C2=A0 =C2=A0((hctl)->ssize * (nsegs) * sizeof(HASHBUCKET)))

This is what the 0003 patch does. There's still one minor difference, i= n
that we used to align each segment independently - each element_alloc()
call allocated a new CACHELINEALIGN-ed chunk, while now have just a
single chunk. But I think that's OK.

=C2=A0
Before this patch, following structu= res were allocated separately using ShmemAllocRaw
directory, = each segment(seg_alloc) and a chunk of elements (element_alloc). Hence,=C2= =A0
I don't understand why v-0003* CACHEALIGNs=C2=A0 in the m= anner it does.

I think if we want to emulate the current behaviour w= e should do something like:
CACHELINEALIGN(sizeof(HASHHDR) + dsize * siz= eof(HASHSEGMENT)) +
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 + CACHELINEALIGN(sizeof(HASHBUCKET) * ssize) * nsegs
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 + CACHELINEALIGN(init_size * elem= entSize);

Like you mentioned the only difference would be that we wo= uld be aligning all elements
at once instead of aligning individual part= itions of elements.


3) I find the comment before hash_get_init_size a bit unclear/confusing. It says this:

=C2=A0* init_size should match the total number of elements allocated durin= g
=C2=A0* hash table creation, it could be zero for non-shared hash tables =C2=A0* depending on the value of nelem_alloc. For more explanation see
=C2=A0* comments within this function.
=C2=A0*
=C2=A0* nelem_alloc parameter is not relevant for shared hash tables.

What does "should match" mean here? Doesn't it *determine* th= e number of
elements allocated? What if it doesn't match?
=C2= =A0
by should match I mean - init_size=C2=A0 here=C2=A0 *is* equa= l to nelem in hash_create() .

AFAICS it means the hash table is sized to expect init_size elements,
but only nelem_alloc elements are actually pre-allocated, right?
=C2=A0
No. All the init_size elements are pre-allocated fo= r shared hash table irrespective of=C2=A0
nelem_alloc value.
For non-= shared hash tables init_size elements are allocated only
if it is less t= han nelem_alloc, otherwise they are allocated as part of expansion.
=C2= =A0
But the
comment says it's init_size which determines the number of elements
allocated during creation. Confusing.

It says "it could be zero ... depending on the value of nelem_alloc&qu= ot;.
Depending how? What's the relationship.

=C2=A0
The relationship is defined in this = comment:
=C2=A0/*
* For a shared hash table, preallocate the= requested number of elements.
* This reduces problems with run-time o= ut-of-shared-memory conditions.
*
* For a non-shared hash table, = preallocate the requested number of
* elements if it's less than o= ur chosen nelem_alloc.=C2=A0 This avoids wasting
* space if the caller= correctly estimates a small table size.
*/

hash_create code is= confusing because the nelem_alloc named variable is used
in two differe= nt cases, In=C2=A0 the above case=C2=A0 nelem_alloc=C2=A0 refers to the one= =C2=A0
returned by choose_nelem_alloc function.

The other nelem_a= lloc determines the number of elements in each partition
for a partition= ed=C2=A0hash table. This is not what is being referred to in the above=C2= =A0
comment.

The bit "For more explanation see comments within this function" = is not
great, if only because there are not many comments within the function,
so there's no "more explanation". But if there's somethin= g important, it
should be in the main comment, preferably.

=C2=A0
I will improve the comment in the ne= xt version.

Thank you,
Rahila Syed
--000000000000ddd0510631651f64--