Skip to content

Commit

Permalink
Feature/concurrent node containers (#271)
Browse files Browse the repository at this point in the history
* added concurrent node containers

* removed spurious typename

* added missing includes

* avoided unused param warning

* worked around Clang bug

* s/{}/() to work around GCC4.8 problems with aggregate initialization

* used /bigobj for cfoa/visit_tests.cpp

* suppressed localized maybe-uninitialized warnings

* fixed comments

* added /bigobj to cfoa/insert_tests.cpp

* instrumented double exact comparison to spot a spurious error

* fixed pedantic error

* refactored byte_span machinery

* compromised on sub-epsilon equality for doubles that should be identical

* documented boost::concurrent_node_(map|set)

* added concurrent_node_set

* added missing AlternativeType

* tested empty node insertion

* tested node_handle allocator management

* added nonassignable_allocator and node_handle_allocator_swap_tests

* fixed warning disabling

* silenced spurious GCC warning

* broadened scope of previous pragma

* broadened even more

* worked around spurious constexpr-related msvc-14.0 bug
https://godbolt.org/z/v78545Ebf

* added workaround back

* replaced previous workaround with built-in one

* added workaround back on top of built-in solution (which doesn't work 100% of the time)
  • Loading branch information
joaquintides authored Aug 25, 2024
1 parent 35bdabf commit f734e39
Show file tree
Hide file tree
Showing 58 changed files with 7,006 additions and 337 deletions.
2 changes: 1 addition & 1 deletion doc/unordered/buckets.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ h|*Method* h|*Description*

|`float max_load_factor(float z)`
|Changes the container's maximum load factor, using `z` as a hint. +
**Open-addressing containers:** this function does nothing: users are not allowed to change the maximum load factor.
**Open-addressing and concurrent containers:** this function does nothing: users are not allowed to change the maximum load factor.

|`void rehash(size_type n)`
|Changes the number of buckets so that there at least `n` buckets, and so that the load factor is less than the maximum load factor.
Expand Down
3 changes: 2 additions & 1 deletion doc/unordered/changes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
:github-pr-url: https://github.com/boostorg/unordered/pull
:cpp: C++

== Release 1.87.0
== Release 1.87.0 - Major update

* Added concurrent, node-based containers `boost::concurrent_node_map` and `boost::concurrent_node_set`.
* Made visitation exclusive-locked within certain
`boost::concurrent_flat_set` operations to allow for safe mutable modification of elements
({github-pr-url}/265[PR#265^]).
Expand Down
8 changes: 5 additions & 3 deletions doc/unordered/compliance.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ The main differences with C++ unordered associative containers are:
== Concurrent Containers

There is currently no specification in the C++ standard for this or any other type of concurrent
data structure. The APIs of `boost::concurrent_flat_set` and `boost::concurrent_flat_map`
data structure. The APIs of `boost::concurrent_flat_set`/`boost::concurrent_node_set` and
`boost::concurrent_flat_map`/`boost::concurrent_node_map`
are modelled after `std::unordered_flat_set` and `std::unordered_flat_map`, respectively,
with the crucial difference that iterators are not provided
due to their inherent problems in concurrent scenarios (high contention, prone to deadlocking):
Expand All @@ -105,7 +106,7 @@ In a non-concurrent unordered container, iterators serve two main purposes:
* Access to an element previously located via lookup.
* Container traversal.

In place of iterators, `boost::concurrent_flat_set` and `boost::concurrent_flat_map` use _internal visitation_
In place of iterators, Boost.Unordered concurrent containers use _internal visitation_
facilities as a thread-safe substitute. Classical operations returning an iterator to an
element already existing in the container, like for instance:

Expand Down Expand Up @@ -141,7 +142,8 @@ respectively, here visitation is granted mutable or const access depending on
the constness of the member function used (there are also `*cvisit` overloads for
explicit const visitation); In the case of `boost::concurrent_flat_set`, visitation is always const.

One notable operation not provided by `boost::concurrent_flat_map` is `operator[]`/`at`, which can be
One notable operation not provided by `boost::concurrent_flat_map`/`boost::concurrent_node_map`
is `operator[]`/`at`, which can be
replaced, if in a more convoluted manner, by
xref:#concurrent_flat_map_try_emplace_or_cvisit[`try_emplace_or_visit`].

Expand Down
39 changes: 29 additions & 10 deletions doc/unordered/concurrent.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

:idprefix: concurrent_

Boost.Unordered provides `boost::concurrent_flat_set` and `boost::concurrent_flat_map`,
Boost.Unordered provides `boost::concurrent_node_set`, `boost::concurrent_node_map`,
`boost::concurrent_flat_set` and `boost::concurrent_flat_map`,
hash tables that allow concurrent write/read access from
different threads without having to implement any synchronzation mechanism on the user's side.

Expand Down Expand Up @@ -43,7 +44,7 @@ logical cores in the CPU).

== Visitation-based API

The first thing a new user of `boost::concurrent_flat_set` or `boost::concurrent_flat_map`
The first thing a new user of Boost.Unordered concurrent containers
will notice is that these classes _do not provide iterators_ (which makes them technically
not https://en.cppreference.com/w/cpp/named_req/Container[Containers^]
in the C++ standard sense). The reason for this is that iterators are inherently
Expand All @@ -62,7 +63,7 @@ thread issues an `m.erase(k)` operation between A and B. There are designs that
can remedy this by making iterators lock the element they point to, but this
approach lends itself to high contention and can easily produce deadlocks in a program.
`operator[]` has similar concurrency issues, and is not provided by
`boost::concurrent_flat_map` either. Instead, element access is done through
`boost::concurrent_flat_map`/`boost::concurrent_node_map` either. Instead, element access is done through
so-called _visitation functions_:

[source,c++]
Expand Down Expand Up @@ -112,7 +113,7 @@ if (found) {
}
----

Visitation is prominent in the API provided by `boost::concurrent_flat_set` and `boost::concurrent_flat_map`, and
Visitation is prominent in the API provided by concurrent containers, and
many classical operations have visitation-enabled variations:

[source,c++]
Expand All @@ -125,13 +126,15 @@ m.insert_or_visit(x, [](auto& y) {
----

Note that in this last example the visitation function could actually _modify_
the element: as a general rule, operations on a `boost::concurrent_flat_map` `m`
the element: as a general rule, operations on a concurrent map `m`
will grant visitation functions const/non-const access to the element depending on whether
`m` is const/non-const. Const access can be always be explicitly requested
by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result
in higher parallelization. For `boost::concurrent_flat_set`, on the other hand,
in higher parallelization. For concurrent sets, on the other hand,
visitation is always const access.
Consult the references of
xref:#concurrent_node_set[`boost::concurrent_node_set`],
xref:#concurrent_flat_map[`boost::concurrent_node_map`],
xref:#concurrent_flat_set[`boost::concurrent_flat_set`] and
xref:#concurrent_flat_map[`boost::concurrent_flat_map`]
for the complete list of visitation-enabled operations.
Expand Down Expand Up @@ -245,7 +248,7 @@ may yield worse performance.

== Blocking Operations

``boost::concurrent_flat_set``s and ``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any
Concurrent containers can be copied, assigned, cleared and merged just like any other
Boost.Unordered container. Unlike most other operations, these are _blocking_,
that is, all other threads are prevented from accesing the tables involved while a copy, assignment,
clear or merge operation is in progress. Blocking is taken care of automatically by the library
Expand All @@ -258,9 +261,25 @@ reserving space in advance of bulk insertions will generally speed up the proces
== Interoperability with non-concurrent containers

As open-addressing and concurrent containers are based on the same internal data structure,
`boost::unordered_flat_set` and `boost::unordered_flat_map` can
be efficiently move-constructed from `boost::concurrent_flat_set` and `boost::concurrent_flat_map`,
respectively, and vice versa.
they can be efficiently move-constructed from their non-concurrent counterpart, and vice versa.

[caption=, title='Table {counter:table-counter}. Concurrent/non-concurrent interoperatibility']
[cols="1,1", frame=all, grid=all]
|===
^|`boost::concurrent_node_set`
^|`boost::unordered_node_set`

^|`boost::concurrent_node_map`
^|`boost::unordered_node_map`

^|`boost::concurrent_flat_set`
^|`boost::unordered_flat_set`

^|`boost::concurrent_flat_map`
^|`boost::unordered_flat_map`

|===

This interoperability comes handy in multistage scenarios where parts of the data processing happen
in parallel whereas other steps are non-concurrent (or non-modifying). In the following example,
we want to construct a histogram from a huge input vector of words:
Expand Down
Loading

0 comments on commit f734e39

Please sign in to comment.