Skip to content

Commit ce9f06d

Browse files
committed
New issue from Matt Bentley: "hive operations involving insertion/erasure should have 𝒪(log n) time complexity"
1 parent e6bbd2f commit ce9f06d

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed

xml/issue4320.xml

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
<?xml version='1.0' encoding='utf-8' standalone='no'?>
2+
<!DOCTYPE issue SYSTEM "lwg-issue.dtd">
3+
4+
<issue num="4320" status="New">
5+
<title>`hive` operations involving insertion/erasure should have <tt>&#x1d4aa;(log n)</tt> time complexity</title>
6+
<section>
7+
<sref ref="[hive]"/>
8+
</section>
9+
<submitter>Matt Bentley</submitter>
10+
<date>19 Aug 2025</date>
11+
<priority>99</priority>
12+
13+
<discussion>
14+
<p>
15+
Under <sref ref="[hive.modifiers]"/> p4 complexity is stated as "Constant.
16+
Exactly one object of type `T` is constructed."
17+
<p/>
18+
However the approach to implementation necessary to support 8/16-bit
19+
types without artificially widening the type storage, as described under
20+
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0447r28.html#non_reference_implementations_info">
21+
"Additional info for supporting small types" in P0447</a> basically specifies
22+
time complexity which is `Log(n)` in the capacity of the element block selected
23+
to insert into (when erased element memory locations are available for reuse) but
24+
imposes a small maximum block capacity cap. This time complexity only occurs
25+
during the operation to find an erased element memory location within a block
26+
which is known to have one.
27+
<p/>
28+
This is both the simplest and fastest solution to supporting small types
29+
in hive without artificial widening that I have come across.
30+
<p/>
31+
Further, I have discovered that this approach can be extended to larger
32+
block capacities via
33+
<a href="https://plflib.org/matt_bentley_-_bitset_stacking.pdf">"bitset stacking"</a>
34+
while retaining <tt>&#x1d4aa;(log n)</tt> intra-block lookup time complexity,
35+
regardless of block capacity. Overall this approach would be useful for embedded
36+
and other memory-scarse platforms as it reduces the 16-bit-per-element cost of the
37+
reference implementation down to a 1-bit-per-element cost. For 64-bit and larger types,
38+
there are other ways to obtain this reduction without
39+
losing <tt>&#x1d4aa;(1)</tt> lookup but it is unclear whether those methods would
40+
in fact be faster.
41+
<p/>
42+
Regardless, it is necessary for small types. In all cases <tt>N</tt> is capped by
43+
the maximum block capacity defined in the implementation.
44+
<p/>
45+
There is ambiguity as to whether this should result in a change to <tt>hive::insert/emplace</tt>
46+
time complexity when discussed on the reflector, as it is unrelated to element numbers (unless
47+
all elements fit within one block), but it is related to block capacities, which are defined
48+
as part of the `hive` technical specification.
49+
<p/>
50+
The exact mechanism by which erased element locations are recorded (for later reuse
51+
<sref ref="[hive.overview]"/> p3) in `hive` is not specified in the technical
52+
specification. The issue is therefore in some ways similar to `deque` `insert/emplace`
53+
time complexity, where a `push_back` may in fact result in a <tt>&#x1d4aa;(n)</tt>
54+
operation in the number of blocks &mdash; but because we do not specify the storage
55+
mechanism in `deque`, we ignore the fact that deques are typically constructed as
56+
vectors of pointers to blocks, and therefore any insert may trigger vector expansion.
57+
This was also the reasoning discussed for `hive` during LWG talks, since it can also
58+
be implemented as a vector of pointers to blocks.
59+
<p/>
60+
In addition, if we were to support approaches involving bitset stacking, this would
61+
also affect functions which can erase elements, since the number of modifications to
62+
the bitset-stack of a given block would increase by 1 with every 64x increase (assuming
63+
64-bit words) in block capacity.
64+
</p>
65+
<blockquote class="note">
66+
<p>
67+
I do not personally believe any wording change are necessary here, based on
68+
precedents set by the existing standard and what has been conveyed to me
69+
in the past regarding this topic (<tt>&#x1d4aa;</tt> complexity within blocks).
70+
However reflector discussion on the subject has not been entirely conclusive
71+
since the relevance of some time complexity involves a degree of subjectivity.
72+
This issue has therefore been submitted in order for a definitive conclusion
73+
to be reached on the issue.
74+
</p>
75+
</blockquote>
76+
<p>
77+
<b>Changes necessary:</b>
78+
<p/>
79+
Making this change would affect any functions which may reuse existing
80+
erased-element memory locations. This includes:
81+
<p/>
82+
<sref ref="[hive.modifiers]"/>:<br/>
83+
(`emplace`) p4<br/>
84+
(range `insert`) p9<br/>
85+
(fill `insert`) p13
86+
<p/>
87+
<sref ref="[hive.capacity]"/>:<br/>
88+
(`shrink_to_fit`) p10<br/>
89+
(`reshape`) p24
90+
<p/>
91+
(both of the above functions may potentially relocate existing elements
92+
from one block to erased element locations in another)
93+
<p/>
94+
<sref ref="[hive.cons]"/>:<br/>
95+
(<tt>operator= &amp;</tt>) p27<br/>
96+
(<tt>operator= &amp;&amp;</tt>) p31
97+
<p/>
98+
(move assignment will switch to moving individual elements where
99+
allocators are not equal - and in the case of non-trivial/allocating
100+
types, move-assigning to existing elements in the source may be beneficial)
101+
<p/>
102+
In addition, if we were to support bitset-stacking approaches these also
103+
effect functions which erase individual elements. This includes:
104+
<p/>
105+
<sref ref="[hive.modifiers]"/>:<br/>
106+
(`erase`) p16
107+
<p/>
108+
<sref ref="[hive.operations]"/>:<br/>
109+
(`unique`) p11
110+
</p>
111+
</discussion>
112+
113+
<resolution>
114+
<p>
115+
This wording is relative to <paper num="N5014"/>.
116+
</p>
117+
118+
<blockquote class="note">
119+
<p>
120+
[<i>Drafting note:</i> I am unclear on whether `assign()` and `assign_range()` operations
121+
would require specification since they also have the capability to reuse existing erased
122+
element memory spaces, but we do not currently supply time complexity wording for these
123+
in the standard in general and I'm unsure why that is.]
124+
</p>
125+
</blockquote>
126+
127+
<ol>
128+
129+
<li><p>Modify <sref ref="[hive.overview]"/> as indicated:</p>
130+
131+
<blockquote>
132+
<p>
133+
-1- A `hive` is a type of sequence container <del>that provides constant-time insertion and erasure
134+
operations. S</del><ins>where s</ins>torage is automatically managed in multiple memory blocks, referred
135+
to as element blocks. Insertion position is determined by the container, and <del>insertion</del>
136+
may re-use the memory locations of erased elements. <ins>Insertions are either constant time
137+
or logarithmic in the capacity of the element block inserted into.</ins>
138+
<p/>
139+
-2- [&hellip;]
140+
<p/>
141+
-3- Erasures use unspecified techniques <del>of constant time complexity</del> to identify the memory
142+
locations of erased elements, which are subsequently skipped during iteration <ins>in constant time</ins>,
143+
as opposed to relocating subsequent elements during erasure. <ins>These techniques are either constant time
144+
or logarithmic in the capacity of the element block erased from.</ins>
145+
</p>
146+
</blockquote>
147+
148+
</li>
149+
150+
<li><p>Modify <sref ref="[hive.cons]"/> as indicated:</p>
151+
152+
<blockquote>
153+
<pre>
154+
hive&amp; operator=(const hive&amp; x);
155+
</pre>
156+
<blockquote>
157+
<p>
158+
-25- <i>Preconditions</i>: [&hellip;]
159+
<p/>
160+
-26- <i>Effects</i>: [&hellip;]
161+
<p/>
162+
-27- <i>Complexity</i>: Linear in `size() + x.size()`. <ins>Additionally at worst
163+
<tt>&#x1d4aa;(log n)</tt> in the capacity of each element block which an element
164+
is constructed within.</ins>
165+
</p>
166+
</blockquote>
167+
<pre>
168+
hive&amp; operator=(hive&amp;&amp; x)
169+
noexcept(allocator_traits&lt;Allocator&gt;::propagate_on_container_move_assignment::value ||
170+
allocator_traits&lt;Allocator&gt;::is_always_equal::value);
171+
</pre>
172+
<blockquote>
173+
<p>
174+
-28- <i>Preconditions</i>: [&hellip;]
175+
<p/>
176+
-29- <i>Effects</i>: [&hellip;]
177+
<p/>
178+
-30- <i>Postconditions</i>: [&hellip;]
179+
<p/>
180+
-31- <i>Complexity</i>: Linear in `size()`. If
181+
</p>
182+
<blockquote><pre>
183+
(allocator_traits&lt;Allocator&gt;::propagate_on_container_move_assignment::value ||
184+
get_allocator() == x.get_allocator())
185+
</pre></blockquote>
186+
<p>
187+
is `false`, also linear in `x.size()` <ins>and additionally at worst
188+
<tt>&#x1d4aa;(log n)</tt> in the capacity of each element block which
189+
an element is constructed within</ins>.
190+
</p>
191+
</blockquote>
192+
</blockquote>
193+
194+
</li>
195+
196+
<li><p>Modify <sref ref="[hive.capacity]"/> as indicated:</p>
197+
198+
<blockquote>
199+
<pre>
200+
void shrink_to_fit();
201+
</pre>
202+
<blockquote>
203+
<p>
204+
-8- <i>Preconditions</i>: [&hellip;]
205+
<p/>
206+
-9- <i>Effects</i>: [&hellip;]
207+
<p/>
208+
<p/>
209+
-10- <i>Complexity</i>: If reallocation happens, linear in the size of the sequence
210+
<ins>and at worst <tt>&#x1d4aa;(log n)</tt> in the capacity of each element block which
211+
elements are reallocated into</ins>.
212+
<p/>
213+
-11- <i>Remarks</i>: [&hellip;]
214+
</p>
215+
</blockquote>
216+
<p>
217+
[&hellip;]
218+
</p>
219+
<pre>
220+
void reshape(hive_limits block_limits);
221+
</pre>
222+
<blockquote>
223+
<p>
224+
-21- <i>Preconditions</i>: [&hellip;]
225+
<p/>
226+
-22- <i>Effects</i>: [&hellip;]
227+
<p/>
228+
-23- <i>Postconditions</i>: [&hellip;]
229+
<p/>
230+
-24- <i>Complexity</i>: Linear in the number of element blocks in `*this`. If reallocation happens,
231+
also linear in the number of elements reallocated <ins>and at worst <tt>&#x1d4aa;(log n)</tt> in
232+
the capacity of each element block which elements are reallocated into</ins>.
233+
<p/>
234+
-25- <i>Remarks</i>: [&hellip;]
235+
</p>
236+
</blockquote>
237+
</blockquote>
238+
239+
</li>
240+
241+
<li><p>Modify <sref ref="[hive.modifiers]"/> as indicated:</p>
242+
243+
<blockquote>
244+
<pre>
245+
template&lt;class... Args&gt; iterator emplace(Args&amp;&amp;... args);
246+
template&lt;class... Args&gt; iterator emplace_hint(const_iterator hint, Args&amp;&amp;... args);
247+
</pre>
248+
<blockquote>
249+
<p>
250+
-1- <i>Preconditions</i>: [&hellip;]
251+
<p/>
252+
-2- <i>Effects</i>: [&hellip;]
253+
<p/>
254+
-3- <i>Returns</i>: [&hellip;]
255+
<p/>
256+
-4- <i>Complexity</i>: <del>Constant</del><ins>At worst <tt>&#x1d4aa;(log n)</tt> in the capacity
257+
of the element block which the element is constructed within</ins>. Exactly one object of type `T` is constructed.
258+
</p>
259+
</blockquote>
260+
<p>
261+
[&hellip;]
262+
</p>
263+
<pre>
264+
void insert(initializer_list&lt;T&gt; rg);
265+
template&lt;<i>container-compatible-range</i>&lt;T&gt; R&gt;
266+
void insert_range(R&amp;&amp; rg);
267+
</pre>
268+
<blockquote>
269+
<p>
270+
-7- <i>Preconditions</i>: [&hellip;]
271+
<p/>
272+
-8- <i>Effects</i>: [&hellip;]
273+
<p/>
274+
<p/>
275+
-9- <i>Complexity</i>: Linear in the number of elements inserted. <ins>Additionally at
276+
worst <tt>&#x1d4aa;(log n)</tt> in the capacity of each element block which an element is
277+
constructed within.</ins> Exactly one object of type `T` is constructed for each element
278+
inserted.
279+
<p/>
280+
-10- <i>Remarks</i>: [&hellip;]
281+
</p>
282+
</blockquote>
283+
<pre>
284+
void insert(size_type n, const T&amp; x);
285+
</pre>
286+
<blockquote>
287+
<p>
288+
-11- <i>Preconditions</i>: [&hellip;]
289+
<p/>
290+
-12- <i>Effects</i>: [&hellip;]
291+
<p/>
292+
<p/>
293+
-13- <i>Complexity</i>: Linear in `n`. <ins>Additionally at worst <tt>&#x1d4aa;(log n)</tt>
294+
in the capacity of each element block which an element is constructed within.</ins>.
295+
Exactly one object of type `T` is constructed for each element inserted.
296+
<p/>
297+
-14- <i>Remarks</i>: [&hellip;]
298+
</p>
299+
</blockquote>
300+
<p>
301+
[&hellip;]
302+
</p>
303+
<pre>
304+
iterator erase(const_iterator position);
305+
iterator erase(const_iterator first, const_iterator last);
306+
</pre>
307+
<blockquote>
308+
<p>
309+
-16- Complexity: Linear in the number of elements erased. Additionally, if any active blocks
310+
become empty of elements as a result of the function call, at worst linear in the number of
311+
element blocks. <ins>For any active blocks which do not become empty of elements as a result
312+
of the function call, at worst <tt>&#x1d4aa;(log n)</tt> in the capacity of each block
313+
which has an element erased from it.</ins>
314+
</p>
315+
</blockquote>
316+
</blockquote>
317+
318+
</li>
319+
320+
<li><p>Modify <sref ref="[hive.operations]"/> as indicated:</p>
321+
322+
<blockquote>
323+
<pre>
324+
template&lt;class BinaryPredicate = equal_to&lt;T&gt;&gt;
325+
size_type unique(BinaryPredicate binary_pred = BinaryPredicate());
326+
</pre>
327+
<blockquote>
328+
<p>
329+
-7- <i>Preconditions</i>: [&hellip;]
330+
<p/>
331+
-8- <i>Effects</i>: [&hellip;]
332+
<p/>
333+
-9- <i>Returns</i>: [&hellip;]
334+
<p/>
335+
-10- <i>Throws</i>: [&hellip;]
336+
<p/>
337+
-11- <i>Complexity</i>: If `empty()` is `false`, exactly `size() - 1` applications
338+
of the corresponding predicate, otherwise no applications of the predicate.
339+
<ins>If erasures occur, also linear in the number of elements erased.
340+
Additionally, if any active blocks become empty of elements as a result
341+
of the function call, at worst linear in the number of element blocks.
342+
For any active blocks which do not become empty of elements as a result
343+
of the function call, at worst <tt>&#x1d4aa;(log n)</tt> in the capacity of
344+
each block which has an element erased from it.</ins>
345+
<p/>
346+
-12- <i>Remarks</i>: [&hellip;]
347+
</p>
348+
</blockquote>
349+
</blockquote>
350+
351+
</li>
352+
353+
</ol>
354+
</resolution>
355+
356+
</issue>

0 commit comments

Comments
 (0)