Compare commits

..
41 Commits
Author SHA1 Message Date
Alexandra Epanchinzeva 4161e48d2b To fix CI 2021-09-29 11:09:21 +03:00
Alexandra Epanchinzeva a148e98d83 Clean-up tasks 2021-09-27 14:07:02 +03:00
Sergey Zheltov 43169c243f CMake: fix compile options for Windows ARM64 (#579)
Signed-off-by: Zheltov, Sergey1 <sergey1.zheltov@intel.com>
2021-09-16 19:18:49 +03:00
Alexandra 5d7abed881 Update index.rst (#578) 2021-09-15 12:46:54 +03:00
Anton PotapovandAlexandra f580ec8edb DOC: Migration guide: Deferred task creation (#537)
- added Deferred task creation chapter

Co-authored-by: Alexandra <alexandra.epanchinzeva@intel.com>

Signed-off-by: Anton Potapov <anton.potapov@intel.com>
2021-09-14 21:53:25 +03:00
Anton Potapov c4a120705f [DOC]: Update Get Started Guide (#575)
- added code sample
2021-09-14 18:33:42 +03:00
Alexandra 3b1bc31f45 [DOC]: Update Get Started Guide (#564) 2021-09-14 16:55:00 +03:00
e8037363ac DOC: Migration guide chapter for Scheduler Bypass (#525)
* DOC: Migration guide chapter for Scheduler Bypass

Signed-off-by: Anton Potapov <anton.potapov@intel.com>

* DOC: Migration guide chapter for Scheduler Bypass

Minor code cleanups for code samples

Co-authored-by: Pavel <pavel.kumbrasev@intel.com>
Signed-off-by: Anton Potapov <anton.potapov@intel.com>

* DOC: Migration guide chapter for Scheduler Bypass

- fixed wording

Co-authored-by: Alex <alexei.katranov@intel.com>
Co-authored-by: Alexandra <alexandra.epanchinzeva@intel.com>

* DOC: Migration guide chapter for Scheduler Bypass

- fixed wording

* DOC: Migration guide chapter for Scheduler Bypass

- fixed code sample
- add sentence about what defer is doing

Signed-off-by: Anton Potapov <anton.potapov@intel.com>

* DOC: Migration guide chapter for Scheduler Bypass

- fixed wording

Co-authored-by: Alexandra <alexandra.epanchinzeva@intel.com>

Co-authored-by: Pavel <pavel.kumbrasev@intel.com>
Co-authored-by: Alex <alexei.katranov@intel.com>
Co-authored-by: Alexandra <alexandra.epanchinzeva@intel.com>
2021-09-14 15:57:16 +03:00
Alexandra 0a57bda871 Update index.rst (#572) 2021-09-14 11:08:07 +03:00
Ekaterina Mekhnetsova 4624e4ff59 Remove old tms configurations (#571)
* Remove old tms configurations

* Update custom.js
2021-09-10 13:50:24 +03:00
Ilya Mishin 7567de93ae Workaround gcc bug in test_limiter_node (#569)
* Workaround gcc bug in test_limiter_node

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* unify workaround

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>
2021-09-07 18:29:59 +03:00
Alex 97ac8f4324 Disable RTM based tests under TSAN (#567)
* Disable RTM based tests (speculative mutexes) under TSAN

Signed-off-by: Alexei Katranov <alexei.katranov@intel.com>
2021-09-07 11:26:55 +03:00
PavelandAlex 40a9a10600 Fix memory semantics in queuing_rw_mutex (#552)
Signed-off-by: pavelkumbrasev <pavel.kumbrasev@intel.com>

Co-authored-by: Alex <alexei.katranov@intel.com>
2021-09-03 17:05:11 +03:00
Alexey Oralov 5d8ddb3fb6 Add PR template (#554)
Signed-off-by: Alexey Oralov alexey.oralov@intel.com
2021-09-03 09:25:41 +03:00
vlserovandpavelkumbrasev 4d3f9752d0 Performance: Avoid lock during working with multiple arenas (#562)
Synchronization order of calls function market::adjust_demand should
be made for arena, not for the market itself.

Signed-off-by: Serov, Vladimir <vladimir.serov@intel.com>
Co-authored-by: pavelkumbrasev <pavel.kumbrasev@intel.com>
2021-09-02 15:22:24 +03:00
Ekaterina Mekhnetsova 0a2b398702 Add analytics script for documentation (#561)
* Add analytics script for doc

* Update layout.html
2021-09-02 13:45:25 +03:00
Alexey Veprev 497a2b1695 Update version to 2021.5 (#559)
Signed-off-by: Veprev, Alexey <alexey.veprev@intel.com>
2021-09-01 17:07:44 +03:00
Pavel 86fe3f04c1 Fix arena layout (#557)
Signed-off-by: pavelkumbrasev <pavel.kumbrasev@intel.com>
2021-08-31 15:03:40 +03:00
Anton PotapovandAlex dd8f8a7826 task_group extensions run_and_wait(task_handle&&) method (#538)
* task_group extensions:

- added task_group::run_and_wait(task_handle&&) method

Signed-off-by: Anton Potapov <anton.potapov@intel.com>
Co-authored-by: Alex <alexei.katranov@intel.com>
2021-08-31 14:51:53 +03:00
Ilya Isaev f81abcb640 Increase number of iterations to cover 'resume_node::reset' call (#551)
Increased number of iterations to have a more stable coverage of resume_node::reset() function.

Signed-off-by: Ilya Isaev ilya.isaev@intel.com
2021-08-30 13:54:46 +03:00
Ilya Mishin 6aab19f72a Cover remove_edge with successor::input_ports_type (#549)
Signed-off-by: Ilya Mishin <ilya.mishin@intel.com>
2021-08-27 12:59:13 +03:00
Alexandra a6b90fc9f5 Contribution guidelines (#506)
* Signed-off-by:Alexandra Epanchinzeva alexandra.epanchinzeva@intel.com

* Minor fixes

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md
2021-08-26 17:55:23 +03:00
Ivan Kochin 1ea36d9e9d Cover the constraints_threads_per_core() reserved interface by testing (#544)
* Cover the constraints_threads_per_core reserved interface by testing

Signed-off-by: Kochin Ivan <kochin.ivan@intel.com>
2021-08-25 13:12:07 +03:00
Ilya IsaevandAlex 9924f9e808 Fix static build on Windows (#543)
This patch fixes issue with reference leak reported by test_global_control and test_tbb_fork on static build.

Signed-off-by: Isaev, Ilya ilya.isaev@intel.com

Co-authored-by: Alex <alexei.katranov@intel.com>
2021-08-24 14:23:05 +03:00
Ekaterina Mekhnetsova b60c4d3631 Update conf.py (#545) 2021-08-24 10:40:49 +03:00
Ekaterina Mekhnetsova 0695d176ff DOC: Add a link to Intel cookies (#542)
* Add a link to Intel cookies

* Update syntax
2021-08-24 10:29:57 +03:00
Pavel c6045a4f6f Fix using tbb::this_task_arena::current_thread_index() with uninitilized arena (#541)
Fix using tbb::this_task_arena::current_thread_index() with uninitilized arena
Signed-off-by: pavelkumbrasev <pavel.kumbrasev@intel.com>
2021-08-23 09:48:35 +03:00
vlserov d2405e35d1 Remove unused template class tls (#536)
Signed-off-by: Serov, Vladimir <vladimir.serov@intel.com>
2021-08-18 10:46:17 +03:00
Ilya Mishin de0109bed0 Improve Flow Graph conformance tests (#410)
* continue and function node

* multifunction node

* input node

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* buffering nodes

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* new input and output messages

* service nodes

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* green CI

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* graph reset tests

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* additional reset tests

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* fix codestyle issues

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* copy constructor fix

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* sfinae fix

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* fix unique_ptr on cxx11

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* style fixes

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* copy_constructor fix

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* fix atomic initialization

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>

* fid data race

Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>
2021-08-12 11:29:31 +03:00
AlexandAlexandra b44f164b5c [DOC] Fix task_scheduler_handle description (#509)
* [DOC] Fix task_scheduler_hadnle description

Signed-off-by: Alexei Katranov <alexei.katranov@intel.com>

Co-authored-by: Alexandra <alexandra.epanchinzeva@intel.com>
2021-08-12 10:51:15 +03:00
Sergey Zheltov 1167f54bdf CMake: suppress alignment warning for ARM64-MSVC (#532)
Signed-off-by: Zheltov, Sergey1 <sergey1.zheltov@intel.com>
2021-08-11 20:39:08 +03:00
Ilya Isaev 8584c45634 Code coverage fixes (#516)
In scope of this patch TBB_BUILD and TBBMALLOC_BUILD options were added. It was done to support scenarios when we are testing without tbbmalloc. It allows us to cover place where we fail to link with tbbmalloc and start using std_cache_aligned_allocate/deallocate.

Signed-off-by: Ilya Isaev ilya.isaev@intel.com
2021-08-09 11:52:01 +03:00
Ilya Mishin 1ecde27f49 Fix inclusive language usage issues in tbbmalloc (#522)
Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>
2021-08-06 11:45:00 +03:00
vlserov 5a643c2360 GCC 11: suppress stringop-overread warning (#519)
1. Using thread_local for non-pointer types instead of tbb::detail::r1::tls
2. Replace magic-value with valid pointer

Signed-off-by: Serov, Vladimir <vladimir.serov@intel.com>
2021-08-05 15:57:27 +03:00
Alexey Veprev 357c9b27fa Update version number for 2021.4 (#520)
Signed-off-by: Veprev, Alexey alexey.veprev@intel.com
2021-08-02 18:20:31 +03:00
Alex 4df48f9850 Fix a race condition in AddrRange in tbbmalloc (#518)
The change fixes a possible crash of realloc when replaced with tbbmalloc_proxy on Linux.

Signed-off-by: Alexei Katranov <alexei.katranov@intel.com>
2021-07-30 21:09:57 +03:00
Pavel 552f342b26 Add workaround for incorrect soft limit initialization (#512)
Signed-off-by: pavelkumbrasev <pavel.kumbrasev@intel.com>
2021-07-30 15:59:06 +03:00
Alexandpavelkumbrasev 34c7d170ca Add heavy mix test for the scheduler (#490)
The test found a couple of issues that are also addressed in the patch

Signed-off-by: Alexei Katranov <alexei.katranov@intel.com>
Co-authored-by: pavelkumbrasev <pavel.kumbrasev@intel.com>
2021-07-30 15:58:14 +03:00
Ivan KochinandAlexey Veprev 7ec3cf8408 Fix the HWLOC linking when it was found using pkg-config (#508)
* Use imported target instead of variables during the HWLOC search using pkg-config
* Remove redundant code + rephrase the description
* Fix the indentation
* Rework testing

Signed-off-by: Kochin Ivan <kochin.ivan@intel.com>

Co-authored-by: Alexey Veprev <alexey.veprev@intel.com>
2021-07-30 15:21:15 +03:00
Ilya Mishin a070e5f208 Fix optimization flag on icx Windows Release build (#513)
Signed-off-by: Mishin, Ilya <ilya.mishin@intel.com>
2021-07-29 15:50:48 +03:00
Michael Vitrano 7d70bb51c3 Support building for Windows ARM64 (#507)
* Support building for Windows ARM64

Signed-off-by: Michael Vitrano <michaelvitrano@gmail.com>
2021-07-26 18:23:29 +03:00
95 changed files with 4316 additions and 3285 deletions
+38
View File
@@ -0,0 +1,38 @@
### Description
_Add comprehensive description of proposed changes_
Fixes # - _issue number(s) if exists_
### Type of change
_Choose one or multiple, leave empty if none of the other choices apply_
_Add respective label(s) to PR if you have permissions_
- [ ] bug fix - _change which fixes an issue_
- [ ] new feature - _change which adds functionality_
- [ ] tests - _change in tests_
- [ ] infrastructure - _change in infrastructure and CI_
- [ ] documentation - _documentation update_
### Tests
- [ ] added - _required for new features and for some bug fixes_
- [ ] not needed
### Documentation
- [ ] updated in # - _add PR number_
- [ ] needs to be updated
- [ ] not needed
### Breaks backward compatibility
- [ ] Yes
- [ ] No
- [ ] Unknown
### Notify the following users
_List users with `@` to send notifications_
### Other information
+9 -3
View File
@@ -98,6 +98,8 @@ option(TBB_STRICT "Treat compiler warnings as errors" ON)
option(TBB_WINDOWS_DRIVER "Build as Universal Windows Driver (UWD)" OFF)
option(TBB_NO_APPCONTAINER "Apply /APPCONTAINER:NO (for testing binaries for Windows Store)" OFF)
option(TBB4PY_BUILD "Enable tbb4py build" OFF)
option(TBB_BUILD "Enable tbb build" ON)
option(TBBMALLOC_BUILD "Enable tbbmalloc build" ON)
option(TBB_CPF "Enable preview features of the library" OFF)
option(TBB_FIND_PACKAGE "Enable search for external oneTBB using find_package instead of build from sources" OFF)
option(TBB_DISABLE_HWLOC_AUTOMATIC_SEARCH "Disable HWLOC automatic search by pkg-config tool" OFF)
@@ -192,10 +194,14 @@ if (TBB_FIND_PACKAGE OR TBB_DIR)
message(STATUS "Using external TBB for testing")
find_package(TBB REQUIRED)
else()
add_subdirectory(src/tbb)
if (TBB_BUILD)
add_subdirectory(src/tbb)
endif()
if (NOT "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "mips")
add_subdirectory(src/tbbmalloc)
add_subdirectory(src/tbbmalloc_proxy)
if (TBBMALLOC_BUILD)
add_subdirectory(src/tbbmalloc)
add_subdirectory(src/tbbmalloc_proxy)
endif()
if (APPLE)
message(STATUS "TBBBind build targets are disabled due to unsupported environment")
else()
+55
View File
@@ -0,0 +1,55 @@
<!--
******************************************************************************
* 
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/-->
# How to Contribute
As an open source project, we welcome community contributions to oneAPI Threading Building Blocks (oneTBB). This document explains how to participate in project conversations, log bugs and enhancement requests, and submit code patches to the project.
## Licensing
Licensing is very important to open source projects. It helps ensure the software continues to be available under the terms that the author desired. The oneTBB project uses the [Apache 2.0 License](https://github.com/oneapi-src/oneTBB/blob/master/LICENSE.txt), a permissive open source license that allows you to freely use, modify, and distribute your own products that include Apache 2.0 licensed software. By contributing to the oneTBB project, you agree to the license and copyright terms therein and release your own contributions under these terms.
Some imported or reused components within oneTBB use other licenses, as described in [third-party-programs.txt](https://github.com/oneapi-src/oneTBB/blob/master/third-party-programs.txt). By carefully reviewing potential contributions and enforcing a [Developer Certification of Origin (DCO)](https://developercertificate.org/) for contributed code, we can ensure that the community can develop products with oneTBB without concerns over patent or copyright issues.
The DCO is an attestation attached to every contribution made by every developer. In the commit message of the contribution, (described later), the developer simply adds a Signed-off-by statement and thereby agrees to the DCO.
## Prerequisites
As a contributor, you’ll want to be familiar with the oneTBB project and the repository layout. You should also know how to use it as explained in the [oneTBB documentation](https://oneapi-src.github.io/oneTBB/) and how to set up your build development environment to configure, build, and test oneTBB as explained in the [oneTBB Build System Description](cmake/README.md).
## Issues
If you face a problem, first check out open [oneTBB GitHub issues](https://github.com/oneapi-src/oneTBB/issues) to see if the issue you’d like to address is already reported. You may find users that have encountered the bug you’re finding or have similar ideas for changes or additions.
You can use issues to report a problem, make a feature request, or add comments on an existing issue.
## Pull Requests
You can find all [open oneTBB pull requests](https://github.com/oneapi-src/oneTBB/pulls) on GitHub.
No anonymous contributions are accepted. The name in the commit message Signed-of-by line and your email must match the change authorship information. Make sure your .gitconfig is set up correctly so you can use `git commit -s` for signing your patches:
`git config --global user.name "Taylor Developer"`
`git config --global user.email taylor.developer@company.com`
Before contributing changes directly to the oneTBB repository:
* Make sure you can build the product and run all the tests with your patch.
* For a larger feature, provide a relevant test.
* Document your code. The oneTBB project uses reStructuredText for documentation.
* Submit a pull request into the master branch. You can submit changes with a pull request (preferred) or by sending an email patch.
Continuous Integration (CI) testing is enabled for the repository. Your pull request must pass all checks before it can be merged. We will review your contribution and may provide feedback to guide you if any additional fixes or modifications are necessary. When reviewed and accepted, your pull request will be merged into our GitHub repository.
+2 -14
View File
@@ -21,20 +21,8 @@ Please report issues and suggestions via
[oneTBB forum](https://community.intel.com/t5/Intel-oneAPI-Threading-Building/bd-p/oneapi-threading-building-blocks).
## How to Contribute
To contribute to oneTBB, please open a GitHub pull request (preferred) or send us a patch by e-mail.
Please use the sign-off line at the end of the patch.
Your signature certifies that you wrote the patch or
otherwise have the right to pass it on as an open-source patch.
The rules are pretty simple:
if you can certify the https://developercertificate.org then you just add a line to every git commit message:
```
Signed-off-by: Name Surname <user@email.com>
```
Use your real name (sorry, no pseudonyms or anonymous contributions).
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
We welcome community contributions, so check our [contributing guidelines](CONTRIBUTING.md)
to learn more.
## License
oneAPI Threading Building Blocks is licensed under [Apache License, Version 2.0](LICENSE.txt).
+5 -1
View File
@@ -9,7 +9,9 @@ TBB_STRICT:BOOL - Treat compiler warnings as errors (ON by default)
TBB_SANITIZE:STRING - Sanitizer parameter, passed to compiler/linker
TBB_SIGNTOOL:FILEPATH - Tool for digital signing, used in post install step for libraries if provided.
TBB_SIGNTOOL_ARGS:STRING - Additional arguments for TBB_SIGNTOOL, used if TBB_SIGNTOOL is set.
TBB4PY_BUILD:BOOL - Enable Intel(R) oneAPI Threading Building Blocks (oneTBB) Python module build (OFF by default)
TBB_BUILD:BOOL - Enable Intel(R) oneAPI Threading Building Blocks (oneTBB) build (ON by default)
TBBMALLOC_BUILD:BOOL - Enable Intel(R) oneAPI Threading Building Blocks (oneTBB) memory allocator build (ON by default)
TBB4PY_BUILD:BOOL - Enable Intel(R) oneAPI Threading Building Blocks (oneTBB) Python module build (OFF by default)
TBB_CPF:BOOL - Enable preview features of the library (OFF by default)
TBB_INSTALL_VARS:BOOL - Enable auto-generated vars installation(packages generated by `cpack` and `make install` will also include the vars script)(OFF by default)
TBB_VALGRIND_MEMCHECK:BOOL - Enable scan for memory leaks using Valgrind (OFF by default)
@@ -50,6 +52,8 @@ The search for a suitable version of the HWLOC library is enabled by default, bu
- `CMAKE_HWLOC_<HWLOC_VER>_LIBRARY_PATH` - path to the corresponding HWLOC version shared library on Linux or path to `.lib` file on Windows.
- `CMAKE_HWLOC_<HWLOC_VER>_INCLUDE_PATH` - path to the corresponding HWLOC version includes directory.
Automatic HWLOC searching requires CMake >= 3.6.
Windows requires one additional variable for correct TBBBind library building:
- `CMAKE_HWLOC_<HWLOC_VER>_DLL_PATH` - path to the corresponding HWLOC version `.dll` file.
+8 -5
View File
@@ -19,9 +19,11 @@ set(TBB_DEF_FILE_PREFIX win${TBB_ARCH})
# TODO: consider use of CMP0092 CMake policy.
string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
# Warning suppression C4324: structure was padded due to alignment specifier
set(TBB_WARNING_LEVEL $<$<NOT:$<CXX_COMPILER_ID:Intel>>:/W4> $<$<BOOL:${TBB_STRICT}>:/WX>)
# Warning suppression C4324: structure was padded due to alignment specifier
set(TBB_WARNING_SUPPRESS /wd4324)
set(TBB_TEST_COMPILE_FLAGS /bigobj)
if (MSVC_VERSION LESS_EQUAL 1900)
# Warning suppression C4503 for VS2015 and earlier:
@@ -31,7 +33,7 @@ if (MSVC_VERSION LESS_EQUAL 1900)
set(TBB_TEST_COMPILE_FLAGS ${TBB_TEST_COMPILE_FLAGS} /wd4503)
endif()
set(TBB_LIB_COMPILE_FLAGS -D_CRT_SECURE_NO_WARNINGS /GS)
set(TBB_COMMON_COMPILE_FLAGS /volatile:iso /FS)
set(TBB_COMMON_COMPILE_FLAGS /volatile:iso /FS /EHsc)
# Ignore /WX set through add_compile_options() or added to CMAKE_CXX_FLAGS if TBB_STRICT is disabled.
if (NOT TBB_STRICT AND COMMAND tbb_remove_compile_flag)
@@ -69,14 +71,15 @@ if (TBB_WINDOWS_DRIVER)
set(TBB_COMMON_COMPILE_FLAGS ${TBB_COMMON_COMPILE_FLAGS} /D _UNICODE /DUNICODE /DWINAPI_FAMILY=WINAPI_FAMILY_APP /D__WRL_NO_DEFAULT_LIB__)
endif()
set(TBB_IPO_COMPILE_FLAGS $<$<NOT:$<CONFIG:Debug>>:/GL>)
set(TBB_IPO_LINK_FLAGS $<$<NOT:$<CONFIG:Debug>>:-LTCG> $<$<NOT:$<CONFIG:Debug>>:-INCREMENTAL:NO>)
if (CMAKE_CXX_COMPILER_ID MATCHES "(Clang|IntelLLVM)")
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86|AMD64)")
set(TBB_COMMON_COMPILE_FLAGS ${TBB_COMMON_COMPILE_FLAGS} -mrtm -mwaitpkg)
endif()
set(TBB_OPENMP_NO_LINK_FLAG TRUE)
set(TBB_IPO_COMPILE_FLAGS $<$<NOT:$<CONFIG:Debug>>:-flto>)
else()
set(TBB_IPO_COMPILE_FLAGS $<$<NOT:$<CONFIG:Debug>>:/GL>)
set(TBB_IPO_LINK_FLAGS $<$<NOT:$<CONFIG:Debug>>:-LTCG> $<$<NOT:$<CONFIG:Debug>>:-INCREMENTAL:NO>)
endif()
set(TBB_OPENMP_FLAG /openmp)
+3 -4
View File
@@ -51,9 +51,9 @@ if (NOT HWLOC_TARGET_EXPLICITLY_DEFINED AND
NOT TBB_DISABLE_HWLOC_AUTOMATIC_SEARCH
)
find_package(PkgConfig QUIET)
if (PKG_CONFIG_FOUND)
pkg_search_module(HWLOC hwloc)
if (HWLOC_FOUND)
if (PKG_CONFIG_FOUND AND NOT CMAKE_VERSION VERSION_LESS 3.6)
pkg_search_module(HWLOC hwloc IMPORTED_TARGET)
if (TARGET PkgConfig::HWLOC)
if (HWLOC_VERSION VERSION_LESS 2)
set(TBBBIND_LIBRARY_NAME tbbbind)
elseif(HWLOC_VERSION VERSION_LESS 2.5)
@@ -64,4 +64,3 @@ if (NOT HWLOC_TARGET_EXPLICITLY_DEFINED AND
endif()
endif()
endif()
+22 -47
View File
@@ -16,6 +16,7 @@ of multi-core processors. Use |short_name| to write scalable applications that:
- Emphasize data parallel programming
- Take advantage of concurrent collections and parallel algorithms
Before You Begin
****************
@@ -43,55 +44,29 @@ After installing |short_name|, you need to set the environment variables:
``vars.bat in <install_dir>/tbb/latest/env``
System Requirements
*******************
Example
*******
Below you can find a typical example for a |short_name| algorithm.
The sample calculates a sum of all integer numbers from 1 to 100.
.. code:: cpp
int sum = oneapi::tbb::parallel_reduce(oneapi::tbb::blocked_range<int>(1,101), 0,
[](oneapi::tbb::blocked_range<int> const& r, int init) -> int {
for (int v = r.begin(); v != r.end(); v++ ) {
init += v;
}
return init;
},
[](int lhs, int rhs) -> int {
return lhs + rhs;
}
);
Refer to the `oneTBB System Requirements <https://software.intel.com/content/www/us/en/develop/articles/intel-oneapi-threading-building-blocks-system-requirements.html>`_.
Find more
*********
.. list-table::
:widths: 40 60
:header-rows: 0
* -
- `oneTBB Community Forum <https://community.intel.com/>`_
- `Product FAQs <https://software.intel.com/content/www/us/en/develop/support/faq-product.html>`_
- `Support requests <https://software.intel.com/content/www/us/en/develop/articles/how-to-create-a-support-request-at-online-service-center.html>`_
- Use these resources if you need support with oneTBB.
* - `Release Notes <https://software.intel.com/content/www/us/en/develop/articles/intel-oneapi-threading-building-blocks-release-notes.html>`_
- Find up-to-date information about the product, including detailed notes, known issues, and changes.
* - `Documentation <https://software.intel.com/content/www/us/en/develop/documentation/onetbb-documentation/top.html>`_: `Developer Guide <https://software.intel.com/content/www/us/en/develop/documentation/onetbb-documentation/top/onetbb-developer-guide.html>`_ and `API Reference <https://software.intel.com/content/www/us/en/develop/documentation/onetbb-documentation/top/onetbb-api-reference.html>`_
- Learn to use oneTBB.
* - `GitHub* <https://github.com/oneapi-src/oneTBB>`_
- Find oneTBB implementation in open source.
Notices and Disclaimers
***********************
Intel technologies may require enabled hardware, software or service activation.
No product or component can be absolutely secure.
Your costs and results may vary.
© Intel Corporation. Intel, the Intel logo, and other Intel marks are trademarks
of Intel Corporation or its subsidiaries. Other names and brands may be claimed
as the property of others.
No license (express or implied, by estoppel or otherwise) to any intellectual
property rights is granted by this document.
The products described may contain design defects or errors known as errata which
ay cause the product to deviate from published specifications. Current
characterized errata are available on request.
Intel disclaims all express and implied warranties, including without limitation,
the implied warranties of merchantability, fitness for a particular purpose,
and non-infringement, as well as any warranty arising from course of performance,
course of dealing, or usage in trade.
See our `documentation <https://oneapi-src.github.io/oneTBB/>`_ to learn more about |short_name|.
-16
View File
@@ -1,19 +1,3 @@
//configure tms
var wapLocalCode = 'us-en';
//dynamically set per localized site, see mapping table for values
var wapSection = "oneapi-docs";
//load tms
(function() {
var host = (window.document.location.protocol == 'http:') ? "http://www.intel.com" : "https://www.intel.com";
var url = host+"/content/dam/www/global/wap/tms-loader.js"; //wap file url
var po = document.createElement('script');
po.type = 'text/javascript';
po.async = true;
po.src = url;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();
window.MathJax = {
TeX: {
Macros: {
+15
View File
@@ -0,0 +1,15 @@
{% extends "!layout.html" %}
{% block extrahead %}
<script defer type="text/javascript" src="https://www.intel.com/content/dam/www/global/wap/performance-config.js" ></script>
<script type="text/javascript">
// Configure TMS settings
var wapLocalCode = 'us-en'; // Dynamically set per localized site, see mapping table for values
var wapSection = "oneapi-tbb"; // WAP team will give you a unique section for your site
// Load TMS
(function () {
var url = 'https://www.intel.com/content/dam/www/global/wap/tms-loader.js'; // WAP file URL
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = url;
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
{% endblock %}
+2 -1
View File
@@ -129,7 +129,8 @@ else:
'path_to_docs': 'doc/main',
'use_issues_button': True,
'use_edit_page_button': True,
'repository_branch': 'master'
'repository_branch': 'master',
'extra_footer': '<p align="right"><a href="https://www.intel.com/content/www/us/en/privacy/intel-cookie-notice.html">Cookies</a></p>'
}
+25 -1
View File
@@ -1,6 +1,31 @@
|full_name|
===========
This document contains information about |full_name|.
|short_name| is a flexible performance library that can be found in the |base_tk|
or as a stand-alone product. More information and specifications can be found on the
|full_name| main page.
The following are some important topics for the ``novice user``:
**oneTBB Benefits**
:ref:`Benefits` describes how |short_name| differs from typical threading packages.
**Package Contents**
|short_name| includes dynamic library files and header files for Windows*, Linux*, and macOS*
operating systems as described in :ref:`Package_Contents`.
The following is important topic for the ``experienced user``:
**Migrating from Threading Building Blocks (TBB)**
Learn how to migrate from TBB to oneTBB with :ref:`Migration_Guide`.
.. toctree::
:maxdepth: 2
@@ -10,4 +35,3 @@
intro/Benefits
tbb_userguide/title
reference/reference
intro/notices_and_disclaimers
+2 -9
View File
@@ -13,14 +13,7 @@ Getting Help and Support
.. rubric:: Getting Technical Support
:class: sectiontitle
If you did not register your Intel® software product during
installation, please do so now at the Intel® Software Development
Products Registration Center. Registration entitles you to free
technical support, product updates and upgrades for the duration of
the support term.
For general information about Intel technical support, product
For general information about oneTBB technical support, product
updates, user forums, FAQs, tips and tricks and other support
questions, go to: http://www.intel.com/software/products/support/.
questions, go to: [GitHub issues](https://github.com/oneapi-src/oneTBB/issues).
-9
View File
@@ -28,12 +28,3 @@ your specific needs.
The net result is that oneTBB enables you to specify parallelism far
more conveniently than using raw threads, and at the same time can
improve performance.
.. admonition:: Product and Performance Information
Performance varies by use, configuration and other factors. Learn more at `www.intel.com/PerformanceIndex <https://www.intel.com/PerformanceIndex>`_.
Notice revision #20201201
-8
View File
@@ -29,11 +29,3 @@ The net result is that oneTBB enables you to specify parallelism far
more conveniently than using raw threads, and at the same time can
improve performance.
.. admonition:: Product and Performance Information
Performance varies by use, configuration and other factors. Learn more at `www.intel.com/PerformanceIndex <https://www.intel.com/PerformanceIndex>`_.
Notice revision #20201201
@@ -1,49 +0,0 @@
.. _notices_and_disclaimers:
Notices and Disclaimers
=======================
Intel technologies may require enabled hardware, software or service
activation.
No product or component can be absolutely secure.
Your costs and results may vary.
© Intel Corporation. Intel, the Intel logo, and other Intel marks are
trademarks of Intel Corporation or its subsidiaries. Other names and
brands may be claimed as the property of others.
Intel's compilers may or may not optimize to the same degree for
non-Intel microprocessors for optimizations that are not unique to Intel
microprocessors. These optimizations include SSE2, SSE3, and SSSE3
instruction sets and other optimizations. Intel does not guarantee the
availability, functionality, or effectiveness of any optimization on
microprocessors not manufactured by Intel. Microprocessor-dependent
optimizations in this product are intended for use with Intel
microprocessors. Certain optimizations not specific to Intel
microarchitecture are reserved for Intel microprocessors. Please refer
to the applicable product User and Reference Guides for more information
regarding the specific instruction sets covered by this notice.
No license (express or implied, by estoppel or otherwise) to any
intellectual property rights is granted by this document.
The products described may contain design defects or errors known as
errata which may cause the product to deviate from published
specifications. Current characterized errata are available on request.
Intel disclaims all express and implied warranties, including without
limitation, the implied warranties of merchantability, fitness for a
particular purpose, and non-infringement, as well as any warranty
arising from course of performance, course of dealing, or usage in
trade.
+29 -1
View File
@@ -46,6 +46,8 @@ Synopsis
task_scheduler_handle& operator=(const task_scheduler_handle& other) = delete;
task_scheduler_handle& operator=(task_scheduler_handle&& other) noexcept;
explicit operator bool() const noexcept;
static task_scheduler_handle get();
static void release(task_scheduler_handle& handle);
@@ -60,6 +62,12 @@ Synopsis
Member Functions
----------------
.. cpp:function:: task_scheduler_handle()
**Effects**: Creates an instance of the ``task_scheduler_handle`` class that does not contain any reference to the task scheduler.
-------------------------------------------------------
.. cpp:function:: ~task_scheduler_handle()
**Effects**: Destroys an instance of the ``task_scheduler_handle`` class.
@@ -67,9 +75,29 @@ Member Functions
-------------------------------------------------------
.. cpp:function:: task_scheduler_handle(task_scheduler_handle&& other) noexcept
**Effects**: Creates an instance of the ``task_scheduler_handle`` class that references the task scheduler referenced by ``other``. In turn, ``other`` releases its reference to the task scheduler.
-------------------------------------------------------
.. cpp:function:: task_scheduler_handle& operator=(task_scheduler_handle&& other) noexcept
**Effects**: Releases a reference to the task scheduler referenced by ``this``. Adds a reference to the task scheduler referenced by ``other``.
In turn, ``other`` releases its reference to the task scheduler.
-------------------------------------------------------
.. cpp:function:: explicit operator bool() const noexcept
**Returns**: ``true`` if ``this`` references any task scheduler; ``false`` otherwise.
-------------------------------------------------------
.. cpp:function:: task_scheduler_handle get()
**Returns**: An instance of the ``task_scheduler_handle`` class.
**Returns**: An instance of the ``task_scheduler_handle`` class that holds a reference to the task scheduler preventing
its premature destruction.
-------------------------------------------------------
@@ -96,14 +96,6 @@ of computations to memory accesses.
|image1|
.. admonition:: Product and Performance Information
Performance varies by use, configuration and other factors. Learn more at `www.Intel.com/PerformanceIndex <https://www.intel.com/PerformanceIndex>`_.
Notice revision #20201201
.. |image0| image:: Images/image007.jpg
:width: 453px
:height: 178px
@@ -157,12 +157,6 @@ quite well.
work than an iteration of an inner loop.
.. admonition:: Product and Performance Information
Performance varies by use, configuration and other factors. Learn more at `www.intel.com/PerformanceIndex <https://www.intel.com/PerformanceIndex>`_.
Notice revision #20201201
.. |image0| image:: Images/image002.jpg
:width: 161px
:height: 163px
@@ -325,3 +325,131 @@ task:
}
tg.wait();
}
Scheduler Bypass
----------------
TBB ``task::execute()`` method can return a pointer to a task that can be executed next by the current thread.
This might reduce scheduling overheads compared to direct ``spawn``. Similar to ``spawn``, the returned task
is not guaranteed to be executed next by the current thread.
.. code:: cpp
#include <tbb/task.h>
// Assuming OtherTask is defined.
struct Task : tbb::task {
task* execute(){
// some work to do ...
auto* other_p = new(this->parent().allocate_child()) OtherTask{};
this->parent().add_ref_count();
return other_p;
}
};
int main(){
// Assuming RootTask is defined.
RootTask& root = *new(tbb::task::allocate_root()) RootTask{};
Task& child = *new(root.allocate_child()) Task{/*params*/};
root.add_ref_count();
tbb::task_spawn(child);
root.wait_for_all();;
}
In oneTBB this can be done using the preview feature of ``oneapi::tbb::task_group``.
.. code:: cpp
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
#include <oneapi/tbb/task_group.h>
// Assuming OtherTask is defined.
int main(){
oneapi::tbb::task_group tg;
tg.run([&tg](){
//some work to do ...
return tg.defer(OtherTask{});
});
tg.wait();
}
Here ``oneapi::tbb::task_group::defer`` adds a new task into the ``tg``. However, the task is not put into a
queue of tasks ready for execution via ``oneapi::tbb::task_group::run``, but bypassed to the executing thread directly
via function return value.
Deferred task creation
----------------------
The TBB low-level task API separates the task creation from the actual spawning. This separation allows to
postpone the task spawning, while the parent task and final result production are blocked from premature leave.
For example, ``RootTask``, ``ChildTask``, and ``CallBackTask`` are the user-side functors that
inherit ``tbb::task`` and implement its interface. Then, blocking the ``RootTask`` from leaving prematurely
and waiting on it is implemented as follows:
.. code:: cpp
#include <tbb/task.h>
int main() {
// Assuming RootTask, ChildTask, and CallBackTask are defined.
RootTask& root = *new(tbb::task::allocate_root()) RootTask{};
ChildTask& child = *new(root.allocate_child()) ChildTask{/*params*/};
CallBackTask& cb_task = *new(root.allocate_child()) CallBackTask{/*params*/};
root.set_ref_count(3);
tbb::task::spawn(child);
register_callback([cb_task&](){
tbb::task::enqueue(cb_task);
});
root.wait_for_all();
// Control flow will reach here only after both ChildTask and CallBackTask are executed,
// i.e. after the callback is called
}
In oneTBB this can be done using the preview feature of ``oneapi::tbb::task_group``.
.. code:: cpp
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
#include <oneapi/tbb/task_group.h>
int main(){
oneapi::tbb::task_group tg;
oneapi::tbb::task_arena arena;
// Assuming ChildTask and CallBackTask are defined.
auto cb = tg.defer(CallBackTask{/*params*/});
register_callback([&tg, c = std::move(cb), &arena]{
arena.enqueue(c);
});
tg.run(ChildTask{/*params*/});
tg.wait();
// Control flow gets here once both ChildTask and CallBackTask are executed
// i.e. after the callback is called
}
Here ``oneapi::tbb::task_group::defer`` adds a new task into the ``tg``. However, the task is not spawned until
``oneapi::tbb::task_arena::enqueue`` is called.
.. note::
The call to ``oneapi::tbb::task_group::wait`` will not return control until both ``ChildTask`` and
``CallBackTask`` are executed.
+2 -2
View File
@@ -94,7 +94,7 @@
#define __TBB_IS_MACRO_EMPTY(A,IGNORED) __TBB_CONCAT_AUX(__TBB_MACRO_EMPTY,A)
#define __TBB_MACRO_EMPTY 1
#if _M_X64
#if _M_X64 || _M_ARM64
#define __TBB_W(name) name##64
#else
#define __TBB_W(name) name
@@ -327,7 +327,7 @@
#define __TBB_CPP17_UNCAUGHT_EXCEPTIONS_PRESENT (_MSC_VER >= 1900 || __GLIBCXX__ && __cpp_lib_uncaught_exceptions \
|| _LIBCPP_VERSION >= 3700 && (!__TBB_MACOS_TARGET_VERSION || __TBB_MACOS_TARGET_VERSION >= 101200))
#define __TBB_TSX_INTRINSICS_PRESENT ((__RTM__ || _MSC_VER>=1700 || __INTEL_COMPILER) && !__ANDROID__)
#define __TBB_TSX_INTRINSICS_PRESENT (__RTM__ || __INTEL_COMPILER || (_MSC_VER>=1700 && (__TBB_x86_64 || __TBB_x86_32)))
#define __TBB_WAITPKG_INTRINSICS_PRESENT ((__INTEL_COMPILER >= 1900 || __TBB_GCC_VERSION >= 110000 || __TBB_CLANG_VERSION >= 120000) && !__ANDROID__)
+5 -3
View File
@@ -33,9 +33,11 @@
#include <windows.h> // SwitchToThread()
#endif
#ifdef _MSC_VER
#if __TBB_x86_64 || __TBB_x86_32
#pragma intrinsic(__rdtsc)
#endif
#endif
#endif
#if __TBB_x86_64 || __TBB_x86_32
#include <immintrin.h> // _mm_pause
#endif
@@ -75,12 +77,12 @@ using std::this_thread::yield;
// atomic_fence implementation
//--------------------------------------------------------------------------------------------------
#if (_MSC_VER)
#if _MSC_VER && (__TBB_x86_64 || __TBB_x86_32)
#pragma intrinsic(_mm_mfence)
#endif
static inline void atomic_fence(std::memory_order order) {
#if (_WIN32)
#if _MSC_VER && (__TBB_x86_64 || __TBB_x86_32)
if (order == std::memory_order_seq_cst ||
order == std::memory_order_acq_rel ||
order == std::memory_order_acquire ||
@@ -89,7 +91,7 @@ static inline void atomic_fence(std::memory_order order) {
_mm_mfence();
return;
}
#endif /*(_WIN32)*/
#endif /*_MSC_VER && (__TBB_x86_64 || __TBB_x86_32)*/
std::atomic_thread_fence(order);
}
+2 -2
View File
@@ -451,8 +451,8 @@ inline auto isolate(F&& f) -> decltype(f()) {
//! Returns the index, aka slot number, of the calling thread in its current arena
inline int current_thread_index() {
int idx = r1::execution_slot(nullptr);
return idx == -1 ? task_arena_base::not_initialized : idx;
slot_id idx = r1::execution_slot(nullptr);
return idx == slot_id(-1) ? task_arena_base::not_initialized : int(idx);
}
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
+28
View File
@@ -524,6 +524,28 @@ protected:
return cancellation_status ? canceled : complete;
}
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
task_group_status internal_run_and_wait(d2::task_handle&& h) {
if (h == nullptr) {
throw_exception(exception_id::bad_task_handle);
}
using acs = d2::task_handle_accessor;
if (&acs::ctx_of(h) != &context()) {
throw_exception(exception_id::bad_task_handle_wrong_task_group);
}
bool cancellation_status = false;
try_call([&] {
execute_and_wait(*acs::release(h), context(), m_wait_ctx, context());
}).on_completion([&] {
// TODO: the reset method is not thread-safe. Ensure the correct behavior.
cancellation_status = context().is_group_execution_cancelled();
context().reset();
});
return cancellation_status ? canceled : complete;
}
#endif
template<typename F>
task* prepare_task(F&& f) {
m_wait_ctx.reserve();
@@ -632,6 +654,12 @@ public:
task_group_status run_and_wait(const F& f) {
return internal_run_and_wait(f);
}
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
task_group_status run_and_wait(d2::task_handle&& h) {
return internal_run_and_wait(std::move(h));
}
#endif
}; // class task_group
#if TBB_PREVIEW_ISOLATED_TASK_GROUP
+2 -2
View File
@@ -23,7 +23,7 @@
// Product version
#define TBB_VERSION_MAJOR 2021
// Update version
#define TBB_VERSION_MINOR 3
#define TBB_VERSION_MINOR 5
// "Patch" version for custom releases
#define TBB_VERSION_PATCH 0
// Suffix string
@@ -34,7 +34,7 @@
// OneAPI oneTBB specification version
#define ONETBB_SPEC_VERSION "1.0"
// Full interface version
#define TBB_INTERFACE_VERSION 12030
#define TBB_INTERFACE_VERSION 12050
// Major interface version
#define TBB_INTERFACE_VERSION_MAJOR (TBB_INTERFACE_VERSION/1000)
// Minor interface version
+1 -1
View File
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
add_library(tbb
add_library(tbb
address_waiter.cpp
allocator.cpp
arena.cpp
+3 -2
View File
@@ -122,7 +122,7 @@ void arena::process(thread_data& tls) {
// worker thread enters the dispatch loop to look for a work
tls.my_inbox.set_is_idle(true);
if (tls.my_arena_slot->is_task_pool_published()) {
tls.my_inbox.set_is_idle(false);
tls.my_inbox.set_is_idle(false);
}
task_dispatcher& task_disp = tls.my_arena_slot->default_task_dispatcher();
@@ -434,7 +434,8 @@ void __TBB_EXPORTED_FUNC enqueue(d1::task& t, d1::task_group_context& ctx, d1::t
}
void task_arena_impl::initialize(d1::task_arena_base& ta) {
governor::one_time_init();
// Enforce global market initialization to properly initialize soft limit
(void)governor::get_thread_data();
if (ta.my_max_concurrency < 1) {
#if __TBB_ARENA_BINDING
+21 -14
View File
@@ -217,9 +217,6 @@ struct arena_base : padded<intrusive_list_node> {
task_stream<back_nonnull_accessor> my_critical_task_stream;
#endif
//! The number of workers requested by the external thread owning the arena.
unsigned my_max_num_workers;
//! The total number of workers that are requested from the resource manager.
int my_total_num_workers_requested;
@@ -254,18 +251,9 @@ struct arena_base : padded<intrusive_list_node> {
//! The market that owns this arena.
market* my_market;
//! ABA prevention marker.
std::uintptr_t my_aba_epoch;
//! Default task group context.
d1::task_group_context* my_default_ctx;
//! The number of slots in the arena.
unsigned my_num_slots;
//! The number of reserved slots (can be occupied only by external threads).
unsigned my_num_reserved_slots;
#if __TBB_ENQUEUE_ENFORCED_CONCURRENCY
// arena needs an extra worker despite a global limit
std::atomic<bool> my_global_concurrency_mode;
@@ -284,6 +272,21 @@ struct arena_base : padded<intrusive_list_node> {
int my_local_concurrency_requests;
#endif /* __TBB_ENQUEUE_ENFORCED_CONCURRENCY*/
//! ABA prevention marker.
std::uintptr_t my_aba_epoch;
//! The number of slots in the arena.
unsigned my_num_slots;
//! The number of reserved slots (can be occupied only by external threads).
unsigned my_num_reserved_slots;
//! The number of workers requested by the external thread owning the arena.
unsigned my_max_num_workers;
//! The target serialization epoch for callers of adjust_job_count_estimate
int my_adjust_demand_target_epoch;
//! The current serialization epoch for callers of adjust_job_count_estimate
d1::waitable_atomic<int> my_adjust_demand_current_epoch;
#if TBB_USE_ASSERT
//! Used to trap accesses to the object after its destruction.
std::uintptr_t my_guard;
@@ -476,8 +479,12 @@ inline void arena::on_thread_leaving ( ) {
// concurrently, can't guarantee last is_out_of_work() return true.
}
#endif
if ( (my_references -= ref_param ) == 0 )
// Release our reference to sync with arena destroy
unsigned remaining_ref = my_references.fetch_sub(ref_param, std::memory_order_release) - ref_param;
if (remaining_ref == 0) {
m->try_destroy_arena( this, aba_epoch, priority_level );
}
}
template<arena::new_work_type work_type>
@@ -564,7 +571,7 @@ inline d1::task* arena::steal_task(unsigned arena_index, FastRandom& frnd, execu
arena_slot* victim = &my_slots[k];
d1::task **pool = victim->task_pool.load(std::memory_order_relaxed);
d1::task *t = nullptr;
if (pool == EmptyTaskPool || !(t = victim->steal_task(*this, isolation))) {
if (pool == EmptyTaskPool || !(t = victim->steal_task(*this, isolation, k))) {
return nullptr;
}
if (task_accessor::is_proxy_task(*t)) {
+2 -3
View File
@@ -145,7 +145,7 @@ d1::task* arena_slot::get_task(execution_data_ext& ed, isolation_type isolation)
return result;
}
d1::task* arena_slot::steal_task(arena& a, isolation_type isolation) {
d1::task* arena_slot::steal_task(arena& a, isolation_type isolation, std::size_t slot_index) {
d1::task** victim_pool = lock_task_pool();
if (!victim_pool) {
return nullptr;
@@ -175,7 +175,7 @@ d1::task* arena_slot::steal_task(arena& a, isolation_type isolation) {
}
task_proxy& tp = *static_cast<task_proxy*>(result);
// If mailed task is likely to be grabbed by its destination thread, skip it.
if ( !(task_proxy::is_shared( tp.task_and_tag ) && tp.outbox->recipient_is_idle()) ) {
if (!task_proxy::is_shared(tp.task_and_tag) || !tp.outbox->recipient_is_idle() || a.mailbox(slot_index).recipient_is_idle()) {
break;
}
}
@@ -216,4 +216,3 @@ unlock:
} // namespace r1
} // namespace detail
} // namespace tbb
+1 -1
View File
@@ -135,7 +135,7 @@ public:
d1::task* get_task(execution_data_ext&, isolation_type);
//! Steal task from slot's ready pool
d1::task* steal_task(arena&, isolation_type);
d1::task* steal_task(arena&, isolation_type, std::size_t);
//! Some thread is now the owner of this slot
void occupy() {
+17
View File
@@ -167,6 +167,18 @@ static std::uintptr_t get_stack_base(std::size_t stack_size) {
#endif /* USE_PTHREAD */
}
#if (_WIN32||_WIN64) && !__TBB_DYNAMIC_LOAD_ENABLED
static void register_external_thread_destructor() {
struct thread_destructor {
~thread_destructor() {
governor::terminate_external_thread();
}
};
// ~thread_destructor() will be call during the calling thread termination
static thread_local thread_destructor thr_destructor;
}
#endif // (_WIN32||_WIN64) && !__TBB_DYNAMIC_LOAD_ENABLED
void governor::init_external_thread() {
one_time_init();
// Create new scheduler instance with arena
@@ -192,6 +204,11 @@ void governor::init_external_thread() {
td.my_arena_slot->occupy();
a.my_market->add_external_thread(td);
set_thread_data(td);
#if (_WIN32||_WIN64) && !__TBB_DYNAMIC_LOAD_ENABLED
// The external thread destructor is called from dllMain but it is not available with a static build.
// Therefore, we need to register the current thread to call the destructor during thread termination.
register_external_thread_destructor();
#endif
}
void governor::auto_terminate(void* tls) {
+8 -11
View File
@@ -53,12 +53,6 @@ private:
//! TLS for scheduler instances associated with individual threads
static basic_tls<thread_data*> theTLS;
//! Caches the maximal level of parallelism supported by the hardware
static unsigned DefaultNumberOfThreads;
//! Caches the size of OS regular memory page
static std::size_t DefaultPageSize;
// TODO (TBB_REVAMP_TODO): reconsider constant names
static rml::tbb_factory theRMLServerFactory;
@@ -78,13 +72,14 @@ private:
public:
static unsigned default_num_threads () {
// No memory fence required, because at worst each invoking thread calls AvailableHwConcurrency once.
return DefaultNumberOfThreads ? DefaultNumberOfThreads :
DefaultNumberOfThreads = AvailableHwConcurrency();
// Caches the maximal level of parallelism supported by the hardware
static unsigned num_threads = AvailableHwConcurrency();
return num_threads;
}
static std::size_t default_page_size () {
return DefaultPageSize ? DefaultPageSize :
DefaultPageSize = DefaultSystemPageSize();
// Caches the size of OS regular memory page
static std::size_t page_size = DefaultSystemPageSize();
return page_size;
}
static void one_time_init();
//! Processes scheduler initialization request (possibly nested) in an external thread
@@ -123,9 +118,11 @@ public:
return theTLS.get();
}
#if TBB_USE_ASSERT
static bool is_thread_data_set(thread_data* td) {
return theTLS.get() == td;
}
#endif
//! Undo automatic initialization if necessary; call when a thread exits.
static void terminate_external_thread() {
-2
View File
@@ -35,8 +35,6 @@ namespace r1 {
//------------------------------------------------------------------------
// governor data
basic_tls<thread_data*> governor::theTLS;
unsigned governor::DefaultNumberOfThreads;
size_t governor::DefaultPageSize;
rml::tbb_factory governor::theRMLServerFactory;
bool governor::UsePrivateRML;
bool governor::is_rethrow_broken;
+10 -9
View File
@@ -137,9 +137,9 @@ market& market::global_market(bool is_public, unsigned workers_requested, std::s
const unsigned workers_soft_limit = calc_workers_soft_limit(workers_requested, workers_hard_limit);
// Create the global market instance
std::size_t size = sizeof(market);
__TBB_ASSERT( __TBB_offsetof(market, my_workers) + sizeof(thread_data*) == sizeof(market),
__TBB_ASSERT( __TBB_offsetof(market, my_workers) + sizeof(std::atomic<thread_data*>) == sizeof(market),
"my_workers must be the last data field of the market class");
size += sizeof(thread_data*) * (workers_hard_limit - 1);
size += sizeof(std::atomic<thread_data*>) * (workers_hard_limit - 1);
__TBB_InitOnce::add_ref();
void* storage = cache_aligned_allocate(size);
std::memset( storage, 0, size );
@@ -326,7 +326,8 @@ void market::try_destroy_arena ( arena* a, uintptr_t aba_epoch, unsigned priorit
if ( a == &*it ) {
if ( it->my_aba_epoch == aba_epoch ) {
// Arena is alive
if ( !a->my_num_workers_requested && !a->my_references.load(std::memory_order_relaxed) ) {
// Acquire my_references to sync with threads that just left the arena
if (!a->my_num_workers_requested && !a->my_references.load(std::memory_order_acquire)) {
__TBB_ASSERT(
!a->my_num_workers_allotted.load(std::memory_order_relaxed) &&
(a->my_pool_state == arena::SNAPSHOT_EMPTY || !a->my_max_num_workers),
@@ -577,14 +578,14 @@ void market::adjust_demand ( arena& a, int delta, bool mandatory ) {
my_num_workers_requested += delta;
__TBB_ASSERT(my_num_workers_requested <= (int)effective_soft_limit, NULL);
target_epoch = my_adjust_demand_target_epoch++;
target_epoch = a.my_adjust_demand_target_epoch++;
}
my_adjust_demand_current_epoch.wait_until(target_epoch, /* context = */ target_epoch, std::memory_order_acquire);
a.my_adjust_demand_current_epoch.wait_until(target_epoch, /* context = */ target_epoch, std::memory_order_relaxed);
// Must be called outside of any locks
my_server->adjust_job_count_estimate( delta );
my_adjust_demand_current_epoch.exchange(target_epoch + 1);
my_adjust_demand_current_epoch.notify_relaxed(target_epoch + 1);
a.my_adjust_demand_current_epoch.exchange(target_epoch + 1);
a.my_adjust_demand_current_epoch.notify_relaxed(target_epoch + 1);
}
void market::process( job& j ) {
@@ -621,8 +622,8 @@ void market::acknowledge_close_connection() {
// index serves as a hint decreasing conflicts between workers when they migrate between arenas
thread_data* td = new(cache_aligned_allocate(sizeof(thread_data))) thread_data{ index, true };
__TBB_ASSERT( index <= my_num_workers_hard_limit, NULL );
__TBB_ASSERT( my_workers[index - 1] == nullptr, NULL );
my_workers[index - 1] = td;
__TBB_ASSERT( my_workers[index - 1].load(std::memory_order_relaxed) == nullptr, NULL );
my_workers[index - 1].store(td, std::memory_order_release);
return td;
}
+1 -7
View File
@@ -105,12 +105,6 @@ private:
//! Number of workers currently requested from RML
int my_num_workers_requested;
//! The target serialization epoch for callers of adjust_job_count_estimate
int my_adjust_demand_target_epoch;
//! The current serialization epoch for callers of adjust_job_count_estimate
d1::waitable_atomic<int> my_adjust_demand_current_epoch;
//! First unused index of worker
/** Used to assign indices to the new workers coming from RML, and busy part
of my_workers array. **/
@@ -294,7 +288,7 @@ public:
//! Array of pointers to the registered workers
/** Used by cancellation propagation mechanism.
Must be the last data member of the class market. **/
thread_data* my_workers[1];
std::atomic<thread_data*> my_workers[1];
static unsigned max_num_workers() {
global_market_mutex_type::scoped_lock lock( theMarketMutex );
-2
View File
@@ -79,8 +79,6 @@ public:
//! Accessor to the reader-writer mutex associated with the list.
spin_rw_mutex& mutex () { return my_mutex.begin()[0]; }
bool empty () const { return my_head.load(std::memory_order_relaxed) == nullptr; }
//! Call entry notifications on observers added after last was notified.
/** Updates last to become the last notified observer proxy (in the global list)
or leaves it to be nullptr. The proxy has its refcount incremented. **/
+3 -3
View File
@@ -144,7 +144,7 @@ class input_buffer {
const bool is_ordered;
//! for parallel filters that accepts NULLs, thread-local flag for reaching end_of_input
using end_of_input_tls_t = basic_tls<std::intptr_t>;
using end_of_input_tls_t = basic_tls<input_buffer*>;
end_of_input_tls_t end_of_input_tls;
bool end_of_input_tls_allocated; // no way to test pthread creation of TLS
@@ -240,10 +240,10 @@ public:
handle_perror(status, "Failed to destroy filter TLS");
}
bool my_tls_end_of_input() {
return end_of_input_tls.get() != 0;
return end_of_input_tls.get() != nullptr;
}
void set_my_tls_end_of_input() {
end_of_input_tls.set(1);
end_of_input_tls.set(this);
}
};
+109 -55
View File
@@ -168,7 +168,11 @@ struct queuing_rw_mutex_impl {
s.my_state.store(d1::queuing_rw_mutex::scoped_lock::state_t(write ? STATE_WRITER : STATE_READER), std::memory_order_relaxed);
s.my_internal_lock.store(RELEASED, std::memory_order_relaxed);
queuing_rw_mutex::scoped_lock* predecessor = m.q_tail.exchange(&s, std::memory_order_release);
// The CAS must have release semantics, because we are
// "sending" the fields initialized above to other actors.
// We need acquire semantics, because we are acquiring the predecessor (or mutex if no predecessor)
queuing_rw_mutex::scoped_lock* predecessor = m.q_tail.exchange(&s, std::memory_order_acq_rel);
if( write ) { // Acquiring for write
@@ -177,7 +181,8 @@ struct queuing_rw_mutex_impl {
predecessor = tricky_pointer(predecessor) & ~FLAG;
__TBB_ASSERT( !predecessor->my_next, "the predecessor has another successor!");
tricky_pointer::store(predecessor->my_next, &s, std::memory_order_release);
spin_wait_until_eq(s.my_going, 1U);
// We are acquiring the mutex
spin_wait_until_eq(s.my_going, 1U, std::memory_order_acquire);
}
} else { // Acquiring for read
@@ -194,10 +199,18 @@ struct queuing_rw_mutex_impl {
} else {
// Load predecessor->my_state now, because once predecessor->my_next becomes
// non-NULL, we must assume that *predecessor might be destroyed.
pred_state = STATE_READER;
predecessor->my_state.compare_exchange_strong(pred_state, STATE_READER_UNBLOCKNEXT, std::memory_order_acq_rel);
pred_state = predecessor->my_state.load(std::memory_order_relaxed);
if (pred_state == STATE_READER) {
// Notify the previous reader to unblock us.
predecessor->my_state.compare_exchange_strong(pred_state, STATE_READER_UNBLOCKNEXT, std::memory_order_relaxed);
}
if (pred_state == STATE_ACTIVEREADER) { // either we initially read it or CAS failed
// Active reader means that the predecessor already acquired the mutex and cannot notify us.
// Therefore, we need to acquire the mutex ourselves by re-reading predecessor state.
(void)predecessor->my_state.load(std::memory_order_acquire);
}
}
tricky_pointer::store(s.my_prev, predecessor, std::memory_order_relaxed); // TODO: order?
tricky_pointer::store(s.my_prev, predecessor, std::memory_order_relaxed);
__TBB_ASSERT( !( tricky_pointer(predecessor) & FLAG ), "use of corrupted pointer!" );
__TBB_ASSERT( !predecessor->my_next.load(std::memory_order_relaxed), "the predecessor has another successor!");
tricky_pointer::store(predecessor->my_next, &s, std::memory_order_release);
@@ -206,13 +219,18 @@ struct queuing_rw_mutex_impl {
sync_prepare_done = true;
ITT_NOTIFY(sync_prepare, s.my_mutex);
#endif
// We are acquiring the mutex
spin_wait_until_eq(s.my_going, 1U, std::memory_order_acquire);
}
}
// The protected state must have been acquired here before it can be further released to any other reader(s):
unsigned char old_state = STATE_READER;
s.my_state.compare_exchange_strong(old_state, STATE_ACTIVEREADER, std::memory_order_acq_rel);
// When this reader is signaled by previous actor it acquires the mutex.
// We need to build happens-before relation with all other coming readers that will read our ACTIVEREADER
// without blocking on my_going. Therefore, we need to publish ACTIVEREADER with release semantics.
// On fail it is relaxed, because we will build happens-before on my_going.
s.my_state.compare_exchange_strong(old_state, STATE_ACTIVEREADER, std::memory_order_release, std::memory_order_relaxed);
if( old_state!=STATE_READER ) {
#if __TBB_USE_ITT_NOTIFY
if( !sync_prepare_done )
@@ -220,20 +238,16 @@ struct queuing_rw_mutex_impl {
#endif
// Failed to become active reader -> need to unblock the next waiting reader first
__TBB_ASSERT( s.my_state.load(std::memory_order_relaxed)==STATE_READER_UNBLOCKNEXT, "unexpected state" );
spin_wait_while_eq(s.my_next, 0U);
spin_wait_while_eq(s.my_next, 0U, std::memory_order_acquire);
/* my_state should be changed before unblocking the next otherwise it might finish
and another thread can get our old state and left blocked */
s.my_state.store(STATE_ACTIVEREADER, std::memory_order_relaxed);
tricky_pointer::load(s.my_next, std::memory_order_relaxed)->my_going.store(1U, std::memory_order_release);
}
__TBB_ASSERT( s.my_state.load(std::memory_order_relaxed) ==STATE_ACTIVEREADER, "unlocked reader is active reader" );
__TBB_ASSERT(s.my_state.load(std::memory_order_relaxed) == STATE_ACTIVEREADER, "unlocked reader is active reader");
}
ITT_NOTIFY(sync_acquired, s.my_mutex);
// Force acquire so that user's critical section receives correct values
// from processor that was previously in the user's critical section.
atomic_fence(std::memory_order_acquire); // TODO: consider exchange on line 171
}
//! A method to acquire queuing_rw_mutex if it is free
@@ -253,13 +267,11 @@ struct queuing_rw_mutex_impl {
s.my_internal_lock.store(RELEASED, std::memory_order_relaxed);
// The CAS must have release semantics, because we are
// "sending" the fields initialized above to other processors.
// "sending" the fields initialized above to other actors.
// We need acquire semantics, because we are acquiring the mutex
d1::queuing_rw_mutex::scoped_lock* expected = nullptr;
if (!m.q_tail.compare_exchange_strong(expected, &s, std::memory_order_release))
if (!m.q_tail.compare_exchange_strong(expected, &s, std::memory_order_acq_rel))
return false; // Someone already took the lock
// Force acquire so that user's critical section receives correct values
// from processor that was previously in the user's critical section.
atomic_fence(std::memory_order_acquire);
s.my_mutex = &m;
ITT_NOTIFY(sync_acquired, s.my_mutex);
return true;
@@ -280,32 +292,50 @@ struct queuing_rw_mutex_impl {
d1::queuing_rw_mutex::scoped_lock* next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
if( !next ) {
d1::queuing_rw_mutex::scoped_lock* expected = &s;
if( s.my_mutex->q_tail.compare_exchange_strong(expected, nullptr, std::memory_order_release) ) {
// Release mutex on success otherwise wait for successor publication
if( s.my_mutex->q_tail.compare_exchange_strong(expected, nullptr,
std::memory_order_release, std::memory_order_relaxed) )
{
// this was the only item in the queue, and the queue is now empty.
goto done;
}
spin_wait_while_eq(s.my_next, 0U);
spin_wait_while_eq(s.my_next, 0U, std::memory_order_relaxed);
next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
}
next->my_going.store(2U, std::memory_order_relaxed); // protect next queue node from being destroyed too early
if( next->my_state.load()==STATE_UPGRADE_WAITING ) {
// If the next is STATE_UPGRADE_WAITING, it is expected to acquire all other released readers via release
// sequence in next->my_state. In that case, we need to preserve release sequence in next->my_state
// contributed by other reader. So, there are two approaches not to break the release sequence:
// 1. Use read-modify-write (exchange) operation to store with release the UPGRADE_LOSER state;
// 2. Acquire the release sequence and store the sequence and UPGRADE_LOSER state.
// The second approach seems better on x86 because it does not involve interlocked operations.
// Therefore, we read next->my_state with acquire while it is not required for else branch to get the
// release sequence.
if( next->my_state.load(std::memory_order_acquire)==STATE_UPGRADE_WAITING ) {
// the next waiting for upgrade means this writer was upgraded before.
acquire_internal_lock(s);
// Responsibility transition, the one who reads uncorrupted my_prev will do release.
// Guarantee that above store of 2 into next->my_going happens-before resetting of next->my_prev
d1::queuing_rw_mutex::scoped_lock* tmp = tricky_pointer::exchange(next->my_prev, nullptr, std::memory_order_release);
next->my_state.store(STATE_UPGRADE_LOSER, std::memory_order_relaxed);
// Pass the release sequence that we acquired with the above load of next->my_state.
next->my_state.store(STATE_UPGRADE_LOSER, std::memory_order_release);
// We are releasing the mutex
next->my_going.store(1U, std::memory_order_release);
unblock_or_wait_on_internal_lock(s, get_flag(tmp));
} else {
// next->state cannot be STATE_UPGRADE_REQUESTED
__TBB_ASSERT( next->my_state.load(std::memory_order_relaxed) & (STATE_COMBINED_WAITINGREADER | STATE_WRITER), "unexpected state" );
__TBB_ASSERT( !( next->my_prev.load(std::memory_order_relaxed) & FLAG ), "use of corrupted pointer!" );
tricky_pointer::store(next->my_prev, nullptr, std::memory_order_relaxed);
// Guarantee that above store of 2 into next->my_going happens-before resetting of next->my_prev
tricky_pointer::store(next->my_prev, nullptr, std::memory_order_release);
// We are releasing the mutex
next->my_going.store(1U, std::memory_order_release);
}
} else { // Acquired for read
// The basic idea it to build happens-before relation with left and right readers via prev and next. In addition,
// the first reader should acquire the left (prev) signal and propagate to right (next). To simplify, we always
// build happens-before relation between left and right (left is happened before right).
queuing_rw_mutex::scoped_lock *tmp = nullptr;
retry:
// Addition to the original paper: Mark my_prev as in use
@@ -317,12 +347,9 @@ struct queuing_rw_mutex_impl {
// Failed to acquire the lock on predecessor. The predecessor either unlinks or upgrades.
// In the second case, it could or could not know my "in use" flag - need to check
// Responsibility transition, the one who reads uncorrupted my_prev will do release.
tmp = tricky_pointer::compare_exchange_strong(s.my_prev, tricky_pointer(predecessor) | FLAG, predecessor, std::memory_order_release);
tmp = tricky_pointer::compare_exchange_strong(s.my_prev, tricky_pointer(predecessor) | FLAG, predecessor, std::memory_order_acquire);
if( !(tricky_pointer(tmp) & FLAG) ) {
// Wait for the predecessor to change my_prev (e.g. during unlink)
// TODO: spin_wait condition seems never reachable
__TBB_ASSERT(tricky_pointer::load(s.my_prev, std::memory_order_relaxed) != (tricky_pointer(predecessor) | FLAG), nullptr);
tricky_pointer::spin_wait_while_eq( s.my_prev, tricky_pointer(predecessor)|FLAG );
// Now owner of predecessor is waiting for _us_ to release its lock
release_internal_lock(*predecessor);
}
@@ -338,13 +365,13 @@ struct queuing_rw_mutex_impl {
tricky_pointer::store(predecessor->my_next, nullptr, std::memory_order_release);
d1::queuing_rw_mutex::scoped_lock* expected = &s;
if( !tricky_pointer::load(s.my_next, std::memory_order_relaxed) && !s.my_mutex->q_tail.compare_exchange_strong(expected, predecessor, std::memory_order_release) ) {
if( !tricky_pointer::load(s.my_next, std::memory_order_acquire) && !s.my_mutex->q_tail.compare_exchange_strong(expected, predecessor, std::memory_order_release) ) {
spin_wait_while_eq( s.my_next, 0U, std::memory_order_acquire );
}
__TBB_ASSERT( !(s.my_next.load(std::memory_order_relaxed) & FLAG), "use of corrupted pointer" );
// ensure acquire semantics of reading 'my_next'
if(d1::queuing_rw_mutex::scoped_lock *const l_next = tricky_pointer::load(s.my_next, std::memory_order_acquire) ) { // I->next != nil, TODO: rename to next after clearing up and adapting the n in the comment two lines below
// my_next is acquired either with load or spin_wait.
if(d1::queuing_rw_mutex::scoped_lock *const l_next = tricky_pointer::load(s.my_next, std::memory_order_relaxed) ) { // I->next != nil, TODO: rename to next after clearing up and adapting the n in the comment two lines below
// Equivalent to I->next->prev = I->prev but protected against (prev[n]&FLAG)!=0
tmp = tricky_pointer::exchange(l_next->my_prev, predecessor, std::memory_order_release);
// I->prev->next = I->next;
@@ -359,9 +386,12 @@ struct queuing_rw_mutex_impl {
d1::queuing_rw_mutex::scoped_lock* next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
if( !next ) {
d1::queuing_rw_mutex::scoped_lock* expected = &s;
if( !s.my_mutex->q_tail.compare_exchange_strong(expected, nullptr, std::memory_order_release) ) {
spin_wait_while_eq( s.my_next, 0U );
next = tricky_pointer::load(s.my_next, std::memory_order_relaxed);
// Release mutex on success otherwise wait for successor publication
if( !s.my_mutex->q_tail.compare_exchange_strong(expected, nullptr,
std::memory_order_release, std::memory_order_relaxed) )
{
spin_wait_while_eq( s.my_next, 0U, std::memory_order_relaxed );
next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
} else {
goto unlock_self;
}
@@ -375,7 +405,8 @@ struct queuing_rw_mutex_impl {
unblock_or_wait_on_internal_lock(s, get_flag(tmp));
}
done:
spin_wait_while_eq( s.my_going, 2U );
// Lifetime synchronization, no need to build happens-before relation
spin_wait_while_eq( s.my_going, 2U, std::memory_order_relaxed );
s.initialize();
}
@@ -384,26 +415,41 @@ struct queuing_rw_mutex_impl {
if ( s.my_state.load(std::memory_order_relaxed) == STATE_ACTIVEREADER ) return true; // Already a reader
ITT_NOTIFY(sync_releasing, s.my_mutex);
s.my_state.store(STATE_READER, std::memory_order_relaxed);
if( ! tricky_pointer::load(s.my_next, std::memory_order_relaxed)) {
d1::queuing_rw_mutex::scoped_lock* next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
if( !next ) {
s.my_state.store(STATE_READER, std::memory_order_seq_cst);
// the following load of q_tail must not be reordered with setting STATE_READER above
if( &s==s.my_mutex->q_tail.load() ) {
if( &s == s.my_mutex->q_tail.load(std::memory_order_seq_cst) ) {
unsigned char old_state = STATE_READER;
s.my_state.compare_exchange_strong(old_state, STATE_ACTIVEREADER, std::memory_order_release);
// When this reader is signaled by previous actor it acquires the mutex.
// We need to build happens-before relation with all other coming readers that will read our ACTIVEREADER
// without blocking on my_going. Therefore, we need to publish ACTIVEREADER with release semantics.
// On fail it is relaxed, because we will build happens-before on my_going.
s.my_state.compare_exchange_strong(old_state, STATE_ACTIVEREADER, std::memory_order_release, std::memory_order_relaxed);
if( old_state==STATE_READER )
return true; // Downgrade completed
}
/* wait for the next to register */
spin_wait_while_eq( s.my_next, 0U );
spin_wait_while_eq(s.my_next, 0U, std::memory_order_relaxed);
next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
}
d1::queuing_rw_mutex::scoped_lock *const next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
__TBB_ASSERT( next, "still no successor at this point!" );
if( next->my_state & STATE_COMBINED_WAITINGREADER )
if( next->my_state.load(std::memory_order_relaxed) & STATE_COMBINED_WAITINGREADER )
next->my_going.store(1U, std::memory_order_release);
else if( next->my_state==STATE_UPGRADE_WAITING )
// If the next is STATE_UPGRADE_WAITING, it is expected to acquire all other released readers via release
// sequence in next->my_state. In that case, we need to preserve release sequence in next->my_state
// contributed by other reader. So, there are two approaches not to break the release sequence:
// 1. Use read-modify-write (exchange) operation to store with release the UPGRADE_LOSER state;
// 2. Acquire the release sequence and store the sequence and UPGRADE_LOSER state.
// The second approach seems better on x86 because it does not involve interlocked operations.
// Therefore, we read next->my_state with acquire while it is not required for else branch to get the
// release sequence.
else if( next->my_state.load(std::memory_order_acquire)==STATE_UPGRADE_WAITING )
// the next waiting for upgrade means this writer was upgraded before.
next->my_state.store(STATE_UPGRADE_LOSER, std::memory_order_relaxed);
s.my_state.store(STATE_ACTIVEREADER, std::memory_order_relaxed);
// To safe release sequence on next->my_state read it with acquire
next->my_state.store(STATE_UPGRADE_LOSER, std::memory_order_release);
s.my_state.store(STATE_ACTIVEREADER, std::memory_order_release);
return true;
}
@@ -419,16 +465,20 @@ struct queuing_rw_mutex_impl {
queuing_rw_mutex::scoped_lock* me = &s;
ITT_NOTIFY(sync_releasing, s.my_mutex);
s.my_state.store(STATE_UPGRADE_REQUESTED, std::memory_order_relaxed);
// Publish ourselves into my_state that other UPGRADE_WAITING actors can acquire our state.
s.my_state.store(STATE_UPGRADE_REQUESTED, std::memory_order_release);
requested:
__TBB_ASSERT( !(s.my_next.load(std::memory_order_relaxed) & FLAG), "use of corrupted pointer!" );
acquire_internal_lock(s);
d1::queuing_rw_mutex::scoped_lock* expected = &s;
if( !s.my_mutex->q_tail.compare_exchange_strong(expected, tricky_pointer(me)|FLAG, std::memory_order_release) ) {
spin_wait_while_eq( s.my_next, 0U );
if( !s.my_mutex->q_tail.compare_exchange_strong(expected, tricky_pointer(me)|FLAG, std::memory_order_acq_rel) ) {
spin_wait_while_eq( s.my_next, 0U, std::memory_order_relaxed );
queuing_rw_mutex::scoped_lock * next;
next = tricky_pointer::fetch_add(s.my_next, FLAG, std::memory_order_acquire);
unsigned short n_state = next->my_state;
// While we were READER the next READER might reach STATE_UPGRADE_WAITING state.
// Therefore, it did not build happens before relation with us and we need to acquire the
// next->my_state to build the happens before relation ourselves
unsigned short n_state = next->my_state.load(std::memory_order_acquire);
/* the next reader can be blocked by our state. the best thing to do is to unblock it */
if( n_state & STATE_COMBINED_WAITINGREADER )
next->my_going.store(1U, std::memory_order_release);
@@ -439,7 +489,7 @@ struct queuing_rw_mutex_impl {
// save next|FLAG for simplicity of following comparisons
tmp = tricky_pointer(next)|FLAG;
for( atomic_backoff b; tricky_pointer::load(s.my_next, std::memory_order_relaxed)==tmp; b.pause() ) {
if( s.my_state & STATE_COMBINED_UPGRADING ) {
if( s.my_state.load(std::memory_order_acquire) & STATE_COMBINED_UPGRADING ) {
if( tricky_pointer::load(s.my_next, std::memory_order_acquire)==tmp )
tricky_pointer::store(s.my_next, next, std::memory_order_relaxed);
goto waiting;
@@ -458,7 +508,11 @@ struct queuing_rw_mutex_impl {
} // if( this != my_mutex->q_tail... )
{
unsigned char old_state = STATE_UPGRADE_REQUESTED;
s.my_state.compare_exchange_strong(old_state, STATE_UPGRADE_WAITING, std::memory_order_acquire);
// If we reach STATE_UPGRADE_WAITING state we do not build happens-before relation with READER on
// left. We delegate this responsibility to READER on left when it try upgrading. Therefore, we are releasing
// on success.
// Otherwise, on fail, we already acquired the next->my_state.
s.my_state.compare_exchange_strong(old_state, STATE_UPGRADE_WAITING, std::memory_order_release, std::memory_order_relaxed);
}
waiting:
__TBB_ASSERT( !( s.my_next.load(std::memory_order_relaxed) & FLAG ), "use of corrupted pointer!" );
@@ -477,11 +531,14 @@ struct queuing_rw_mutex_impl {
// While the predecessor pointer (my_prev) is in use (FLAG is set), we can safely update the node`s state.
// Corrupted pointer transitions responsibility to release the predecessor`s node on us.
unsigned char old_state = STATE_UPGRADE_REQUESTED;
predecessor->my_state.compare_exchange_strong(old_state, STATE_UPGRADE_WAITING, std::memory_order_release);
// Try to build happens before with the upgrading READER on left. If fail, the predecessor state is not
// important for us because it will acquire our state.
predecessor->my_state.compare_exchange_strong(old_state, STATE_UPGRADE_WAITING, std::memory_order_release,
std::memory_order_relaxed);
}
if( !success ) {
// Responsibility transition, the one who reads uncorrupted my_prev will do release.
tmp = tricky_pointer::compare_exchange_strong(s.my_prev, tricky_pointer(predecessor)|FLAG, predecessor, std::memory_order_release);
tmp = tricky_pointer::compare_exchange_strong(s.my_prev, tricky_pointer(predecessor)|FLAG, predecessor, std::memory_order_acquire);
if( tricky_pointer(tmp) & FLAG ) {
tricky_pointer::spin_wait_while_eq(s.my_prev, predecessor);
predecessor = tricky_pointer::load(s.my_prev, std::memory_order_relaxed);
@@ -509,9 +566,6 @@ struct queuing_rw_mutex_impl {
// now wait for the predecessor to finish working with my fields
spin_wait_while_eq( s.my_going, 2U );
// Acquire critical section indirectly from previous owner or directly from predecessor (TODO: not clear).
atomic_fence(std::memory_order_acquire); // on either "my_mutex->q_tail" or "my_going" (TODO: not clear)
bool result = ( s.my_state != STATE_UPGRADE_LOSER );
s.my_state.store(STATE_WRITER, std::memory_order_relaxed);
s.my_going.store(1U, std::memory_order_relaxed);
+2 -2
View File
@@ -51,7 +51,7 @@ void __TBB_EXPORTED_FUNC spawn(d1::task& t, d1::task_group_context& ctx, d1::slo
// Mark isolation
task_accessor::isolation(t) = ed.isolation;
if ( id != d1::no_slot && id != tls->my_arena_index ) {
if ( id != d1::no_slot && id != tls->my_arena_index && id < a->my_num_slots) {
// Allocate proxy task
d1::small_object_allocator alloc{};
auto proxy = alloc.new_object<task_proxy>(static_cast<d1::execution_data&>(ed));
@@ -133,7 +133,7 @@ d1::slot_id __TBB_EXPORTED_FUNC execution_slot(const d1::execution_data* ed) {
return ed_ext->task_disp->m_thread_data->my_arena_index;
} else {
thread_data* td = governor::get_thread_data_if_initialized();
return td ? int(td->my_arena_index) : -1;
return td ? td->my_arena_index : d1::slot_id(-1);
}
}
+1
View File
@@ -231,6 +231,7 @@ d1::task* task_dispatcher::receive_or_steal_task(
waiter.pause(slot);
} // end of nonlocal task retrieval loop
__TBB_ASSERT(is_alive(a.my_guard), nullptr);
if (inbox.is_idle_state(true)) {
inbox.set_is_idle(false);
}
+1 -1
View File
@@ -257,7 +257,7 @@ bool market::propagate_task_group_state(std::atomic<T> d1::task_group_context::*
// Propagate to all workers and external threads and sync up their local epochs with the global one
unsigned num_workers = my_first_unused_worker_idx;
for (unsigned i = 0; i < num_workers; ++i) {
thread_data* td = my_workers[i];
thread_data* td = my_workers[i].load(std::memory_order_acquire);
// If the worker is only about to be registered, skip it.
if (td)
td->propagate_task_group_state(mptr_state, src, new_state);
-12
View File
@@ -74,18 +74,6 @@ private:
tls_key_t my_key;
};
//! More advanced TLS support template class.
/** It supports RAII and to some extent mimic __declspec(thread) variables. */
template <typename T>
class tls : public basic_tls<T> {
typedef basic_tls<T> base;
public:
tls() { base::create(); }
~tls() { base::destroy(); }
T operator=(T value) { base::set(value); return value; }
operator T() { return base::get(); }
};
} // namespace r1
} // namespace detail
} // namespace tbb
+3 -5
View File
@@ -18,7 +18,7 @@ endif()
set(CMAKE_SKIP_BUILD_RPATH TRUE)
function(tbbbind_build TBBBIND_NAME REQUIRED_HWLOC_TARGET)
if (NOT TARGET ${REQUIRED_HWLOC_TARGET} AND NOT DEFINED HWLOC_LIBRARIES)
if (NOT TARGET ${REQUIRED_HWLOC_TARGET})
message(STATUS "HWLOC target ${REQUIRED_HWLOC_TARGET} doesn't exist."
" The ${TBBBIND_NAME} target cannot be created")
return()
@@ -70,7 +70,6 @@ function(tbbbind_build TBBBIND_NAME REQUIRED_HWLOC_TARGET)
${TBB_LIB_LINK_FLAGS}
${TBB_COMMON_LINK_FLAGS}
${TBB_IPO_LINK_FLAGS}
${HWLOC_LIBRARY_DIRS} # pkg-config defined
)
else()
target_link_libraries(${TBBBIND_NAME}
@@ -78,7 +77,6 @@ function(tbbbind_build TBBBIND_NAME REQUIRED_HWLOC_TARGET)
${TBB_LIB_LINK_FLAGS}
${TBB_COMMON_LINK_FLAGS}
${TBB_IPO_LINK_FLAGS}
${HWLOC_LIBRARY_DIRS} # pkg-config defined
)
endif()
@@ -97,9 +95,9 @@ function(tbbbind_build TBBBIND_NAME REQUIRED_HWLOC_TARGET)
endif()
endfunction()
if (NOT DEFINED HWLOC_TARGET_EXPLICITLY_DEFINED AND DEFINED HWLOC_LIBRARIES)
if (NOT DEFINED HWLOC_TARGET_EXPLICITLY_DEFINED AND TARGET PkgConfig::HWLOC)
message(STATUS "The ${TBBBIND_LIBRARY_NAME} target will be configured using the HWLOC ${HWLOC_VERSION}")
tbbbind_build(${TBBBIND_LIBRARY_NAME} ${HWLOC_LIBRARIES})
tbbbind_build(${TBBBIND_LIBRARY_NAME} PkgConfig::HWLOC)
else()
tbbbind_build(tbbbind HWLOC::hwloc_1_11)
tbbbind_build(tbbbind_2_0 HWLOC::hwloc_2 )
+1 -1
View File
@@ -25,7 +25,7 @@
# define __ARCH_ipf 1
# elif defined(_M_IX86)||defined(__i386__) // the latter for MinGW support
# define __ARCH_x86_32 1
# elif defined(_M_ARM)
# elif defined(_M_ARM) || defined(_M_ARM64)
# define __ARCH_other 1
# else
# error Unknown processor architecture for Windows
+6 -1
View File
@@ -967,9 +967,15 @@ void *Backend::remap(void *ptr, size_t oldSize, size_t newSize, size_t alignment
return NULL;
regionList.remove(oldRegion);
// The deallocation should be registered in address range before mremap to
// prevent a race condition with allocation on another thread.
// (OS can reuse the memory and registerAlloc will be missed on another thread)
usedAddrRange.registerFree((uintptr_t)oldRegion, (uintptr_t)oldRegion + oldRegionSize);
void *ret = mremap(oldRegion, oldRegion->allocSz, requestSize, MREMAP_MAYMOVE);
if (MAP_FAILED == ret) { // can't remap, revert and leave
regionList.add(oldRegion);
usedAddrRange.registerAlloc((uintptr_t)oldRegion, (uintptr_t)oldRegion + oldRegionSize);
return NULL;
}
MemRegion *region = (MemRegion*)ret;
@@ -1001,7 +1007,6 @@ void *Backend::remap(void *ptr, size_t oldSize, size_t newSize, size_t alignment
MALLOC_ASSERT((uintptr_t)lmb + lmb->unalignedSize >=
(uintptr_t)object + lmb->objectSize, "An object must fit to the block.");
usedAddrRange.registerFree((uintptr_t)oldRegion, (uintptr_t)oldRegion + oldRegionSize);
usedAddrRange.registerAlloc((uintptr_t)region, (uintptr_t)region + requestSize);
totalMemSize.fetch_add(region->allocSz - oldRegionSize);
+66 -66
View File
@@ -32,10 +32,10 @@ struct BackRefBlock : public BlockI {
// list of all blocks that were allocated from raw mem (i.e., not from backend)
BackRefBlock *nextRawMemBlock;
std::atomic<int> allocatedCount; // the number of objects allocated
BackRefIdx::master_t myNum; // the index in the master
BackRefIdx::main_t myNum; // the index in the main
MallocMutex blockMutex;
// true if this block has been added to the listForUse chain,
// modifications protected by masterMutex
// modifications protected by mainMutex
std::atomic<bool> addedToForUse;
BackRefBlock(const BackRefBlock *blockToUse, intptr_t num) :
@@ -44,8 +44,8 @@ struct BackRefBlock : public BlockI {
addedToForUse(false) {
memset(&blockMutex, 0, sizeof(MallocMutex));
MALLOC_ASSERT(!(num >> CHAR_BIT*sizeof(BackRefIdx::master_t)),
"index in BackRefMaster must fit to BackRefIdx::master");
MALLOC_ASSERT(!(num >> CHAR_BIT*sizeof(BackRefIdx::main_t)),
"index in BackRefMain must fit to BackRefIdx::main");
}
// clean all but header
void zeroSet() { memset(this+1, 0, BackRefBlock::bytes-sizeof(BackRefBlock)); }
@@ -55,19 +55,19 @@ struct BackRefBlock : public BlockI {
// max number of backreference pointers in slab block
static const int BR_MAX_CNT = (BackRefBlock::bytes-sizeof(BackRefBlock))/sizeof(void*);
struct BackRefMaster {
struct BackRefMain {
/* On 64-bit systems a slab block can hold up to ~2K back pointers to slab blocks
* or large objects, so it can address at least 32MB. The master array of 256KB
* or large objects, so it can address at least 32MB. The main array of 256KB
* holds 32K pointers to such blocks, addressing ~1 TB.
* On 32-bit systems there is ~4K back pointers in a slab block, so ~64MB can be addressed.
* The master array of 8KB holds 2K pointers to leaves, so ~128 GB can addressed.
* The main array of 8KB holds 2K pointers to leaves, so ~128 GB can addressed.
*/
static const size_t bytes = sizeof(uintptr_t)>4? 256*1024 : 8*1024;
static const int dataSz;
/* space is reserved for master table and 4 leaves
/* space is reserved for main table and 4 leaves
taking into account VirtualAlloc allocation granularity */
static const int leaves = 4;
static const size_t masterSize = BackRefMaster::bytes+leaves*BackRefBlock::bytes;
static const size_t mainSize = BackRefMain::bytes+leaves*BackRefBlock::bytes;
// The size of memory request for a few more leaf blocks;
// selected to match VirtualAlloc granularity
static const size_t blockSpaceSize = 64*1024;
@@ -87,65 +87,65 @@ struct BackRefMaster {
bool requestNewSpace();
};
const int BackRefMaster::dataSz
= 1+(BackRefMaster::bytes-sizeof(BackRefMaster))/sizeof(BackRefBlock*);
const int BackRefMain::dataSz
= 1+(BackRefMain::bytes-sizeof(BackRefMain))/sizeof(BackRefBlock*);
static MallocMutex masterMutex;
static std::atomic<BackRefMaster*> backRefMaster;
static MallocMutex mainMutex;
static std::atomic<BackRefMain*> backRefMain;
bool initBackRefMaster(Backend *backend)
bool initBackRefMain(Backend *backend)
{
bool rawMemUsed;
BackRefMaster *master =
(BackRefMaster*)backend->getBackRefSpace(BackRefMaster::masterSize,
BackRefMain *main =
(BackRefMain*)backend->getBackRefSpace(BackRefMain::mainSize,
&rawMemUsed);
if (! master)
if (! main)
return false;
master->backend = backend;
master->listForUse.store(nullptr, std::memory_order_relaxed);
master->allRawMemBlocks = nullptr;
master->rawMemUsed = rawMemUsed;
master->lastUsed = -1;
memset(&master->requestNewSpaceMutex, 0, sizeof(MallocMutex));
for (int i=0; i<BackRefMaster::leaves; i++) {
BackRefBlock *bl = (BackRefBlock*)((uintptr_t)master + BackRefMaster::bytes + i*BackRefBlock::bytes);
main->backend = backend;
main->listForUse.store(nullptr, std::memory_order_relaxed);
main->allRawMemBlocks = nullptr;
main->rawMemUsed = rawMemUsed;
main->lastUsed = -1;
memset(&main->requestNewSpaceMutex, 0, sizeof(MallocMutex));
for (int i=0; i<BackRefMain::leaves; i++) {
BackRefBlock *bl = (BackRefBlock*)((uintptr_t)main + BackRefMain::bytes + i*BackRefBlock::bytes);
bl->zeroSet();
master->initEmptyBackRefBlock(bl);
main->initEmptyBackRefBlock(bl);
if (i)
master->addToForUseList(bl);
main->addToForUseList(bl);
else // active leaf is not needed in listForUse
master->active.store(bl, std::memory_order_relaxed);
main->active.store(bl, std::memory_order_relaxed);
}
// backRefMaster is read in getBackRef, so publish it in consistent state
backRefMaster.store(master, std::memory_order_release);
// backRefMain is read in getBackRef, so publish it in consistent state
backRefMain.store(main, std::memory_order_release);
return true;
}
#if __TBB_SOURCE_DIRECTLY_INCLUDED
void destroyBackRefMaster(Backend *backend)
void destroyBackRefMain(Backend *backend)
{
if (backRefMaster.load(std::memory_order_acquire)) { // Is initBackRefMaster() called?
for (BackRefBlock *curr = backRefMaster.load(std::memory_order_relaxed)->allRawMemBlocks; curr; ) {
if (backRefMain.load(std::memory_order_acquire)) { // Is initBackRefMain() called?
for (BackRefBlock *curr = backRefMain.load(std::memory_order_relaxed)->allRawMemBlocks; curr; ) {
BackRefBlock *next = curr->nextRawMemBlock;
// allRawMemBlocks list is only for raw mem blocks
backend->putBackRefSpace(curr, BackRefMaster::blockSpaceSize,
backend->putBackRefSpace(curr, BackRefMain::blockSpaceSize,
/*rawMemUsed=*/true);
curr = next;
}
backend->putBackRefSpace(backRefMaster.load(std::memory_order_relaxed), BackRefMaster::masterSize,
backRefMaster.load(std::memory_order_relaxed)->rawMemUsed);
backend->putBackRefSpace(backRefMain.load(std::memory_order_relaxed), BackRefMain::mainSize,
backRefMain.load(std::memory_order_relaxed)->rawMemUsed);
}
}
#endif
void BackRefMaster::addToForUseList(BackRefBlock *bl)
void BackRefMain::addToForUseList(BackRefBlock *bl)
{
bl->nextForUse = listForUse.load(std::memory_order_relaxed);
listForUse.store(bl, std::memory_order_relaxed);
bl->addedToForUse.store(true, std::memory_order_relaxed);
}
void BackRefMaster::initEmptyBackRefBlock(BackRefBlock *newBl)
void BackRefMain::initEmptyBackRefBlock(BackRefBlock *newBl)
{
intptr_t nextLU = lastUsed+1;
new (newBl) BackRefBlock(newBl, nextLU);
@@ -156,13 +156,13 @@ void BackRefMaster::initEmptyBackRefBlock(BackRefBlock *newBl)
lastUsed.store(nextLU, std::memory_order_release);
}
bool BackRefMaster::requestNewSpace()
bool BackRefMain::requestNewSpace()
{
bool isRawMemUsed;
static_assert(!(blockSpaceSize % BackRefBlock::bytes),
"Must request space for whole number of blocks.");
if (backRefMaster.load(std::memory_order_relaxed)->dataSz <= lastUsed + 1) // no space in master
if (backRefMain.load(std::memory_order_relaxed)->dataSz <= lastUsed + 1) // no space in main
return false;
// only one thread at a time may add blocks
@@ -173,28 +173,28 @@ bool BackRefMaster::requestNewSpace()
BackRefBlock *newBl = (BackRefBlock*)backend->getBackRefSpace(blockSpaceSize, &isRawMemUsed);
if (!newBl) return false;
// touch a page for the 1st time without taking masterMutex ...
// touch a page for the 1st time without taking mainMutex ...
for (BackRefBlock *bl = newBl; (uintptr_t)bl < (uintptr_t)newBl + blockSpaceSize;
bl = (BackRefBlock*)((uintptr_t)bl + BackRefBlock::bytes)) {
bl->zeroSet();
}
MallocMutex::scoped_lock lock(masterMutex); // ... and share under lock
MallocMutex::scoped_lock lock(mainMutex); // ... and share under lock
const size_t numOfUnusedIdxs = backRefMaster.load(std::memory_order_relaxed)->dataSz - lastUsed - 1;
if (numOfUnusedIdxs <= 0) { // no space in master under lock, roll back
const size_t numOfUnusedIdxs = backRefMain.load(std::memory_order_relaxed)->dataSz - lastUsed - 1;
if (numOfUnusedIdxs <= 0) { // no space in main under lock, roll back
backend->putBackRefSpace(newBl, blockSpaceSize, isRawMemUsed);
return false;
}
// It's possible that only part of newBl is used, due to lack of indices in master.
// It's possible that only part of newBl is used, due to lack of indices in main.
// This is OK as such underutilization is possible only once for backreferneces table.
int blocksToUse = min(numOfUnusedIdxs, blockSpaceSize / BackRefBlock::bytes);
// use the first block in the batch to maintain the list of "raw" memory
// to be released at shutdown
if (isRawMemUsed) {
newBl->nextRawMemBlock = backRefMaster.load(std::memory_order_relaxed)->allRawMemBlocks;
backRefMaster.load(std::memory_order_relaxed)->allRawMemBlocks = newBl;
newBl->nextRawMemBlock = backRefMain.load(std::memory_order_relaxed)->allRawMemBlocks;
backRefMain.load(std::memory_order_relaxed)->allRawMemBlocks = newBl;
}
for (BackRefBlock *bl = newBl; blocksToUse>0; bl = (BackRefBlock*)((uintptr_t)bl + BackRefBlock::bytes), blocksToUse--) {
initEmptyBackRefBlock(bl);
@@ -207,7 +207,7 @@ bool BackRefMaster::requestNewSpace()
return true;
}
BackRefBlock *BackRefMaster::findFreeBlock()
BackRefBlock *BackRefMain::findFreeBlock()
{
BackRefBlock* active_block = active.load(std::memory_order_acquire);
MALLOC_ASSERT(active_block, ASSERT_TEXT);
@@ -216,7 +216,7 @@ BackRefBlock *BackRefMaster::findFreeBlock()
return active_block;
if (listForUse.load(std::memory_order_relaxed)) { // use released list
MallocMutex::scoped_lock lock(masterMutex);
MallocMutex::scoped_lock lock(mainMutex);
if (active_block->allocatedCount.load(std::memory_order_relaxed) == BR_MAX_CNT) {
active_block = listForUse.load(std::memory_order_relaxed);
@@ -235,16 +235,16 @@ BackRefBlock *BackRefMaster::findFreeBlock()
void *getBackRef(BackRefIdx backRefIdx)
{
// !backRefMaster means no initialization done, so it can't be valid memory
// !backRefMain means no initialization done, so it can't be valid memory
// see addEmptyBackRefBlock for fences around lastUsed
if (!(backRefMaster.load(std::memory_order_acquire))
|| backRefIdx.getMaster() > (backRefMaster.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_acquire))
if (!(backRefMain.load(std::memory_order_acquire))
|| backRefIdx.getMain() > (backRefMain.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_acquire))
|| backRefIdx.getOffset() >= BR_MAX_CNT)
{
return NULL;
}
std::atomic<void*>& backRefEntry = *(std::atomic<void*>*)(
(uintptr_t)backRefMaster.load(std::memory_order_relaxed)->backRefBl[backRefIdx.getMaster()]
(uintptr_t)backRefMain.load(std::memory_order_relaxed)->backRefBl[backRefIdx.getMain()]
+ sizeof(BackRefBlock) + backRefIdx.getOffset() * sizeof(std::atomic<void*>)
);
return backRefEntry.load(std::memory_order_relaxed);
@@ -252,9 +252,9 @@ void *getBackRef(BackRefIdx backRefIdx)
void setBackRef(BackRefIdx backRefIdx, void *newPtr)
{
MALLOC_ASSERT(backRefIdx.getMaster()<=backRefMaster.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed)
MALLOC_ASSERT(backRefIdx.getMain()<=backRefMain.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed)
&& backRefIdx.getOffset()<BR_MAX_CNT, ASSERT_TEXT);
((std::atomic<void*>*)((uintptr_t)backRefMaster.load(std::memory_order_relaxed)->backRefBl[backRefIdx.getMaster()]
((std::atomic<void*>*)((uintptr_t)backRefMain.load(std::memory_order_relaxed)->backRefBl[backRefIdx.getMain()]
+ sizeof(BackRefBlock) + backRefIdx.getOffset() * sizeof(void*)))->store(newPtr, std::memory_order_relaxed);
}
@@ -266,8 +266,8 @@ BackRefIdx BackRefIdx::newBackRef(bool largeObj)
bool lastBlockFirstUsed = false;
do {
MALLOC_ASSERT(backRefMaster.load(std::memory_order_relaxed), ASSERT_TEXT);
blockToUse = backRefMaster.load(std::memory_order_relaxed)->findFreeBlock();
MALLOC_ASSERT(backRefMain.load(std::memory_order_relaxed), ASSERT_TEXT);
blockToUse = backRefMain.load(std::memory_order_relaxed)->findFreeBlock();
if (!blockToUse)
return BackRefIdx();
toUse = NULL;
@@ -294,7 +294,7 @@ BackRefIdx BackRefIdx::newBackRef(bool largeObj)
}
if (toUse) {
if (!blockToUse->allocatedCount.load(std::memory_order_relaxed) &&
!backRefMaster.load(std::memory_order_relaxed)->listForUse.load(std::memory_order_relaxed)) {
!backRefMain.load(std::memory_order_relaxed)->listForUse.load(std::memory_order_relaxed)) {
lastBlockFirstUsed = true;
}
blockToUse->allocatedCount.store(blockToUse->allocatedCount.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);
@@ -304,9 +304,9 @@ BackRefIdx BackRefIdx::newBackRef(bool largeObj)
// The first thread that uses the last block requests new space in advance;
// possible failures are ignored.
if (lastBlockFirstUsed)
backRefMaster.load(std::memory_order_relaxed)->requestNewSpace();
backRefMain.load(std::memory_order_relaxed)->requestNewSpace();
res.master = blockToUse->myNum;
res.main = blockToUse->myNum;
uintptr_t offset =
((uintptr_t)toUse - ((uintptr_t)blockToUse + sizeof(BackRefBlock)))/sizeof(void*);
// Is offset too big?
@@ -320,9 +320,9 @@ BackRefIdx BackRefIdx::newBackRef(bool largeObj)
void removeBackRef(BackRefIdx backRefIdx)
{
MALLOC_ASSERT(!backRefIdx.isInvalid(), ASSERT_TEXT);
MALLOC_ASSERT(backRefIdx.getMaster()<=backRefMaster.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed)
MALLOC_ASSERT(backRefIdx.getMain()<=backRefMain.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed)
&& backRefIdx.getOffset()<BR_MAX_CNT, ASSERT_TEXT);
BackRefBlock *currBlock = backRefMaster.load(std::memory_order_relaxed)->backRefBl[backRefIdx.getMaster()];
BackRefBlock *currBlock = backRefMain.load(std::memory_order_relaxed)->backRefBl[backRefIdx.getMain()];
std::atomic<void*>& backRefEntry = *(std::atomic<void*>*)((uintptr_t)currBlock + sizeof(BackRefBlock)
+ backRefIdx.getOffset()*sizeof(std::atomic<void*>));
MALLOC_ASSERT(((uintptr_t)&backRefEntry >(uintptr_t)currBlock &&
@@ -342,12 +342,12 @@ void removeBackRef(BackRefIdx backRefIdx)
}
// TODO: do we need double-check here?
if (!currBlock->addedToForUse.load(std::memory_order_relaxed) &&
currBlock!=backRefMaster.load(std::memory_order_relaxed)->active.load(std::memory_order_relaxed)) {
MallocMutex::scoped_lock lock(masterMutex);
currBlock!=backRefMain.load(std::memory_order_relaxed)->active.load(std::memory_order_relaxed)) {
MallocMutex::scoped_lock lock(mainMutex);
if (!currBlock->addedToForUse.load(std::memory_order_relaxed) &&
currBlock!=backRefMaster.load(std::memory_order_relaxed)->active.load(std::memory_order_relaxed))
backRefMaster.load(std::memory_order_relaxed)->addToForUseList(currBlock);
currBlock!=backRefMain.load(std::memory_order_relaxed)->active.load(std::memory_order_relaxed))
backRefMain.load(std::memory_order_relaxed)->addToForUseList(currBlock);
}
}
+2 -2
View File
@@ -2044,7 +2044,7 @@ static bool initMemoryManager()
extMemPool.init(0, NULL, NULL, granularity,
/*keepAllMemory=*/false, /*fixedPool=*/false);
// TODO: extMemPool.init() to not allocate memory
if (!initOk || !initBackRefMaster(&defaultMemPool->extMemPool.backend) || !ThreadId::init())
if (!initOk || !initBackRefMain(&defaultMemPool->extMemPool.backend) || !ThreadId::init())
return false;
MemoryPool::initDefaultPool();
// init() is required iff initMemoryManager() is called
@@ -2916,7 +2916,7 @@ extern "C" void __TBB_mallocProcessShutdownNotification(bool windows_process_dyi
on thread termination when then the tbbmalloc code can be already unloaded.
*/
defaultMemPool->destroy();
destroyBackRefMaster(&defaultMemPool->extMemPool.backend);
destroyBackRefMain(&defaultMemPool->extMemPool.backend);
ThreadId::destroy(); // Delete key for thread id
hugePages.reset();
// new total malloc initialization is possible after this point
+14 -14
View File
@@ -288,31 +288,31 @@ public:
/* Large objects entities */
#include "large_objects.h"
// select index size for BackRefMaster based on word size: default is uint32_t,
// select index size for BackRefMain based on word size: default is uint32_t,
// uint16_t for 32-bit platforms
template<bool>
struct MasterIndexSelect {
typedef uint32_t master_type;
struct MainIndexSelect {
typedef uint32_t main_type;
};
template<>
struct MasterIndexSelect<false> {
typedef uint16_t master_type;
struct MainIndexSelect<false> {
typedef uint16_t main_type;
};
class BackRefIdx { // composite index to backreference array
public:
typedef MasterIndexSelect<4 < sizeof(uintptr_t)>::master_type master_t;
typedef MainIndexSelect<4 < sizeof(uintptr_t)>::main_type main_t;
private:
static const master_t invalid = ~master_t(0);
master_t master; // index in BackRefMaster
static const main_t invalid = ~main_t(0);
main_t main; // index in BackRefMain
uint16_t largeObj:1; // is this object "large"?
uint16_t offset :15; // offset from beginning of BackRefBlock
public:
BackRefIdx() : master(invalid), largeObj(0), offset(0) {}
bool isInvalid() const { return master == invalid; }
BackRefIdx() : main(invalid), largeObj(0), offset(0) {}
bool isInvalid() const { return main == invalid; }
bool isLargeObject() const { return largeObj; }
master_t getMaster() const { return master; }
main_t getMain() const { return main; }
uint16_t getOffset() const { return offset; }
#if __TBB_USE_THREAD_SANITIZER
@@ -320,7 +320,7 @@ public:
__attribute__((no_sanitize("thread")))
BackRefIdx dereference(const BackRefIdx* ptr) {
BackRefIdx idx;
idx.master = ptr->master;
idx.main = ptr->main;
idx.largeObj = ptr->largeObj;
idx.offset = ptr->offset;
return idx;
@@ -733,8 +733,8 @@ public:
unsigned int getThreadId();
bool initBackRefMaster(Backend *backend);
void destroyBackRefMaster(Backend *backend);
bool initBackRefMain(Backend *backend);
void destroyBackRefMain(Backend *backend);
void removeBackRef(BackRefIdx backRefIdx);
void setBackRef(BackRefIdx backRefIdx, void *newPtr);
void *getBackRef(BackRefIdx backRefIdx);
+243 -243
View File
@@ -33,6 +33,8 @@ function(tbb_add_test)
PRIVATE
${TBB_CXX_STD_FLAG}
${TBB_WARNING_LEVEL}
# Warning suppression C4324: structure was padded due to alignment specifier
$<$<STREQUAL:${MSVC_CXX_ARCHITECTURE_ID},ARM64>:/wd4324>
${TBB_TEST_WARNING_FLAGS}
${TBB_TEST_COMPILE_FLAGS}
${TBB_COMMON_COMPILE_FLAGS}
@@ -256,21 +258,12 @@ function(tbb_add_tbbbind_test)
set_property(TEST ${_tbbbind_test_NAME} PROPERTY ENVIRONMENT TBB_VERSION=1 APPEND)
# Handle the case when HWLOC was found using pkg-config
if (NOT DEFINED HWLOC_TARGET_EXPLICITLY_DEFINED AND DEFINED HWLOC_LIBRARIES)
if (NOT DEFINED HWLOC_TARGET_EXPLICITLY_DEFINED AND TARGET PkgConfig::HWLOC)
set_tests_properties(${_tbbbind_test_NAME} PROPERTIES
PASS_REGULAR_EXPRESSION "oneTBB: TBBBIND.*${TBBBIND_LIBRARY_NAME}"
FAIL_REGULAR_EXPRESSION "Status: FAILURE!"
)
set_property(
TARGET ${_tbbbind_test_NAME}
PROPERTY LINK_LIBRARIES ${HWLOC_LIBRARY_DIRS} ${HWLOC_LIBRARIES}
APPEND
)
set_property(
TARGET ${_tbbbind_test_NAME}
PROPERTY INCLUDE_DIRECTORIES ${HWLOC_INCLUDE_DIRS}
APPEND
)
target_link_libraries(${_tbbbind_test_NAME} PRIVATE PkgConfig::HWLOC)
target_compile_definitions(${_tbbbind_test_NAME} PRIVATE __TBB_HWLOC_VALID_ENVIRONMENT)
return()
endif()
@@ -353,7 +346,7 @@ if (TBB_FOUND)
unset(_tbb_test_components)
endif()
# Find Intel(R) Software Development Emulator to run test_mutex and conformance_mutex for coverage
# Find Intel(R) Software Development Emulator to run test_mutex and conformance_mutex for coverage
set(_sde_find_name sde)
if (UNIX AND TBB_ARCH EQUAL 64)
@@ -370,246 +363,253 @@ unset(_sde_find_name)
# Common target for the tbbbind related tests
add_custom_target(test_suite_arena_constraints)
# Define the tests
tbb_add_test(SUBDIR tbb NAME test_tick_count DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_allocators DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_arena_priorities DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_dynamic_link DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_collaborative_call_once DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_lru_cache DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_unordered_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_unordered_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_priority_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_partitioner DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_for DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_for_each DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_reduce DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_sort DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_invoke DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_scan DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_pipeline DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_eh_algorithms DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_blocked_range DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_vector DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_task_group DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_hash_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_task_arena DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_enumerable_thread_specific DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_resumable_tasks DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_mutex DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_adaptive_mutex DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_function_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_multifunction_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_broadcast_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_buffer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_composite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_continue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_eh_flow_graph DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_flow_graph DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_flow_graph_priorities DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_flow_graph_whitebox DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_indexer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_join_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_join_node_key_matching DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_join_node_msg_key_matching DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_limiter_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_priority_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_sequencer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_split_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_tagged_msg DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_overwrite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_write_once_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_async_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_input_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_profiling DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_queue_whitebox DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_intrusive_list DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_semaphore DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_environment_whitebox DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_hw_concurrency DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_eh_thread DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_global_control DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_task DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_monitor DEPENDENCIES TBB::tbb)
if (TARGET TBB::tbb)
# Define the tests
tbb_add_test(SUBDIR tbb NAME test_tick_count DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_allocators DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_arena_priorities DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_dynamic_link DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_collaborative_call_once DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_lru_cache DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_unordered_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_unordered_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_priority_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_partitioner DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_for DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_for_each DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_reduce DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_sort DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_invoke DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_scan DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_parallel_pipeline DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_eh_algorithms DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_blocked_range DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_vector DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_task_group DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_hash_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_task_arena DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_enumerable_thread_specific DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_resumable_tasks DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_mutex DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_adaptive_mutex DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_function_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_multifunction_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_broadcast_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_buffer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_composite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_continue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_eh_flow_graph DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_flow_graph DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_flow_graph_priorities DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_flow_graph_whitebox DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_indexer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_join_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_join_node_key_matching DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_join_node_msg_key_matching DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_limiter_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_priority_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_sequencer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_split_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_tagged_msg DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_overwrite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_write_once_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_async_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_input_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_profiling DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_queue_whitebox DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_intrusive_list DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_semaphore DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_environment_whitebox DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_hw_concurrency DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_eh_thread DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_global_control DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_task DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_concurrent_monitor DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR tbb NAME test_scheduler_mix DEPENDENCIES TBB::tbb)
# test_handle_perror
tbb_add_test(SUBDIR tbb NAME test_handle_perror)
target_include_directories(test_handle_perror PRIVATE
$<TARGET_PROPERTY:TBB::tbb,INTERFACE_INCLUDE_DIRECTORIES>
)
# HWLOC related test
tbb_add_tbbbind_test(SUBDIR tbb NAME test_arena_constraints)
if (NOT "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "mips")
# TODO: Fix for MIPS
tbb_add_test(SUBDIR tbb NAME test_tbb_fork DEPENDENCIES TBB::tbb)
endif()
tbb_add_test(SUBDIR tbb NAME test_tbb_header DEPENDENCIES TBB::tbb)
target_sources(test_tbb_header PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tbb/test_tbb_header_secondary.cpp)
if (TBB_OPENMP_FLAG AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "(mips)")
tbb_add_test(SUBDIR tbb NAME test_openmp DEPENDENCIES TBB::tbb)
set_target_properties(test_openmp PROPERTIES COMPILE_FLAGS ${TBB_OPENMP_FLAG})
if (NOT TBB_OPENMP_NO_LINK_FLAG)
set_target_properties(test_openmp PROPERTIES LINK_FLAGS ${TBB_OPENMP_FLAG})
endif()
endif()
# Define the conformance tests
tbb_add_test(SUBDIR conformance NAME conformance_tick_count DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_allocators DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_mutex DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_task_group_context DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_task_arena DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_lru_cache DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_unordered_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_unordered_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_priority_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_for DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_for_each DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_reduce DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_scan DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_sort DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_pipeline DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_invoke DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_range DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_range2d DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_range3d DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_rangeNd DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_vector DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_global_control DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_hash_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_enumerable_thread_specific DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_combinable DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_resumable_tasks DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_version DEPENDENCIES TBB::tbb)
# functional nodes conformance
tbb_add_test(SUBDIR conformance NAME conformance_function_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_multifunction_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_input_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_continue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_async_node DEPENDENCIES TBB::tbb)
# buffering nodes conformance
tbb_add_test(SUBDIR conformance NAME conformance_overwrite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_write_once_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_buffer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_priority_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_sequencer_node DEPENDENCIES TBB::tbb)
# service nodes conformance
tbb_add_test(SUBDIR conformance NAME conformance_limiter_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_broadcast_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_composite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_indexer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_split_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_join_node DEPENDENCIES TBB::tbb)
# flowraph auxiliary conformance
# TODO: add conformance tests for graph_node, continue_msg, tagged_msg, copy_body, input_port, output_port, make_edge, remove_edge
tbb_add_test(SUBDIR conformance NAME conformance_graph DEPENDENCIES TBB::tbb)
# HWLOC related conformance
tbb_add_tbbbind_test(SUBDIR conformance NAME conformance_arena_constraints)
if (MSVC AND BUILD_SHARED_LIBS AND CMAKE_VERSION VERSION_GREATER 3.13) # LINK_OPTIONS property first appeared in 3.13
# version of the CMake
tbb_add_test(SUBDIR tbb NAME test_implicit_linkage_on_windows)
# TODO: consider setting environment instead of passing additional
# compiler and linker options
target_include_directories(test_implicit_linkage_on_windows PRIVATE
$<TARGET_PROPERTY:TBB::tbb,INTERFACE_INCLUDE_DIRECTORIES>)
set_target_properties(test_implicit_linkage_on_windows PROPERTIES
LINK_OPTIONS /LIBPATH:$<TARGET_LINKER_FILE_DIR:TBB::tbb>)
add_dependencies(test_implicit_linkage_on_windows TBB::tbb)
endif()
# TBB allocator tests
if (NOT "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "mips")
# Define TBB malloc tests
tbb_add_test(SUBDIR tbbmalloc NAME test_scalable_allocator DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_pools DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_init_shutdown DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_regression DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_shutdown_hang DEPENDENCIES TBB::tbb TBB::tbbmalloc)
if (NOT (WINDOWS_STORE OR TBB_WINDOWS_DRIVER))
# TODO: Consider adding following tests on WINDOWS_STORE and TBB_WINDOWS_DRIVER platforms
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_compliance DEPENDENCIES TBB::tbbmalloc)
tbb_add_lib_test(SUBDIR tbbmalloc NAME test_malloc_used_by_lib DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_used_by_lib DEPENDENCIES _test_malloc_used_by_lib)
tbb_add_lib_test(SUBDIR tbbmalloc NAME test_malloc_lib_unload)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_lib_unload DEPENDENCIES _test_malloc_lib_unload)
endif()
enable_language(C)
tbb_add_c_test(SUBDIR tbbmalloc NAME test_malloc_pure_c DEPENDENCIES TBB::tbbmalloc)
# ----------------------------------------------------------------------------------------
# Whitebox testing
add_executable(test_malloc_whitebox tbbmalloc/test_malloc_whitebox.cpp)
target_include_directories(test_malloc_whitebox
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../include
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(test_malloc_whitebox PRIVATE __TBBMALLOC_BUILD)
target_compile_options(test_malloc_whitebox
PRIVATE
${TBB_CXX_STD_FLAG}
${TBB_WARNING_SUPPRESS}
${TBB_TEST_COMPILE_FLAGS}
${TBB_COMMON_COMPILE_FLAGS}
${TBBMALLOC_LIB_COMPILE_FLAGS}
# test_handle_perror
tbb_add_test(SUBDIR tbb NAME test_handle_perror)
target_include_directories(test_handle_perror PRIVATE
$<TARGET_PROPERTY:TBB::tbb,INTERFACE_INCLUDE_DIRECTORIES>
)
if (ANDROID_PLATFORM)
target_link_libraries(test_malloc_whitebox PRIVATE -rdynamic) # for the test_dynamic_link
add_test(NAME test_malloc_whitebox
COMMAND ${CMAKE_COMMAND}
-DBINARIES_PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
-DTEST_NAME=test_malloc_whitebox
-P ${PROJECT_SOURCE_DIR}/cmake/android/test_launcher.cmake)
else()
add_test(NAME test_malloc_whitebox COMMAND test_malloc_whitebox --force-colors=1)
# HWLOC related test
tbb_add_tbbbind_test(SUBDIR tbb NAME test_arena_constraints)
if (NOT "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "mips")
# TODO: Fix for MIPS
tbb_add_test(SUBDIR tbb NAME test_tbb_fork DEPENDENCIES TBB::tbb)
endif()
if (COMMAND target_link_options)
target_link_options(test_malloc_whitebox PRIVATE ${TBB_COMMON_LINK_FLAGS})
else()
target_link_libraries(test_malloc_whitebox PRIVATE ${TBB_COMMON_LINK_FLAGS})
endif()
target_link_libraries(test_malloc_whitebox PRIVATE Threads::Threads ${TBB_COMMON_LINK_LIBS})
# ------------------------------------------------------------------------------------------
tbb_add_test(SUBDIR tbb NAME test_tbb_header DEPENDENCIES TBB::tbb)
target_sources(test_tbb_header PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tbb/test_tbb_header_secondary.cpp)
if (TBB_OPENMP_FLAG AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "(mips)")
tbb_add_test(SUBDIR tbb NAME test_openmp DEPENDENCIES TBB::tbb)
# Define TBB malloc conformance tests
# tbbmalloc_add_test(conformance conformance_scalable_allocator)
set_target_properties(test_openmp PROPERTIES COMPILE_FLAGS ${TBB_OPENMP_FLAG})
if ("${CMAKE_MSVC_RUNTIME_LIBRARY}" STREQUAL MultiThreaded OR "${CMAKE_MSVC_RUNTIME_LIBRARY}" STREQUAL MultiThreadedDebug)
if ("${CMAKE_MSVC_RUNTIME_LIBRARY}" STREQUAL MultiThreaded)
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
else()
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDebugDLL)
if (NOT TBB_OPENMP_NO_LINK_FLAG)
set_target_properties(test_openmp PROPERTIES LINK_FLAGS ${TBB_OPENMP_FLAG})
endif()
endif()
# Thread Sanitizer overloads memory management routines that conflicts with tbbmalloc_proxy.
if (BUILD_SHARED_LIBS AND NOT TBB_SANITIZE MATCHES "thread")
# Define TBB malloc proxy tests
tbb_add_lib_test(SUBDIR tbbmalloc NAME test_malloc_atexit DEPENDENCIES TBB::tbbmalloc_proxy TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_atexit DEPENDENCIES TBB::tbbmalloc_proxy TBB::tbbmalloc _test_malloc_atexit)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_overload DEPENDENCIES TBB::tbbmalloc_proxy)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_overload_disable DEPENDENCIES TBB::tbbmalloc_proxy TBB::tbbmalloc) # safer_msize call need to be available
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_new_handler DEPENDENCIES TBB::tbbmalloc_proxy)
# Define the conformance tests
tbb_add_test(SUBDIR conformance NAME conformance_tick_count DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_allocators DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_mutex DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_task_group_context DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_task_arena DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_lru_cache DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_unordered_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_unordered_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_set DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_priority_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_for DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_for_each DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_reduce DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_scan DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_sort DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_pipeline DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_parallel_invoke DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_range DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_range2d DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_range3d DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_blocked_rangeNd DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_vector DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_global_control DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_hash_map DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_enumerable_thread_specific DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_combinable DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_concurrent_queue DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_resumable_tasks DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_version DEPENDENCIES TBB::tbb)
# functional nodes conformance
tbb_add_test(SUBDIR conformance NAME conformance_function_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_multifunction_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_input_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_continue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_async_node DEPENDENCIES TBB::tbb)
# buffering nodes conformance
tbb_add_test(SUBDIR conformance NAME conformance_overwrite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_write_once_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_buffer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_priority_queue_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_sequencer_node DEPENDENCIES TBB::tbb)
# service nodes conformance
tbb_add_test(SUBDIR conformance NAME conformance_limiter_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_broadcast_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_composite_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_indexer_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_split_node DEPENDENCIES TBB::tbb)
tbb_add_test(SUBDIR conformance NAME conformance_join_node DEPENDENCIES TBB::tbb)
# flowraph auxiliary conformance
# TODO: add conformance tests for graph_node, continue_msg, tagged_msg, copy_body, input_port, output_port, make_edge, remove_edge
tbb_add_test(SUBDIR conformance NAME conformance_graph DEPENDENCIES TBB::tbb)
# HWLOC related conformance
tbb_add_tbbbind_test(SUBDIR conformance NAME conformance_arena_constraints)
if (MSVC AND BUILD_SHARED_LIBS AND CMAKE_VERSION VERSION_GREATER 3.13) # LINK_OPTIONS property first appeared in 3.13
# version of the CMake
tbb_add_test(SUBDIR tbb NAME test_implicit_linkage_on_windows)
# TODO: consider setting environment instead of passing additional
# compiler and linker options
target_include_directories(test_implicit_linkage_on_windows PRIVATE
$<TARGET_PROPERTY:TBB::tbb,INTERFACE_INCLUDE_DIRECTORIES>)
set_target_properties(test_implicit_linkage_on_windows PROPERTIES
LINK_OPTIONS /LIBPATH:$<TARGET_LINKER_FILE_DIR:TBB::tbb>)
add_dependencies(test_implicit_linkage_on_windows TBB::tbb)
endif()
endif()
if (TARGET TBB::tbbmalloc)
# TBB allocator tests
if (NOT "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "mips")
# Define TBB malloc tests
tbb_add_test(SUBDIR tbbmalloc NAME test_scalable_allocator DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_pools DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_init_shutdown DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_regression DEPENDENCIES TBB::tbbmalloc)
if (TARGET TBB::tbb)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_shutdown_hang DEPENDENCIES TBB::tbb TBB::tbbmalloc)
endif()
if (NOT (WINDOWS_STORE OR TBB_WINDOWS_DRIVER))
# TODO: Consider adding following tests on WINDOWS_STORE and TBB_WINDOWS_DRIVER platforms
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_compliance DEPENDENCIES TBB::tbbmalloc)
tbb_add_lib_test(SUBDIR tbbmalloc NAME test_malloc_used_by_lib DEPENDENCIES TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_used_by_lib DEPENDENCIES _test_malloc_used_by_lib)
tbb_add_lib_test(SUBDIR tbbmalloc NAME test_malloc_lib_unload)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_lib_unload DEPENDENCIES _test_malloc_lib_unload)
endif()
enable_language(C)
tbb_add_c_test(SUBDIR tbbmalloc NAME test_malloc_pure_c DEPENDENCIES TBB::tbbmalloc)
# ----------------------------------------------------------------------------------------
# Whitebox testing
add_executable(test_malloc_whitebox tbbmalloc/test_malloc_whitebox.cpp)
target_include_directories(test_malloc_whitebox
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../include
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(test_malloc_whitebox PRIVATE __TBBMALLOC_BUILD)
target_compile_options(test_malloc_whitebox
PRIVATE
${TBB_CXX_STD_FLAG}
${TBB_WARNING_SUPPRESS}
${TBB_TEST_COMPILE_FLAGS}
${TBB_COMMON_COMPILE_FLAGS}
${TBBMALLOC_LIB_COMPILE_FLAGS}
)
if (ANDROID_PLATFORM)
target_link_libraries(test_malloc_whitebox PRIVATE -rdynamic) # for the test_dynamic_link
add_test(NAME test_malloc_whitebox
COMMAND ${CMAKE_COMMAND}
-DBINARIES_PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
-DTEST_NAME=test_malloc_whitebox
-P ${PROJECT_SOURCE_DIR}/cmake/android/test_launcher.cmake)
else()
add_test(NAME test_malloc_whitebox COMMAND test_malloc_whitebox --force-colors=1)
endif()
if (COMMAND target_link_options)
target_link_options(test_malloc_whitebox PRIVATE ${TBB_COMMON_LINK_FLAGS})
else()
target_link_libraries(test_malloc_whitebox PRIVATE ${TBB_COMMON_LINK_FLAGS})
endif()
target_link_libraries(test_malloc_whitebox PRIVATE Threads::Threads ${TBB_COMMON_LINK_LIBS})
# ------------------------------------------------------------------------------------------
# Define TBB malloc conformance tests
# tbbmalloc_add_test(conformance conformance_scalable_allocator)
if ("${CMAKE_MSVC_RUNTIME_LIBRARY}" STREQUAL MultiThreaded OR "${CMAKE_MSVC_RUNTIME_LIBRARY}" STREQUAL MultiThreadedDebug)
if ("${CMAKE_MSVC_RUNTIME_LIBRARY}" STREQUAL MultiThreaded)
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
else()
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDebugDLL)
endif()
endif()
# Thread Sanitizer overloads memory management routines that conflicts with tbbmalloc_proxy.
if (BUILD_SHARED_LIBS AND NOT TBB_SANITIZE MATCHES "thread")
# Define TBB malloc proxy tests
tbb_add_lib_test(SUBDIR tbbmalloc NAME test_malloc_atexit DEPENDENCIES TBB::tbbmalloc_proxy TBB::tbbmalloc)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_atexit DEPENDENCIES TBB::tbbmalloc_proxy TBB::tbbmalloc _test_malloc_atexit)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_overload DEPENDENCIES TBB::tbbmalloc_proxy)
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_overload_disable DEPENDENCIES TBB::tbbmalloc_proxy TBB::tbbmalloc) # safer_msize call need to be available
tbb_add_test(SUBDIR tbbmalloc NAME test_malloc_new_handler DEPENDENCIES TBB::tbbmalloc_proxy)
endif()
endif()
endif()
+95 -132
View File
@@ -18,158 +18,121 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_async_node.cpp
//! \brief Test for [flow_graph.async_node] specification
/*
TODO: implement missing conformance tests for async_node:
- [ ] Write `test_forwarding()'.
- [ ] Improve test of the node's copy-constructor.
- [ ] Write `test_priority'.
- [ ] Rename `test_discarding' to `test_buffering'.
- [ ] Write inheritance test.
- [ ] Constructor with explicitly passed Policy parameter.
- [ ] Concurrency testing of the node: make a loop over possible concurrency levels. It is
important to test at least on five values: 1, oneapi::tbb::flow::serial, `max_allowed_parallelism'
obtained from `oneapi::tbb::global_control', `oneapi::tbb::flow::unlimited', and, if `max allowed
parallelism' is > 2, use something in the middle of the [1, max_allowed_parallelism]
interval. Use `utils::ExactConcurrencyLevel' entity (extending it if necessary).
- [ ] Write `test_rejecting', where avoid dependency on OS scheduling of the threads; add check
that `try_put()' returns `false'
- [ ] The `copy_body' function copies altered body (e.g. after successful `try_put()' call).
- [ ] The copy constructor and copy assignment are called for the node's input and output types.
- [ ] Add CTAD test.
*/
using input_msg = conformance::message</*default_ctor*/true, /*copy_ctor*/true, /*copy_assign*/false>;
using output_msg = conformance::message</*default_ctor*/false, /*copy_ctor*/false, /*copy_assign*/false>;
template<typename I, typename O>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, async_node<I, O>>::value), "async_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<I>, async_node<I, O>>::value), "async_node should be derived from receiver<Input>");
CHECK_MESSAGE( (std::is_base_of<sender<O>, async_node<I, O>>::value), "async_node should be derived from sender<Output>");
}
template< typename OutputType >
struct as_inc_functor {
std::thread my_thread;
std::atomic<size_t>& local_execute_count;
as_inc_functor(std::atomic<size_t>& execute_count ) :
local_execute_count (execute_count)
{ }
as_inc_functor( const as_inc_functor &f ) : local_execute_count(f.local_execute_count) { }
void operator=(const as_inc_functor &f) { local_execute_count = size_t(f.local_execute_count); }
void operator()( int num , oneapi::tbb::flow::async_node<int, int>::gateway_type& g) {
++local_execute_count;
g.try_put(num);
// my_thread = std::thread([&](){
// g.try_put(num);
// });
}
};
void test_async_body(){
oneapi::tbb::flow::graph g;
std::atomic<size_t> local_count(0);
as_inc_functor<int> fun(local_count);
oneapi::tbb::flow::async_node<int, int> node1(g, oneapi::tbb::flow::unlimited, fun);
const size_t n = 10;
for(size_t i = 0; i < n; ++i) {
CHECK_MESSAGE((node1.try_put(1) == true), "try_put needs to return true");
}
//fun.my_thread.join();
g.wait_for_all();
CHECK_MESSAGE( (fun.local_execute_count.load() == n), "Body of the node needs to be executed N times");
}
void test_copy(){
oneapi::tbb::flow::graph g;
std::atomic<size_t> local_count(0);
as_inc_functor<int> fun(local_count);
oneapi::tbb::flow::async_node<int, int> node1(g, oneapi::tbb::flow::unlimited, fun);
oneapi::tbb::flow::async_node<int, int> node2(node1);
}
void test_priority(){
oneapi::tbb::flow::graph g;
std::atomic<size_t> local_count(0);
as_inc_functor<int> fun(local_count);
oneapi::tbb::flow::async_node<int, int> node1(g, oneapi::tbb::flow::unlimited, fun, oneapi::tbb::flow::no_priority);
}
void test_discarding(){
oneapi::tbb::flow::graph g;
std::atomic<size_t> local_count(0);
as_inc_functor<int> fun(local_count);
oneapi::tbb::flow::async_node<int, int> node1(g, oneapi::tbb::flow::unlimited, fun);
oneapi::tbb::flow::limiter_node< int > rejecter1( g,0);
oneapi::tbb::flow::limiter_node< int > rejecter2( g,0);
make_edge(node1, rejecter2);
make_edge(node1, rejecter1);
node1.try_put(1);
int tmp = -1;
CHECK_MESSAGE((node1.try_get(tmp) == false), "Value should be discarded after rejection");
g.wait_for_all();
}
//! Test discarding property
//! Test async_node constructors
//! \brief \ref requirement
TEST_CASE("async_node discarding") {
test_discarding();
TEST_CASE("async_node constructors"){
using namespace oneapi::tbb::flow;
graph g;
conformance::dummy_functor<int> fun;
async_node<int, int> fn1(g, unlimited, fun);
async_node<int, int> fn2(g, unlimited, fun, oneapi::tbb::flow::node_priority_t(1));
async_node<int, int, lightweight> lw_node1(g, serial, fun, lightweight());
async_node<int, int, lightweight> lw_node2(g, serial, fun, lightweight(), oneapi::tbb::flow::node_priority_t(1));
}
//! Test async_node priority interface
//! \brief \ref interface
TEST_CASE("async_node priority interface"){
test_priority();
//! Test buffering property
//! \brief \ref requirement
TEST_CASE("async_node buffering") {
conformance::dummy_functor<int> fun;
conformance::test_buffering<oneapi::tbb::flow::async_node<input_msg, int>, input_msg>(oneapi::tbb::flow::unlimited, fun);
}
//! Test async_node copy
//! \brief \ref interface
TEST_CASE("async_node copy"){
test_copy();
//! Test priorities work in single-threaded configuration
//! \brief \ref requirement
TEST_CASE("async_node priority support"){
conformance::test_priority<oneapi::tbb::flow::async_node<input_msg, int>, input_msg>(oneapi::tbb::flow::unlimited);
}
//! The node that is constructed has a reference to the same graph object as src, has a copy of the initial body used by src, and has the same concurrency threshold as src.
//! The predecessors and successors of src are not copied.
//! \brief \ref requirement
TEST_CASE("async_node copy constructor"){
conformance::test_copy_ctor<oneapi::tbb::flow::async_node<int, int>>();
}
//! Test calling async body
//! \brief \ref interface \ref requirement
TEST_CASE("Test async_node body") {
test_async_body();
conformance::test_body_exec<oneapi::tbb::flow::async_node<input_msg, output_msg>, input_msg, output_msg>(oneapi::tbb::flow::unlimited);
}
//! Test async_node inheritance relations
//! \brief \ref interface
TEST_CASE("async_node superclasses"){
test_inheritance<int, int>();
test_inheritance<void*, float>();
conformance::test_inheritance<oneapi::tbb::flow::async_node<int, int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::async_node<void*, float>, void*, float>();
conformance::test_inheritance<oneapi::tbb::flow::async_node<input_msg, output_msg>, input_msg, output_msg>();
}
//! Test node broadcast messages to successors
//! \brief \ref requirement
TEST_CASE("async_node broadcast"){
conformance::counting_functor<int> fun(conformance::expected);
conformance::test_forwarding<oneapi::tbb::flow::async_node<input_msg, int>, input_msg, int>(1, oneapi::tbb::flow::unlimited, fun);
}
//! Test async_node has a user-settable concurrency limit. It can be set to one of predefined values.
//! The user can also provide a value of type std::size_t to limit concurrency.
//! Test that not more than limited threads works in parallel.
//! \brief \ref requirement
TEST_CASE("concurrency follows set limits"){
conformance::test_concurrency<oneapi::tbb::flow::async_node<int, int>>();
}
//! Test body copying and copy_body logic
//! Test the body object passed to a node is copied
//! \brief \ref interface
TEST_CASE("async_node body copying"){
conformance::test_copy_body_function<oneapi::tbb::flow::async_node<int, int>, conformance::copy_counting_object<int>>(oneapi::tbb::flow::unlimited);
}
//! Test node reject the incoming message if the concurrency limit achieved.
//! \brief \ref interface
TEST_CASE("async_node with rejecting policy"){
conformance::test_rejecting<oneapi::tbb::flow::async_node<int, int, oneapi::tbb::flow::rejecting>>();
}
//! Test node Input class meet the DefaultConstructible and CopyConstructible requirements and Output class meet the CopyConstructible requirements.
//! \brief \ref interface \ref requirement
TEST_CASE("Test async_node Output and Input class") {
using Body = conformance::copy_counting_object<int>;
conformance::test_output_input_class<oneapi::tbb::flow::async_node<Body, Body>, Body>();
}
//! Test the body of assync_node typically submits the messages to an external activity for processing outside of the graph.
//! \brief \ref interface
TEST_CASE("async_node with rejecting policy"){
using async_node_type = tbb::flow::async_node<int, int>;
using gateway_type = async_node_type::gateway_type;
oneapi::tbb::flow::graph g;
std::atomic<bool> flag{false};
std::thread thr;
async_node_type testing_node{
g, tbb::flow::unlimited,
[&](const int& input, gateway_type& gateway) {
gateway.reserve_wait();
thr = std::thread{[&]{
flag = true;
gateway.try_put(input);
gateway.release_wait();
}};
}
};
testing_node.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((flag.load()), "The body of assync_node must submits the messages to an external activity for processing outside of the graph");
thr.join();
}
+36 -75
View File
@@ -18,99 +18,60 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_broadcast_node.cpp
//! \brief Test for [flow_graph.broadcast_node] specification
/*
TODO: implement missing conformance tests for broadcast_node:
- [ ] The copy constructor and copy assignment are called for the node's type template parameter.
- [ ] Improve test for constructors.
*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, broadcast_node<T>>::value), "broadcast_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, broadcast_node<T>>::value), "broadcast_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, broadcast_node<T>>::value), "broadcast_node should be derived from sender<T>");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
broadcast_node<int> n(g);
broadcast_node<int> n2(n);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::broadcast_node<int> node(g);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == false), "try_get after rejection should not succeed");
CHECK_MESSAGE( (tmp == -1), "try_get after rejection should not set value");
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::broadcast_node<int> node1(g);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
int c2 = get_count(node2), c3 = get_count(node3);
CHECK_MESSAGE( ( c2 == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE( ( c3 == 1), "Descendant of the node must receive one message.");
}
using input_msg = conformance::message</*default_ctor*/false, /*copy_ctor*/true/*enable for queue_node successor*/, /*copy_assign*/true/*enable for queue_node successor*/>;
//! Test function_node broadcast
//! \brief \ref requirement
TEST_CASE("broadcast_node broadcasts"){
test_forwarding();
conformance::test_forwarding<oneapi::tbb::flow::broadcast_node<int>, int>(1);
conformance::test_forwarding<oneapi::tbb::flow::broadcast_node<input_msg>, input_msg>(1);
}
//! Test broadcast_node buffering
//! \brief \ref requirement
TEST_CASE("broadcast_node buffering"){
test_buffering();
}
//! Test copy constructor
//! \brief \ref interface
TEST_CASE("broadcast_node copy constructor"){
test_copies();
conformance::test_buffering<oneapi::tbb::flow::broadcast_node<int>, int>();
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("broadcast_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
conformance::test_inheritance<oneapi::tbb::flow::broadcast_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::broadcast_node<float>, float, float>();
conformance::test_inheritance<oneapi::tbb::flow::broadcast_node<input_msg>, input_msg, input_msg>();
}
//! The node that is constructed has a reference to the same graph object as src.
//! The predecessors and successors of src are not copied.
//! \brief \ref interface
TEST_CASE("broadcast_node copy constructor"){
using namespace oneapi::tbb::flow;
graph g;
broadcast_node<int> node0(g);
broadcast_node<int> node1(g);
conformance::test_push_receiver<int> node2(g);
conformance::test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node0, node1);
oneapi::tbb::flow::make_edge(node1, node2);
broadcast_node<int> node_copy(node1);
oneapi::tbb::flow::make_edge(node_copy, node3);
node_copy.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 1), "Copied node doesn`t copy successor");
node0.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 1 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy predecessor");
}
+32 -82
View File
@@ -18,107 +18,57 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#define CONFORMANCE_BUFFERING_NODES
#include "conformance_flowgraph.h"
//! \file conformance_buffer_node.cpp
//! \brief Test for [flow_graph.buffer_node] specification
/*
TODO: implement missing conformance tests for buffer_node:
- [ ] The copy constructor is called for the node's type template parameter.
- [ ] Improve `test_forwarding' by checking that the value passed is the actual one received.
- [ ] Improve `test_buffering' by checking that additional `try_get()' does not receive the same
value.
- [ ] Improve tests of the constructors.
- [ ] Based on the decision about the details for `try_put()' and `try_get()' write corresponding
tests.
- [ ] Fix description in `TEST_CASEs'.*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, buffer_node<T>>::value), "buffer_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, buffer_node<T>>::value), "buffer_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, buffer_node<T>>::value), "buffer_node should be derived from sender<T>");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
buffer_node<int> n(g);
buffer_node<int> n2(n);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::buffer_node<int> node(g);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == false), "try_get before placemnt should not succeed");
node.try_put(1);
tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == true), "try_get after rejection should succeed");
CHECK_MESSAGE( (tmp == 1), "try_get after rejection should set value");
g.wait_for_all();
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::buffer_node<int> node1(g);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
int c2 = get_count(node2), c3 = get_count(node3);
CHECK_MESSAGE( (c2 != c3 ), "Only one descendant the node needs to receive");
CHECK_MESSAGE( (c2 + c3 == 1 ), "All messages need to be received");
//! Test buffer_node broadcast
//! \brief \ref requirement
TEST_CASE("buffer_node single_push"){
conformance::test_forwarding_single_push<oneapi::tbb::flow::buffer_node<int>>();
}
//! Test function_node buffering
//! \brief \ref requirement
TEST_CASE("buffer_node buffering"){
test_forwarding();
conformance::test_buffering<oneapi::tbb::flow::buffer_node<int>, int>();
}
//! Test function_node buffering
//! Constructs an empty buffer_node that belongs to the same graph g as src.
//! Any intermediate state of src, including its links to predecessors and successors, is not copied.
//! \brief \ref requirement
TEST_CASE("buffer_node buffering"){
test_buffering();
}
//! Test copy constructor
//! \brief \ref interface
TEST_CASE("buffer_node copy constructor"){
test_copies();
conformance::test_copy_ctor_for_buffering_nodes<oneapi::tbb::flow::buffer_node<int>>();
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("buffer_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
conformance::test_inheritance<oneapi::tbb::flow::buffer_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::buffer_node<void*>, void*, void*>();
}
//! Test buffer_node node `try_put()` and `try_get()`
//! \brief \ref requirement
TEST_CASE("buffer_node methods"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::buffer_node<int> testing_node(g);
int tmp1 = -1;
int tmp2 = -1;
CHECK_MESSAGE((!testing_node.try_get(tmp1) && tmp1 == -1), "`try_get` must returns false if there is no non-reserved item currently in the node.");
testing_node.try_put(1);
testing_node.try_put(2);
g.wait_for_all();
testing_node.try_get(tmp1);
CHECK_MESSAGE((tmp1 == 1 || tmp1 == 2), "Messages must be an arbitrary order");
testing_node.try_get(tmp2);
CHECK_MESSAGE((tmp2 != -1 && tmp2 != tmp1), "Additional `try_get()' does not receive the same value as previous");
}
+113 -42
View File
@@ -18,77 +18,148 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_composite_node.cpp
//! \brief Test for [flow_graph.composite_node] specification
/*
TODO: implement missing conformance tests for composite_node:
- [ ] Check that `input_ports_type' and `output_ports_type' are defined, accessible, and meet
their requirements, that is, each element is a reference to actual node, input or output
port respectively.
- [ ] Add tests for `composite_node' with only input and output ports.
- [ ] Make sure `input_ports()' and `output_ports()' are defined and accessible in respective
specializations.
- [ ] Check the size of input and output tuples is equal to the size of `input_ports_type' and
`output_ports_type'.
*/
using namespace oneapi::tbb::flow;
using namespace std;
class adder : public composite_node< tuple<int, int>, tuple<int> > {
join_node< tuple<int,int>, queueing > j;
function_node< tuple<int,int>, int > f;
queue_node <int> qn;
typedef composite_node< tuple<int,int>, tuple<int> > base_type;
class adder : public oneapi::tbb::flow::composite_node<std::tuple<int, int>, std::tuple<int>> {
oneapi::tbb::flow::join_node<std::tuple<int,int>, oneapi::tbb::flow::queueing> j;
oneapi::tbb::flow::function_node<std::tuple<int,int>, int> f;
oneapi::tbb::flow::queue_node<int> qn;
using base_type = oneapi::tbb::flow::composite_node<std::tuple<int,int>, std::tuple<int>>;
struct f_body {
int operator()(const tuple<int,int> &t) {
int sum = get<0>(t) + get<1>(t);
int operator()(const std::tuple<int,int> &t) {
int sum = std::get<0>(t) + std::get<1>(t);
return sum;
}
};
public:
adder(graph &g) : base_type(g), j(g), f(g, unlimited, f_body()), qn(g) {
adder(oneapi::tbb::flow::graph &g) : base_type(g), j(g), f(g, oneapi::tbb::flow::unlimited, f_body()), qn(g) {
make_edge(j, f);
make_edge(f, qn);
base_type::input_ports_type input_tuple(input_port<0>(j), input_port<1>(j));
base_type::input_ports_type input_tuple(oneapi::tbb::flow::input_port<0>(j), oneapi::tbb::flow::input_port<1>(j));
base_type::output_ports_type output_tuple(qn);
base_type::set_external_ports(input_tuple, output_tuple);
}
};
void test_inheritance(){
using namespace oneapi::tbb::flow;
template<int N, typename T1, typename T2>
struct compare {
static void compare_refs(T1 tuple1, T2 tuple2) {
CHECK_MESSAGE(( &std::get<N>(tuple1) == &std::get<N>(tuple2)), "ports not set correctly");
compare<N-1, T1, T2>::compare_refs(tuple1, tuple2);
}
};
CHECK_MESSAGE( (std::is_base_of<graph_node, adder>::value), "multifunction_node should be derived from graph_node");
}
template<typename T1, typename T2>
struct compare<1, T1, T2> {
static void compare_refs(T1 tuple1, T2 tuple2) {
CHECK_MESSAGE((&std::get<0>(tuple1) == &std::get<0>(tuple2)), "port 0 not correctly set");
}
};
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("composite_node superclasses"){
test_inheritance();
CHECK_MESSAGE((std::is_base_of<oneapi::tbb::flow::graph_node, adder>::value), "composite_node should be derived from graph_node");
}
//! Test inheritance relations
//! Test composite_node ports
//! \brief \ref interface \ref requirement
TEST_CASE("Construction and message test"){
TEST_CASE("composite_node ports"){
oneapi::tbb::flow::graph g;
using InputTupleType = std::tuple<tbb::flow::continue_msg, std::tuple<int, int>, int, int, int, int,
int, int, int, int, int, int, int, int>;
using OutputTupleType = std::tuple<tbb::flow::continue_msg, std::tuple<int, int>, oneapi::tbb::flow::tagged_msg<size_t, int, float>,
int, int, int, int, int, int, int, int, int, int, int, int>;
using EmptyTupleType= std::tuple<>;
using input_output_type = oneapi::tbb::flow::composite_node<InputTupleType, OutputTupleType>;
using input_only_type = oneapi::tbb::flow::composite_node<InputTupleType, EmptyTupleType>;
using output_only_type = oneapi::tbb::flow::composite_node<EmptyTupleType, OutputTupleType>;
const size_t NUM_INPUTS = std::tuple_size<InputTupleType>::value;
const size_t NUM_OUTPUTS = std::tuple_size<OutputTupleType>::value;
using body = conformance::dummy_functor<int>;
//node types
oneapi::tbb::flow::continue_node<tbb::flow::continue_msg> ct(g, body());
oneapi::tbb::flow::split_node< std::tuple<int, int> > s(g);
oneapi::tbb::flow::input_node<int> src(g, body());
oneapi::tbb::flow::function_node<int, int> fxn(g, oneapi::tbb::flow::unlimited, body());
oneapi::tbb::flow::multifunction_node<int, std::tuple<int, int> > m_fxn(g, oneapi::tbb::flow::unlimited, body());
oneapi::tbb::flow::broadcast_node<int> bc(g);
oneapi::tbb::flow::limiter_node<int> lim(g, 2);
oneapi::tbb::flow::indexer_node<int, float> ind(g);
oneapi::tbb::flow::join_node< std::tuple< int, int >, oneapi::tbb::flow::queueing > j(g);
oneapi::tbb::flow::queue_node<int> q(g);
oneapi::tbb::flow::buffer_node<int> bf(g);
oneapi::tbb::flow::priority_queue_node<int> pq(g);
oneapi::tbb::flow::write_once_node<int> wo(g);
oneapi::tbb::flow::overwrite_node<int> ovw(g);
oneapi::tbb::flow::sequencer_node<int> seq(g, conformance::sequencer_functor<int>());
auto input_tuple = std::tie(ct, s, m_fxn, fxn, bc, oneapi::tbb::flow::input_port<0>(j), lim, q, oneapi::tbb::flow::input_port<0>(ind),
pq, ovw, wo, bf, seq);
auto output_tuple = std::tie(ct,j, ind, fxn, src, bc, oneapi::tbb::flow::output_port<0>(s), lim, oneapi::tbb::flow::output_port<0>(m_fxn),
q, pq, ovw, wo, bf, seq);
//composite_node with both input_ports and output_ports
input_output_type a_node(g);
a_node.set_external_ports(input_tuple, output_tuple);
a_node.add_visible_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
a_node.add_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
auto a_node_input_ports_ptr = a_node.input_ports();
compare<NUM_INPUTS-1, decltype(a_node_input_ports_ptr), decltype(input_tuple)>::compare_refs(a_node_input_ports_ptr, input_tuple);
CHECK_MESSAGE(NUM_INPUTS == std::tuple_size<decltype(a_node_input_ports_ptr)>::value, "not all declared input ports were bound to nodes");
auto a_node_output_ports_ptr = a_node.output_ports();
compare<NUM_OUTPUTS-1, decltype(a_node_output_ports_ptr), decltype(output_tuple)>::compare_refs(a_node_output_ports_ptr, output_tuple);
CHECK_MESSAGE((NUM_OUTPUTS == std::tuple_size<decltype(a_node_output_ports_ptr)>::value), "not all declared output ports were bound to nodes");
//composite_node with only input_ports
input_only_type b_node(g);
b_node.set_external_ports(input_tuple);
b_node.add_visible_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
b_node.add_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
auto b_node_input_ports_ptr = b_node.input_ports();
compare<NUM_INPUTS-1, decltype(b_node_input_ports_ptr), decltype(input_tuple)>::compare_refs(b_node_input_ports_ptr, input_tuple);
CHECK_MESSAGE(NUM_INPUTS == std::tuple_size<decltype(b_node_input_ports_ptr)>::value, "not all declared input ports were bound to nodes");
//composite_node with only output_ports
output_only_type c_node(g);
c_node.set_external_ports(output_tuple);
// Reset is not suppose to do anything. Check that it can be called.
g.reset();
c_node.add_visible_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
c_node.add_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
auto c_node_output_ports_ptr = c_node.output_ports();
compare<NUM_OUTPUTS-1, decltype(c_node_output_ports_ptr), decltype(output_tuple)>::compare_refs(c_node_output_ports_ptr, output_tuple);
CHECK_MESSAGE(NUM_OUTPUTS == std::tuple_size<decltype(c_node_output_ports_ptr)>::value, "not all declared input ports were bound to nodes");
}
//! Test composite_node construction and message passing
//! \brief \ref interface \ref requirement
TEST_CASE("composite_node construction and message test"){
using namespace oneapi::tbb::flow;
graph g;
split_node< tuple<int, int, int, int> > s(g);
split_node<std::tuple<int, int, int, int>> s(g);
adder a0(g);
adder a1(g);
adder a2(g);
+208 -194
View File
@@ -18,217 +18,115 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#define CONFORMANCE_CONTINUE_NODE
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_continue_node.cpp
//! \brief Test for [flow_graph.continue_node] specification
/*
TODO: implement missing conformance tests for continue_node:
- [ ] For `test_forwarding' check that the value passed is the actual one received.
- [ ] The `copy_body' function copies altered body (e.g. after its successful invocation).
- [ ] Improve CTAD test.
- [ ] Improve constructors test, including addition of calls to constructors with
`number_of_predecessors' parameter.
- [ ] Explicit test for copy constructor of the node.
- [ ] Rewrite test_priority.
- [ ] Check `Output' type indeed copy-constructed and copy-assigned while working with the node.
- [ ] Explicit test for correct working of `number_of_predecessors' constructor parameter,
including taking it into account when making and removing edges.
- [ ] Add testing of `try_put' statement. In particular that it does not wait for the execution of
the body to complete.
*/
void test_cont_body(){
oneapi::tbb::flow::graph g;
inc_functor<int> cf;
cf.execute_count = 0;
oneapi::tbb::flow::continue_node<int> node1(g, cf);
const size_t n = 10;
for(size_t i = 0; i < n; ++i) {
CHECK_MESSAGE((node1.try_put(oneapi::tbb::flow::continue_msg()) == true),
"continue_node::try_put() should never reject a message.");
}
g.wait_for_all();
CHECK_MESSAGE( (cf.execute_count == n), "Body of the first node needs to be executed N times");
}
template<typename O>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, continue_node<O>>::value), "continue_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<continue_msg>, continue_node<O>>::value), "continue_node should be derived from receiver<Input>");
CHECK_MESSAGE( (std::is_base_of<sender<O>, continue_node<O>>::value), "continue_node should be derived from sender<Output>");
}
using output_msg = conformance::message</*default_ctor*/false, /*copy_ctor*/true, /*copy_assign*/true/*enable for queue_node successor*/>;
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
void test_deduction_guides(){
oneapi::tbb::flow::graph g;
inc_functor<int> fun;
oneapi::tbb::flow::continue_node node1(g, fun);
}
#endif
void test_forwarding(){
oneapi::tbb::flow::graph g;
inc_functor<int> fun;
fun.execute_count = 0;
oneapi::tbb::flow::continue_node<int> node1(g, fun);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
}
void test_buffering(){
oneapi::tbb::flow::graph g;
inc_functor<int> fun;
oneapi::tbb::flow::continue_node<int> node(g, fun);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(oneapi::tbb::flow::continue_msg());
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == false), "try_get after rejection should not succeed");
CHECK_MESSAGE( (tmp == -1), "try_get after rejection should not alter passed value");
g.wait_for_all();
}
void test_policy_ctors(){
using namespace oneapi::tbb::flow;
template <typename ExpectedType, typename Body>
void test_deduction_guides_common(Body body) {
using namespace tbb::flow;
graph g;
inc_functor<int> fun;
continue_node c1(g, body);
static_assert(std::is_same_v<decltype(c1), continue_node<ExpectedType>>);
continue_node<int, lightweight> lw_node(g, fun);
continue_node c2(g, body, lightweight());
static_assert(std::is_same_v<decltype(c2), continue_node<ExpectedType, lightweight>>);
continue_node c3(g, 5, body);
static_assert(std::is_same_v<decltype(c3), continue_node<ExpectedType>>);
continue_node c4(g, 5, body, lightweight());
static_assert(std::is_same_v<decltype(c4), continue_node<ExpectedType, lightweight>>);
continue_node c5(g, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c5), continue_node<ExpectedType>>);
continue_node c6(g, body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c6), continue_node<ExpectedType, lightweight>>);
continue_node c7(g, 5, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c7), continue_node<ExpectedType>>);
continue_node c8(g, 5, body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c8), continue_node<ExpectedType, lightweight>>);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
broadcast_node<continue_msg> b(g);
continue_node c9(follows(b), body);
static_assert(std::is_same_v<decltype(c9), continue_node<ExpectedType>>);
continue_node c10(follows(b), body, lightweight());
static_assert(std::is_same_v<decltype(c10), continue_node<ExpectedType, lightweight>>);
continue_node c11(follows(b), 5, body);
static_assert(std::is_same_v<decltype(c11), continue_node<ExpectedType>>);
continue_node c12(follows(b), 5, body, lightweight());
static_assert(std::is_same_v<decltype(c12), continue_node<ExpectedType, lightweight>>);
continue_node c13(follows(b), body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c13), continue_node<ExpectedType>>);
continue_node c14(follows(b), body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c14), continue_node<ExpectedType, lightweight>>);
continue_node c15(follows(b), 5, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c15), continue_node<ExpectedType>>);
continue_node c16(follows(b), 5, body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c16), continue_node<ExpectedType, lightweight>>);
#endif // __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
continue_node c17(c1);
static_assert(std::is_same_v<decltype(c17), continue_node<ExpectedType>>);
}
void test_ctors(){
using namespace oneapi::tbb::flow;
graph g;
int continue_body_f(const tbb::flow::continue_msg&) { return 1; }
void continue_void_body_f(const tbb::flow::continue_msg&) {}
inc_functor<int> fun;
void test_deduction_guides() {
using tbb::flow::continue_msg;
test_deduction_guides_common<int>([](const continue_msg&)->int { return 1; } );
test_deduction_guides_common<continue_msg>([](const continue_msg&) {});
continue_node<int> proto1(g, 2, fun, oneapi::tbb::flow::node_priority_t(1));
test_deduction_guides_common<int>([](const continue_msg&) mutable ->int { return 1; });
test_deduction_guides_common<continue_msg>([](const continue_msg&) mutable {});
test_deduction_guides_common<int>(continue_body_f);
test_deduction_guides_common<continue_msg>(continue_void_body_f);
}
template<typename O>
struct CopyCounterBody{
size_t copy_count;
#endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
CopyCounterBody():
copy_count(0) {}
CopyCounterBody(const CopyCounterBody<O>& other):
copy_count(other.copy_count + 1) {}
CopyCounterBody& operator=(const CopyCounterBody<O>& other){
copy_count = other.copy_count + 1;
return *this;
}
O operator()(oneapi::tbb::flow::continue_msg){
return 1;
}
};
void test_copies(){
using namespace oneapi::tbb::flow;
CopyCounterBody<int> b;
graph g;
continue_node<int> fn(g, b);
CopyCounterBody<int> b2 = copy_body<CopyCounterBody<int>,
continue_node<int>>(fn);
CHECK_MESSAGE( (b.copy_count + 2 <= b2.copy_count), "copy_body and constructor should copy bodies");
//! Test execution of node body
//! Test node can do try_put call
//! \brief \ref interface \ref requirement
TEST_CASE("continue body") {
conformance::test_body_exec<oneapi::tbb::flow::continue_node<output_msg>, oneapi::tbb::flow::continue_msg, output_msg>();
}
void test_priority(){
size_t concurrency_limit = 1;
oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, concurrency_limit);
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::continue_node<oneapi::tbb::flow::continue_msg> source(g,
[](oneapi::tbb::flow::continue_msg){ return oneapi::tbb::flow::continue_msg();});
source.try_put(oneapi::tbb::flow::continue_msg());
first_functor<int>::first_id = -1;
first_functor<int> low_functor(1);
first_functor<int> high_functor(2);
oneapi::tbb::flow::continue_node<int, int> high(g, high_functor, oneapi::tbb::flow::node_priority_t(1));
oneapi::tbb::flow::continue_node<int, int> low(g, low_functor);
make_edge(source, low);
make_edge(source, high);
g.wait_for_all();
CHECK_MESSAGE( (first_functor<int>::first_id == 2), "High priority node should execute first");
}
//! Test node costructors
//! \brief \ref requirement
TEST_CASE("continue_node constructors"){
test_ctors();
}
//! Test priorities work in single-threaded configuration
//! \brief \ref requirement
TEST_CASE("continue_node priority support"){
test_priority();
//! Test continue_node is a graph_node, receiver<continue_msg>, and sender<Output>
//! \brief \ref interface
TEST_CASE("continue_node superclasses"){
conformance::test_inheritance<oneapi::tbb::flow::continue_node<int>, oneapi::tbb::flow::continue_msg, int>();
conformance::test_inheritance<oneapi::tbb::flow::continue_node<void*>, oneapi::tbb::flow::continue_msg, void*>();
conformance::test_inheritance<oneapi::tbb::flow::continue_node<output_msg>, oneapi::tbb::flow::continue_msg, output_msg>();
}
//! Test body copying and copy_body logic
//! Test the body object passed to a node is copied
//! \brief \ref interface
TEST_CASE("continue_node and body copying"){
test_copies();
}
//! Test constructors
//! \brief \ref interface
TEST_CASE("continue_node constructors"){
test_policy_ctors();
}
//! Test continue_node buffering
//! \brief \ref requirement
TEST_CASE("continue_node buffering"){
test_buffering();
}
//! Test function_node broadcasting
//! \brief \ref requirement
TEST_CASE("continue_node broadcast"){
test_forwarding();
conformance::test_copy_body_function<oneapi::tbb::flow::continue_node<int>, conformance::copy_counting_object<int, oneapi::tbb::flow::continue_msg>>();
}
//! Test deduction guides
@@ -239,15 +137,131 @@ TEST_CASE("Deduction guides"){
#endif
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("continue_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
//! Test node broadcast messages to successors
//! \brief \ref requirement
TEST_CASE("continue_node broadcast"){
conformance::counting_functor<int> fun(conformance::expected);
conformance::test_forwarding<oneapi::tbb::flow::continue_node<int>, oneapi::tbb::flow::continue_msg, int>(1, fun);
}
//! Test body execution
//! \brief \ref interface \ref requirement
TEST_CASE("continue body") {
test_cont_body();
//! Test node not buffered unsuccessful message, and try_get after rejection should not succeed.
//! \brief \ref requirement
TEST_CASE("continue_node buffering"){
conformance::dummy_functor<int> fun;
conformance::test_buffering<oneapi::tbb::flow::continue_node<int>, oneapi::tbb::flow::continue_msg>(fun);
}
//! Test all node costructors
//! \brief \ref requirement
TEST_CASE("continue_node constructors"){
using namespace oneapi::tbb::flow;
graph g;
conformance::counting_functor<int> fun;
continue_node<int> proto1(g, fun);
continue_node<int> proto2(g, fun, oneapi::tbb::flow::node_priority_t(1));
continue_node<int> proto3(g, 2, fun);
continue_node<int> proto4(g, 2, fun, oneapi::tbb::flow::node_priority_t(1));
continue_node<int, lightweight> lw_node1(g, fun, lightweight());
continue_node<int, lightweight> lw_node2(g, fun, lightweight(), oneapi::tbb::flow::node_priority_t(1));
continue_node<int, lightweight> lw_node3(g, 2, fun, lightweight());
continue_node<int, lightweight> lw_node4(g, 2, fun, lightweight(), oneapi::tbb::flow::node_priority_t(1));
}
//! The node that is constructed has a reference to the same graph object as src,
//! has a copy of the initial body used by src, and only has a non-zero threshold if src is constructed with a non-zero threshold..
//! The predecessors and successors of src are not copied.
//! \brief \ref interface
TEST_CASE("continue_node copy constructor"){
using namespace oneapi::tbb::flow;
graph g;
conformance::dummy_functor<oneapi::tbb::flow::continue_msg> fun1;
using counting_body = conformance::copy_counting_object<output_msg, oneapi::tbb::flow::continue_msg>;
counting_body fun2;
continue_node<oneapi::tbb::flow::continue_msg> node0(g, fun1);
continue_node<output_msg> node1(g, 2, fun2);
conformance::test_push_receiver<output_msg> node2(g);
conformance::test_push_receiver<output_msg> node3(g);
oneapi::tbb::flow::make_edge(node0, node1);
oneapi::tbb::flow::make_edge(node1, node2);
continue_node<output_msg> node_copy(node1);
counting_body b2 = copy_body<counting_body, continue_node<output_msg>>(node_copy);
CHECK_MESSAGE((fun2.copy_count + 1 < b2.copy_count), "constructor should copy bodies");
oneapi::tbb::flow::make_edge(node_copy, node3);
node_copy.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy successor, but copy number of predecessors");
node_copy.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 1), "Copied node doesn`t copy successor, but copy number of predecessors");
node1.try_put(oneapi::tbb::flow::continue_msg());
node1.try_put(oneapi::tbb::flow::continue_msg());
node0.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 1 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy predecessor, but copy number of predecessors");
}
//! Test continue_node wait for their predecessors to complete before executing, but no explicit data is passed across the incoming edges.
//! \brief \ref requirement
TEST_CASE("continue_node number_of_predecessors") {
oneapi::tbb::flow::graph g;
conformance::counting_functor<int> fun;
oneapi::tbb::flow::continue_node<oneapi::tbb::flow::continue_msg> node1(g, fun);
oneapi::tbb::flow::continue_node<oneapi::tbb::flow::continue_msg> node2(g, 1, fun);
oneapi::tbb::flow::continue_node<oneapi::tbb::flow::continue_msg> node3(g, 1, fun);
oneapi::tbb::flow::continue_node<int> node4(g, fun);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node2, node4);
oneapi::tbb::flow::make_edge(node1, node3);
oneapi::tbb::flow::make_edge(node1, node3);
oneapi::tbb::flow::remove_edge(node1, node3);
oneapi::tbb::flow::make_edge(node3, node4);
node3.try_put(oneapi::tbb::flow::continue_msg());
node2.try_put(oneapi::tbb::flow::continue_msg());
node1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((fun.execute_count == 4), "Node wait for their predecessors to complete before executing");
}
//! Test nodes for execution with priority in single-threaded configuration
//! \brief \ref requirement
TEST_CASE("continue_node priority support"){
conformance::test_priority<oneapi::tbb::flow::continue_node<oneapi::tbb::flow::continue_msg, int>, oneapi::tbb::flow::continue_msg>();
}
//! Test node Output class meet the CopyConstructible requirements.
//! \brief \ref requirement
TEST_CASE("continue_node Output class") {
conformance::test_output_class<oneapi::tbb::flow::continue_node<conformance::copy_counting_object<int, oneapi::tbb::flow::continue_msg>>, conformance::copy_counting_object<int, oneapi::tbb::flow::continue_msg>>();
}
//! Test body `try_put' statement not wait for the execution of the body to complete
//! \brief \ref requirement
TEST_CASE("continue_node `try_put' statement not wait for the execution of the body to complete") {
conformance::wait_flag_body body;
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::continue_node<oneapi::tbb::flow::continue_msg> node1(g, body);
node1.try_put(oneapi::tbb::flow::continue_msg());
conformance::wait_flag_body::flag.store(true);
g.wait_for_all();
}
+761 -43
View File
@@ -17,74 +17,792 @@
#ifndef __TBB_test_conformance_conformance_flowgraph_H
#define __TBB_test_conformance_conformance_flowgraph_H
struct passthru_body {
int operator()( int i ) {
return i;
}
void operator()( const int& argument, oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
std::get<0>(op).try_put(argument);
}
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "common/concurrency_tracker.h"
};
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
namespace conformance {
constexpr int expected = 5;
template<typename V>
using test_push_receiver = oneapi::tbb::flow::queue_node<V>;
template<typename V>
int get_count( test_push_receiver<V>& rr ){
int val = 0;
for(V tmp; rr.try_get(tmp); ++val);
template<typename Input, typename Output = Input>
using multifunc_ports_t =
typename oneapi::tbb::flow::multifunction_node<Input, std::tuple<Output>>::output_ports_type;
return val;
template<typename Input, typename Output = Input>
using async_ports_t =
typename oneapi::tbb::flow::async_node<Input, Output>::gateway_type;
template<bool DefaultConstructible, bool CopyConstructible, bool CopyAssignable>
struct message {
int data;
message(int _data) : data(_data) {};
template<bool T = DefaultConstructible, typename = typename std::enable_if<T>::type>
message(){};
template<bool T = CopyConstructible, typename = typename std::enable_if<T>::type>
message(const message& msg) : data(msg.data) {};
template<bool T = CopyAssignable, typename = typename std::enable_if<T>::type>
message& operator=(const message& msg) {
this->data = msg.data;
return *this;
};
bool operator==(const int expected_data) const {
return data == expected_data;
}
bool operator==(const message& msg) const {
return data == msg.data;
}
operator std::size_t() const {
return static_cast<std::size_t>(data);
}
operator int() const {
return data;
}
};
template<typename V>
typename std::enable_if<!std::is_default_constructible<V>::value, std::vector<V>>::type get_values( test_push_receiver<V>& rr ) {
std::vector<V> messages;
int val = 0;
for(V tmp(0); rr.try_get(tmp); ++val) {
messages.push_back(tmp);
}
return messages;
}
template<typename V>
typename std::enable_if<std::is_default_constructible<V>::value, std::vector<V>>::type get_values( test_push_receiver<V>& rr ) {
std::vector<V> messages;
int val = 0;
for(V tmp; rr.try_get(tmp); ++val) {
messages.push_back(tmp);
}
return messages;
}
template< typename OutputType >
struct first_functor {
template<typename Node, typename InputType = void>
bool produce_messages(Node& node, int arg = 1) {
utils::suppress_unused_warning(arg);
#if defined CONFORMANCE_INPUT_NODE
node.activate();
return true;
#elif defined CONFORMANCE_CONTINUE_NODE
return node.try_put(InputType());
#else
return node.try_put(InputType(arg));
#endif
}
template<typename T, typename U>
typename std::enable_if<std::is_same<T, U>::value, bool>::type check_output_type(){
return true;
}
template<typename T, typename U>
typename std::enable_if<!std::is_same<T, U>::value, bool>::type check_output_type(){
return false;
}
template<typename T>
struct sequencer_functor {
struct seq_message {
std::size_t id;
T data;
};
using input_type = T;
std::size_t operator()(T v) {
return v;
}
std::size_t operator()(seq_message msg) {
return msg.id;
}
};
template<typename OutputType>
struct track_first_id_functor {
int my_id;
static std::atomic<int> first_id;
first_functor(int id) : my_id(id) {}
track_first_id_functor(int id) : my_id(id) {}
OutputType operator()( OutputType argument ) {
int old_value = first_id;
int old_value = -1;
while(first_id == -1 &&
!first_id.compare_exchange_weak(old_value, my_id))
;
!first_id.compare_exchange_strong(old_value, my_id));
return argument;
}
OutputType operator()( const oneapi::tbb::flow::continue_msg& ) {
return operator()(OutputType());
template<typename InputType>
OutputType operator()( InputType& ) {
return operator()(OutputType(0));
}
void operator()( const OutputType& argument, oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
operator()(OutputType());
template<typename InputType>
void operator()( InputType, async_ports_t<InputType, OutputType>& g ) {
g.try_put(operator()(OutputType(0)));
}
template<typename InputType>
void operator()( InputType, multifunc_ports_t<InputType, OutputType>& op ) {
std::get<0>(op).try_put(operator()(OutputType(0)));
}
};
template<typename OutputType>
std::atomic<int> track_first_id_functor<OutputType>::first_id = {-1};
template<typename OutputType>
struct counting_functor {
OutputType return_value;
static std::atomic<std::size_t> execute_count;
counting_functor( OutputType value = OutputType(0) ) : return_value(value) {
execute_count = 0;
}
counting_functor( const counting_functor & c ) : return_value(static_cast<int>(c.return_value)) {
execute_count = 0;
}
template<typename InputType>
OutputType operator()( InputType ) {
++execute_count;
return return_value;
}
template<typename InputType>
void operator()( InputType, multifunc_ports_t<InputType, OutputType>& op ) {
++execute_count;
std::get<0>(op).try_put(return_value);
}
OutputType operator()( oneapi::tbb::flow_control& fc ) {
++execute_count;
if(execute_count > std::size_t(return_value)) {
fc.stop();
return return_value;
}
return return_value;
}
template<typename InputType>
void operator()( InputType, async_ports_t<InputType, OutputType>& g ) {
++execute_count;
g.try_put(return_value);
}
};
template<typename OutputType>
std::atomic<std::size_t> counting_functor<OutputType>::execute_count = {0};
template<typename OutputType>
struct dummy_functor {
template<typename InputType>
OutputType operator()( InputType ) {
#ifdef CONFORMANCE_CONTINUE_NODE
return OutputType();
#else
return OutputType(0);
#endif
}
template<typename InputType>
void operator()( InputType, multifunc_ports_t<InputType, OutputType>& op ) {
std::get<0>(op).try_put(OutputType(0));
}
template<typename InputType>
void operator()( InputType, async_ports_t<InputType, OutputType>& g ) {
g.try_put(OutputType(0));
}
template<typename InputType, typename T>
void operator()( InputType, std::tuple<T, T>& ) {}
OutputType operator()( oneapi::tbb::flow_control & fc ) {
static bool check = false;
if(check) {
check = false;
fc.stop();
return OutputType(1);
}
check = true;
return OutputType(1);
}
};
struct wait_flag_body {
static std::atomic<bool> flag;
wait_flag_body() {
flag.store(false);
}
template<typename InputType>
InputType operator()( InputType ) {
while(!flag.load()) { utils::yield(); };
#ifdef CONFORMANCE_CONTINUE_NODE
return InputType();
#else
return InputType(0);
#endif
}
template<typename InputType>
void operator()( InputType argument, multifunc_ports_t<InputType>& op ) {
while(!flag.load()) { };
std::get<0>(op).try_put(argument);
}
};
template<typename OutputType>
std::atomic<int> first_functor<OutputType>::first_id;
template< typename OutputType >
struct inc_functor {
static std::atomic<size_t> execute_count;
OutputType operator()( oneapi::tbb::flow::continue_msg ) {
++execute_count;
return OutputType();
}
OutputType operator()( int argument ) {
++execute_count;
return argument;
template<typename InputType>
void operator()( InputType argument, async_ports_t<InputType>& g ) {
while(!flag.load()) { };
g.try_put(argument);
}
};
template<typename OutputType>
std::atomic<size_t> inc_functor<OutputType>::execute_count;
std::atomic<bool> wait_flag_body::flag{false};
struct concurrency_peak_checker_body {
std::size_t required_max_concurrency = 0;
concurrency_peak_checker_body( std::size_t req_max_concurrency = 0 ) :
required_max_concurrency(req_max_concurrency) {}
concurrency_peak_checker_body( const concurrency_peak_checker_body & ) = default;
int operator()( oneapi::tbb::flow_control & fc ) {
static int counter = 0;
utils::ConcurrencyTracker ct;
if(++counter > 500) {
counter = 0;
fc.stop();
return 1;
}
utils::doDummyWork(1000);
CHECK_MESSAGE((int)utils::ConcurrencyTracker::PeakParallelism() <= required_max_concurrency,
"Input node is serial and its body never invoked concurrently");
return 1;
}
int operator()( int ) {
utils::ConcurrencyTracker ct;
utils::doDummyWork(1000);
CHECK_MESSAGE((int)utils::ConcurrencyTracker::PeakParallelism() <= required_max_concurrency,
"Measured parallelism is not expected");
return 1;
}
void operator()( const int& argument, multifunc_ports_t<int>& op ) {
utils::ConcurrencyTracker ct;
utils::doDummyWork(1000);
CHECK_MESSAGE((int)utils::ConcurrencyTracker::PeakParallelism() <= required_max_concurrency,
"Measured parallelism is not expected");
std::get<0>(op).try_put(argument);
}
void operator()( const int& argument , async_ports_t<int>& g ) {
utils::ConcurrencyTracker ct;
utils::doDummyWork(1000);
CHECK_MESSAGE((int)utils::ConcurrencyTracker::PeakParallelism() <= required_max_concurrency,
"Measured parallelism is not expected");
g.try_put(argument);
}
};
template<typename OutputType, typename InputType = int>
struct copy_counting_object {
std::size_t copy_count;/*increases on every new copied object*/
mutable std::size_t copies_count;/*count number of objects copied from this object*/
std::size_t assign_count;
bool is_copy;
copy_counting_object():
copy_count(0), copies_count(0), assign_count(0), is_copy(false) {}
copy_counting_object(int):
copy_count(0), copies_count(0), assign_count(0), is_copy(false) {}
copy_counting_object( const copy_counting_object<OutputType, InputType>& other ):
copy_count(other.copy_count + 1), is_copy(true) {
++other.copies_count;
}
copy_counting_object& operator=( const copy_counting_object<OutputType, InputType>& other ) {
assign_count = other.assign_count + 1;
is_copy = true;
return *this;
}
OutputType operator()( InputType ) {
return OutputType(1);
}
void operator()( InputType, multifunc_ports_t<InputType,OutputType>& op ) {
std::get<0>(op).try_put(OutputType(1));
}
void operator()( InputType , async_ports_t<InputType, OutputType>& g) {
g.try_put(OutputType(1));
}
OutputType operator()( oneapi::tbb::flow_control & fc ) {
static bool check = false;
if(check) {
check = false;
fc.stop();
return OutputType(1);
}
check = true;
return OutputType(1);
}
};
template <typename OutputType = int>
struct passthru_body {
OutputType operator()( const oneapi::tbb::flow::continue_msg& ) {
return OutputType(0);
}
OutputType operator()( const OutputType& i ) {
return i;
}
OutputType operator()( oneapi::tbb::flow_control & fc ) {
static bool check = false;
if(check) {
check = false;
fc.stop();
return OutputType(0);
}
check = true;
return OutputType(0);
}
void operator()( OutputType argument, multifunc_ports_t<OutputType>& op ) {
std::get<0>(op).try_put(argument);
}
void operator()( OutputType argument, async_ports_t<OutputType>& g ) {
g.try_put(argument);
}
};
template<typename Node, typename InputType, typename OutputType, typename ...Args>
void test_body_exec(Args... node_args) {
oneapi::tbb::flow::graph g;
counting_functor<OutputType> counting_body;
counting_body.execute_count = 0;
Node testing_node(g, node_args..., counting_body);
constexpr std::size_t n = 10;
for(std::size_t i = 0; i < n; ++i) {
CHECK_MESSAGE((produce_messages<Node, InputType>(testing_node) == true),
"try_put of first node should return true");
}
g.wait_for_all();
CHECK_MESSAGE((counting_body.execute_count == n), "Body of the first node needs to be executed N times");
}
template<typename Node, typename Body, typename ...Args>
void test_copy_body_function(Args... node_args) {
using namespace oneapi::tbb::flow;
Body base_body;
graph g;
Node testing_node(g, node_args..., base_body);
Body b2 = copy_body<Body, Node>(testing_node);
CHECK_MESSAGE((base_body.copy_count + 1 < b2.copy_count), "copy_body and constructor should copy bodies");
}
template<typename Node, typename InputType, typename ...Args>
void test_buffering(Args... node_args) {
oneapi::tbb::flow::graph g;
Node testing_node(g, node_args...);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(testing_node, rejecter);
int tmp = -1;
produce_messages<Node, InputType>(testing_node);
g.wait_for_all();
#if defined CONFORMANCE_BUFFERING_NODES || defined CONFORMANCE_INPUT_NODE
CHECK_MESSAGE((testing_node.try_get(tmp) == true), "try_get after rejection should succeed");
CHECK_MESSAGE((tmp == 1), "try_get after rejection should set value");
#else
#ifdef CONFORMANCE_MULTIFUNCTION_NODE
CHECK_MESSAGE((std::get<0>(testing_node.output_ports()).try_get(tmp) == false), "try_get after rejection should not succeed");
#else
CHECK_MESSAGE((testing_node.try_get(tmp) == false), "try_get after rejection should not succeed");
#endif
CHECK_MESSAGE((tmp == -1), "try_get after rejection should not alter passed value");
#endif
}
template<typename Node, typename InputType, typename OutputType = InputType, typename ...Args>
void test_forwarding(std::size_t messages_received, Args... node_args) {
oneapi::tbb::flow::graph g;
Node testing_node(g, node_args...);
std::vector<std::unique_ptr<test_push_receiver<OutputType>>> receiver_nodes;
for(std::size_t i = 0; i < 10; ++i) {
receiver_nodes.emplace_back(new test_push_receiver<OutputType>(g));
oneapi::tbb::flow::make_edge(testing_node, *receiver_nodes.back());
}
produce_messages<Node, InputType>(testing_node, expected);
#ifdef CONFORMANCE_INPUT_NODE
CHECK_MESSAGE(expected == messages_received, "For correct execution of test");
#endif
g.wait_for_all();
for(auto& receiver : receiver_nodes) {
auto values = get_values(*receiver);
CHECK_MESSAGE((values.size() == messages_received), std::string("Descendant of the node must receive " + std::to_string(messages_received) + " message."));
CHECK_MESSAGE((values[0] == expected), "Value passed is the actual one received.");
}
}
template<typename Node, typename ...Args>
void test_forwarding_single_push(Args... node_args) {
oneapi::tbb::flow::graph g;
Node testing_node(g, node_args...);
test_push_receiver<int> suc_node1(g);
test_push_receiver<int> suc_node2(g);
oneapi::tbb::flow::make_edge(testing_node, suc_node1);
oneapi::tbb::flow::make_edge(testing_node, suc_node2);
testing_node.try_put(0);
g.wait_for_all();
auto values1 = get_values(suc_node1);
auto values2 = get_values(suc_node2);
CHECK_MESSAGE((values1.size() != values2.size()), "Only one descendant the node needs to receive");
CHECK_MESSAGE((values1.size() + values2.size() == 1), "All messages need to be received");
testing_node.try_put(1);
g.wait_for_all();
auto values3 = get_values(suc_node1);
auto values4 = get_values(suc_node2);
CHECK_MESSAGE((values3.size() != values4.size()), "Only one descendant the node needs to receive");
CHECK_MESSAGE((values3.size() + values4.size() == 1), "All messages need to be received");
#ifdef CONFORMANCE_QUEUE_NODE
CHECK_MESSAGE((values1[0] == 0), "Value passed is the actual one received");
CHECK_MESSAGE((values3[0] == 1), "Value passed is the actual one received");
#else
if(values1.size() == 1) {
CHECK_MESSAGE((values1[0] == 0), "Value passed is the actual one received");
}else{
CHECK_MESSAGE((values2[0] == 0), "Value passed is the actual one received");
}
#endif
}
template<typename Node, typename InputType, typename OutputType>
void test_inheritance() {
using namespace oneapi::tbb::flow;
CHECK_MESSAGE((std::is_base_of<graph_node, Node>::value), "Node should be derived from graph_node");
CHECK_MESSAGE((std::is_base_of<receiver<InputType>, Node>::value), "Node should be derived from receiver<Input>");
CHECK_MESSAGE((std::is_base_of<sender<OutputType>, Node>::value), "Node should be derived from sender<Output>");
}
template<typename Node>
void test_copy_ctor() {
using namespace oneapi::tbb::flow;
graph g;
dummy_functor<int> fun1;
conformance::copy_counting_object<int> fun2;
Node node0(g, unlimited, fun1);
Node node1(g, unlimited, fun2);
test_push_receiver<int> suc_node1(g);
test_push_receiver<int> suc_node2(g);
oneapi::tbb::flow::make_edge(node0, node1);
oneapi::tbb::flow::make_edge(node1, suc_node1);
Node node_copy(node1);
conformance::copy_counting_object<int> b2 = copy_body<conformance::copy_counting_object<int>, Node>(node_copy);
CHECK_MESSAGE((fun2.copy_count + 1 < b2.copy_count), "constructor should copy bodies");
oneapi::tbb::flow::make_edge(node_copy, suc_node2);
node_copy.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((get_values(suc_node1).size() == 0 && get_values(suc_node2).size() == 1), "Copied node doesn`t copy successor");
node0.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((get_values(suc_node1).size() == 1 && get_values(suc_node2).size() == 0), "Copied node doesn`t copy predecessor");
}
template<typename Node, typename ...Args>
void test_copy_ctor_for_buffering_nodes(Args... node_args) {
oneapi::tbb::flow::graph g;
dummy_functor<int> fun;
Node testing_node(g, node_args...);
oneapi::tbb::flow::continue_node<int> pred_node(g, fun);
test_push_receiver<int> suc_node1(g);
test_push_receiver<int> suc_node2(g);
oneapi::tbb::flow::make_edge(pred_node, testing_node);
oneapi::tbb::flow::make_edge(testing_node, suc_node1);
#ifdef CONFORMANCE_OVERWRITE_NODE
testing_node.try_put(1);
#endif
Node node_copy(testing_node);
#ifdef CONFORMANCE_OVERWRITE_NODE
int tmp;
CHECK_MESSAGE((!node_copy.is_valid() && !node_copy.try_get(tmp)), "The buffered value is not copied from src");
get_values(suc_node1);
#endif
oneapi::tbb::flow::make_edge(node_copy, suc_node2);
node_copy.try_put(0);
g.wait_for_all();
CHECK_MESSAGE((get_values(suc_node1).size() == 0 && get_values(suc_node2).size() == 1), "Copied node doesn`t copy successor");
#ifdef CONFORMANCE_OVERWRITE_NODE
node_copy.clear();
testing_node.clear();
#endif
pred_node.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((get_values(suc_node1).size() == 1 && get_values(suc_node2).size() == 0), "Copied node doesn`t copy predecessor");
}
template<typename Node, typename InputType, typename ...Args>
void test_priority(Args... node_args) {
std::size_t concurrency_limit = 1;
oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, concurrency_limit);
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::continue_node<InputType> source(g, dummy_functor<InputType>());
track_first_id_functor<int>::first_id = -1;
track_first_id_functor<int> low_functor(1);
track_first_id_functor<int> high_functor(2);
Node high(g, node_args..., high_functor, oneapi::tbb::flow::node_priority_t(1));
Node low(g, node_args..., low_functor);
make_edge(source, low);
make_edge(source, high);
source.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((track_first_id_functor<int>::first_id == 2), "High priority node should execute first");
}
template<typename Node>
void test_concurrency() {
auto max_num_threads = oneapi::tbb::this_task_arena::max_concurrency();
oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism,
max_num_threads);
std::vector<int> threads_count = {1, oneapi::tbb::flow::serial, max_num_threads, oneapi::tbb::flow::unlimited};
if(max_num_threads > 2) {
threads_count.push_back(max_num_threads / 2);
}
for(auto num_threads : threads_count) {
utils::ConcurrencyTracker::Reset();
int expected_threads = num_threads;
if(num_threads == oneapi::tbb::flow::unlimited) {
expected_threads = max_num_threads;
}
if(num_threads == oneapi::tbb::flow::serial) {
expected_threads = 1;
}
oneapi::tbb::flow::graph g;
concurrency_peak_checker_body counter(expected_threads);
Node fnode(g, num_threads, counter);
test_push_receiver<int> suc_node(g);
make_edge(fnode, suc_node);
for(int i = 0; i < 500; ++i) {
fnode.try_put(i);
}
g.wait_for_all();
}
}
template<typename Node>
void test_rejecting() {
oneapi::tbb::flow::graph g;
wait_flag_body body;
Node fnode(g, oneapi::tbb::flow::serial, body);
test_push_receiver<int> suc_node(g);
make_edge(fnode, suc_node);
fnode.try_put(0);
CHECK_MESSAGE((!fnode.try_put(1)), "Messages should be rejected while the first is being processed");
wait_flag_body::flag = true;
g.wait_for_all();
CHECK_MESSAGE((get_values(suc_node).size() == 1), "Messages should be rejected while the first is being processed");
}
template<typename Node, typename CountingBody>
void test_output_input_class() {
using namespace oneapi::tbb::flow;
passthru_body<CountingBody> fun;
graph g;
Node node1(g, unlimited, fun);
test_push_receiver<CountingBody> suc_node(g);
make_edge(node1, suc_node);
CountingBody b1;
CountingBody b2;
node1.try_put(b1);
g.wait_for_all();
suc_node.try_get(b2);
DOCTEST_WARN_MESSAGE((b1.copies_count > 0), "The type Input must meet the DefaultConstructible and CopyConstructible requirements");
DOCTEST_WARN_MESSAGE((b2.is_copy), "The type Output must meet the CopyConstructible requirements");
}
template<typename Node, typename Output = copy_counting_object<int>>
void test_output_class() {
using namespace oneapi::tbb::flow;
passthru_body<Output> fun;
graph g;
Node node1(g, fun);
test_push_receiver<Output> suc_node(g);
make_edge(node1, suc_node);
#ifdef CONFORMANCE_INPUT_NODE
node1.activate();
#else
node1.try_put(oneapi::tbb::flow::continue_msg());
#endif
g.wait_for_all();
Output b;
suc_node.try_get(b);
DOCTEST_WARN_MESSAGE((b.is_copy), "The type Output must meet the CopyConstructible requirements");
}
template<typename Node>
void test_with_reserving_join_node_class() {
using namespace oneapi::tbb::flow;
graph g;
function_node<int, int> static_result_computer_n(
g, serial,
[&](const int& msg) {
// compute the result using incoming message and pass it further, e.g.:
int result = int((msg >> 2) / 4);
return result;
});
Node testing_node(g); // for buffering once computed value
buffer_node<int> buffer_n(g);
join_node<std::tuple<int, int>, reserving> join_n(g);
std::atomic<int> number{2};
std::atomic<int> counter{0};
function_node<std::tuple<int, int>> consumer_n(
g, unlimited,
[&](const std::tuple<int, int>& arg) {
// use the precomputed static result along with dynamic data
++counter;
#ifdef CONFORMANCE_OVERWRITE_NODE
CHECK_MESSAGE((std::get<0>(arg) == int((number >> 2) / 4)), "A overwrite_node store a single item that can be overwritten");
#else
CHECK_MESSAGE((std::get<0>(arg) == int((number >> 2) / 4)), "A write_once_node store a single item that cannot be overwritten");
#endif
});
make_edge(static_result_computer_n, testing_node);
make_edge(testing_node, input_port<0>(join_n));
make_edge(buffer_n, input_port<1>(join_n));
make_edge(join_n, consumer_n);
// do one-time calculation that will be reused many times further in the graph
static_result_computer_n.try_put(number);
constexpr int put_count = 50;
for (int i = 0; i < put_count / 2; i++) {
buffer_n.try_put(i);
}
#ifdef CONFORMANCE_OVERWRITE_NODE
number = 3;
#endif
static_result_computer_n.try_put(number);
for (int i = 0; i < put_count / 2; i++) {
buffer_n.try_put(i);
}
g.wait_for_all();
CHECK_MESSAGE((counter == put_count), "join_node with reserving policy \
if at least one successor accepts the tuple must consume messages");
}
}
#endif // __TBB_test_conformance_conformance_flowgraph_H
+86 -260
View File
@@ -18,303 +18,126 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
using input_msg = conformance::message</*default_ctor*/true, /*copy_ctor*/true, /*copy_assign*/false>;
using output_msg = conformance::message</*default_ctor*/false, /*copy_ctor*/true, /*copy_assign*/false>;
//! \file conformance_function_node.cpp
//! \brief Test for [flow_graph.function_node] specification
/*
TODO: implement missing conformance tests for function_node:
- [ ] Constructor with explicitly passed Policy parameter: `template<typename Body> function_node(
graph &g, size_t concurrency, Body body, Policy(), node_priority_t, priority = no_priority )'
- [ ] Explicit test for copy constructor of the node.
- [ ] Rename test_broadcast to test_forwarding and check that the value passed is the actual one
received.
- [ ] Concurrency testing of the node: make a loop over possible concurrency levels. It is
important to test at least on five values: 1, oneapi::tbb::flow::serial, `max_allowed_parallelism'
obtained from `oneapi::tbb::global_control', `oneapi::tbb::flow::unlimited', and, if `max allowed
parallelism' is > 2, use something in the middle of the [1, max_allowed_parallelism]
interval. Use `utils::ExactConcurrencyLevel' entity (extending it if necessary).
- [ ] make `test_rejecting' deterministic, i.e. avoid dependency on OS scheduling of the threads;
add check that `try_put()' returns `false'
- [ ] The copy constructor and copy assignment are called for the node's input and output types.
- [ ] The `copy_body' function copies altered body (e.g. after successful `try_put()' call).
- [ ] Extend CTAD test to check all node's constructors.
Test node deduction guides
*/
std::atomic<size_t> my_concurrency;
std::atomic<size_t> my_max_concurrency;
template< typename OutputType >
struct concurrency_functor {
OutputType operator()( int argument ) {
++my_concurrency;
size_t old_value = my_max_concurrency;
while(my_max_concurrency < my_concurrency &&
!my_max_concurrency.compare_exchange_weak(old_value, my_concurrency))
;
size_t ms = 1000;
std::chrono::milliseconds sleep_time( ms );
std::this_thread::sleep_for( sleep_time );
--my_concurrency;
return argument;
}
};
void test_func_body(){
oneapi::tbb::flow::graph g;
inc_functor<int> fun;
fun.execute_count = 0;
oneapi::tbb::flow::function_node<int, int> node1(g, oneapi::tbb::flow::unlimited, fun);
const size_t n = 10;
for(size_t i = 0; i < n; ++i) {
CHECK_MESSAGE((node1.try_put(1) == true), "try_put needs to return true");
}
g.wait_for_all();
CHECK_MESSAGE( (fun.execute_count == n), "Body of the node needs to be executed N times");
}
void test_priority(){
size_t concurrency_limit = 1;
oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, concurrency_limit);
oneapi::tbb::flow::graph g;
first_functor<int>::first_id.store(-1);
first_functor<int> low_functor(1);
first_functor<int> high_functor(2);
oneapi::tbb::flow::continue_node<int> source(g, [&](oneapi::tbb::flow::continue_msg){return 1;} );
oneapi::tbb::flow::function_node<int, int> high(g, oneapi::tbb::flow::unlimited, high_functor, oneapi::tbb::flow::node_priority_t(1));
oneapi::tbb::flow::function_node<int, int> low(g, oneapi::tbb::flow::unlimited, low_functor);
make_edge(source, low);
make_edge(source, high);
source.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE( (first_functor<int>::first_id == 2), "High priority node should execute first");
}
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
void test_deduction_guides(){
using namespace oneapi::tbb::flow;
int function_body_f(const int&) { return 1; }
template <typename Body>
void test_deduction_guides_common(Body body) {
using namespace tbb::flow;
graph g;
auto body = [](const int&)->int { return 1; };
function_node f1(g, unlimited, body);
CHECK_MESSAGE((std::is_same_v<decltype(f1), function_node<int, int>>), "Function node type must be deducible from its body");
static_assert(std::is_same_v<decltype(f1), function_node<int, int>>);
function_node f2(g, unlimited, body, rejecting());
static_assert(std::is_same_v<decltype(f2), function_node<int, int, rejecting>>);
function_node f3(g, unlimited, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(f3), function_node<int, int>>);
function_node f4(g, unlimited, body, rejecting(), node_priority_t(5));
static_assert(std::is_same_v<decltype(f4), function_node<int, int, rejecting>>);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
function_node f5(follows(f2), unlimited, body);
static_assert(std::is_same_v<decltype(f5), function_node<int, int>>);
function_node f6(follows(f5), unlimited, body, rejecting());
static_assert(std::is_same_v<decltype(f6), function_node<int, int, rejecting>>);
function_node f7(follows(f6), unlimited, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(f7), function_node<int, int>>);
function_node f8(follows(f7), unlimited, body, rejecting(), node_priority_t(5));
static_assert(std::is_same_v<decltype(f8), function_node<int, int, rejecting>>);
#endif // __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
function_node f9(f1);
static_assert(std::is_same_v<decltype(f9), function_node<int, int>>);
}
void test_deduction_guides() {
test_deduction_guides_common([](const int&)->int { return 1; });
test_deduction_guides_common([](const int&) mutable ->int { return 1; });
test_deduction_guides_common(function_body_f);
}
#endif
void test_broadcast(){
oneapi::tbb::flow::graph g;
passthru_body fun;
oneapi::tbb::flow::function_node<int, int> node1(g, oneapi::tbb::flow::unlimited, fun);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
//! Test calling function body
//! \brief \ref interface \ref requirement
TEST_CASE("Test function_node body") {
conformance::test_body_exec<oneapi::tbb::flow::function_node<input_msg, output_msg>, input_msg, output_msg>(oneapi::tbb::flow::unlimited);
}
template<typename Policy>
void test_buffering(){
oneapi::tbb::flow::graph g;
passthru_body fun;
oneapi::tbb::flow::function_node<int, int, Policy> node(g, oneapi::tbb::flow::unlimited, fun);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == false), "try_get after rejection should not succeed");
CHECK_MESSAGE( (tmp == -1), "try_get after rejection should not alter passed value");
g.wait_for_all();
}
void test_node_concurrency(){
my_concurrency = 0;
my_max_concurrency = 0;
oneapi::tbb::flow::graph g;
concurrency_functor<int> counter;
oneapi::tbb::flow::function_node <int, int> fnode(g, oneapi::tbb::flow::serial, counter);
test_push_receiver<int> sink(g);
make_edge(fnode, sink);
for(int i = 0; i < 10; ++i){
fnode.try_put(i);
}
g.wait_for_all();
CHECK_MESSAGE( ( my_max_concurrency.load() == 1), "Measured parallelism is not expected");
}
template<typename I, typename O>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, function_node<I, O>>::value), "function_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<I>, function_node<I, O>>::value), "function_node should be derived from receiver<Input>");
CHECK_MESSAGE( (std::is_base_of<sender<O>, function_node<I, O>>::value), "function_node should be derived from sender<Output>");
}
void test_policy_ctors(){
//! Test function_node constructors
//! \brief \ref requirement
TEST_CASE("function_node constructors"){
using namespace oneapi::tbb::flow;
graph g;
function_node<int, int, lightweight> lw_node(g, oneapi::tbb::flow::serial,
[](int v) { return v;});
function_node<int, int, queueing_lightweight> qlw_node(g, oneapi::tbb::flow::serial,
[](int v) { return v;});
function_node<int, int, rejecting_lightweight> rlw_node(g, oneapi::tbb::flow::serial,
[](int v) { return v;});
conformance::counting_functor<int> fun;
function_node<int, int> fn1(g, unlimited, fun);
function_node<int, int> fn2(g, unlimited, fun, oneapi::tbb::flow::node_priority_t(1));
function_node<int, int, lightweight> lw_node1(g, serial, fun, lightweight());
function_node<int, int, lightweight> lw_node2(g, serial, fun, lightweight(), oneapi::tbb::flow::node_priority_t(1));
}
class stateful_functor{
public:
int stored;
stateful_functor(): stored(-1){}
int operator()(int value){ stored = 1; return value;}
};
void test_ctors(){
using namespace oneapi::tbb::flow;
graph g;
function_node<int, int> fn(g, unlimited, stateful_functor());
fn.try_put(0);
g.wait_for_all();
stateful_functor b1 = copy_body<stateful_functor, function_node<int, int>>(fn);
CHECK_MESSAGE( (b1.stored == 1), "First node should update");
function_node<int, int> fn2(fn);
stateful_functor b2 = copy_body<stateful_functor, function_node<int, int>>(fn2);
CHECK_MESSAGE( (b2.stored == -1), "Copied node should not update");
//! The node that is constructed has a reference to the same graph object as src, has a copy of the initial body used by src, and has the same concurrency threshold as src.
//! The predecessors and successors of src are not copied.
//! \brief \ref requirement
TEST_CASE("function_node copy constructor"){
conformance::test_copy_ctor<oneapi::tbb::flow::function_node<int, int>>();
}
template<typename I, typename O>
struct CopyCounterBody{
size_t copy_count;
CopyCounterBody():
copy_count(0) {}
CopyCounterBody(const CopyCounterBody<I, O>& other):
copy_count(other.copy_count + 1) {}
CopyCounterBody& operator=(const CopyCounterBody<I, O>& other)
{ copy_count = other.copy_count + 1; return *this;}
O operator()(I in){
return in;
}
};
void test_copies(){
using namespace oneapi::tbb::flow;
CopyCounterBody<int, int> b;
graph g;
function_node<int, int> fn(g, unlimited, b);
CopyCounterBody<int, int> b2 = copy_body<CopyCounterBody<int, int>, function_node<int, int>>(fn);
CHECK_MESSAGE( (b.copy_count + 2 <= b2.copy_count), "copy_body and constructor should copy bodies");
}
void test_rejecting(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::function_node <int, int, oneapi::tbb::flow::rejecting> fnode(g, oneapi::tbb::flow::serial,
[&](int v){
size_t ms = 50;
std::chrono::milliseconds sleep_time( ms );
std::this_thread::sleep_for( sleep_time );
return v;
});
test_push_receiver<int> sink(g);
make_edge(fnode, sink);
for(int i = 0; i < 10; ++i){
fnode.try_put(i);
}
g.wait_for_all();
CHECK_MESSAGE( (get_count(sink) == 1), "Messages should be rejected while the first is being processed");
}
//! Test function_node with rejecting policy
//! Test node reject the incoming message if the concurrency limit achieved.
//! \brief \ref interface
TEST_CASE("function_node with rejecting policy"){
test_rejecting();
conformance::test_rejecting<oneapi::tbb::flow::function_node<int, int, oneapi::tbb::flow::rejecting>>();
}
//! Test body copying and copy_body logic
//! Test the body object passed to a node is copied
//! \brief \ref interface
TEST_CASE("function_node and body copying"){
test_copies();
conformance::test_copy_body_function<oneapi::tbb::flow::function_node<int, int>, conformance::copy_counting_object<int>>(oneapi::tbb::flow::unlimited);
}
//! Test constructors
//! \brief \ref interface
TEST_CASE("function_node constructors"){
test_policy_ctors();
}
//! Test inheritance relations
//! Test function_node is a graph_node, receiver<Input>, and sender<Output>
//! \brief \ref interface
TEST_CASE("function_node superclasses"){
test_inheritance<int, int>();
test_inheritance<void*, float>();
conformance::test_inheritance<oneapi::tbb::flow::function_node<int, int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::function_node<void*, float>, void*, float>();
conformance::test_inheritance<oneapi::tbb::flow::function_node<input_msg, output_msg>, input_msg, output_msg>();
}
//! Test function_node buffering
//! Test node not buffered unsuccessful message, and try_get after rejection should not succeed.
//! \brief \ref requirement
TEST_CASE("function_node buffering"){
test_buffering<oneapi::tbb::flow::rejecting>();
test_buffering<oneapi::tbb::flow::queueing>();
conformance::dummy_functor<int> fun;
conformance::test_buffering<oneapi::tbb::flow::function_node<input_msg, int, oneapi::tbb::flow::rejecting>, input_msg>(oneapi::tbb::flow::unlimited, fun);
conformance::test_buffering<oneapi::tbb::flow::function_node<input_msg, int, oneapi::tbb::flow::queueing>, input_msg>(oneapi::tbb::flow::unlimited, fun);
}
//! Test function_node broadcasting
//! Test node broadcast messages to successors
//! \brief \ref requirement
TEST_CASE("function_node broadcast"){
test_broadcast();
conformance::counting_functor<int> fun(conformance::expected);
conformance::test_forwarding<oneapi::tbb::flow::function_node<input_msg, int>, input_msg, int>(1, oneapi::tbb::flow::unlimited, fun);
}
//! Test deduction guides
@@ -328,17 +151,20 @@ TEST_CASE("Deduction guides"){
//! Test priorities work in single-threaded configuration
//! \brief \ref requirement
TEST_CASE("function_node priority support"){
test_priority();
conformance::test_priority<oneapi::tbb::flow::function_node<input_msg, int>, input_msg>(oneapi::tbb::flow::unlimited);
}
//! Test that measured concurrency respects set limits
//! Test function_node has a user-settable concurrency limit. It can be set to one of predefined values.
//! The user can also provide a value of type std::size_t to limit concurrency.
//! Test that not more than limited threads works in parallel.
//! \brief \ref requirement
TEST_CASE("concurrency follows set limits"){
test_node_concurrency();
conformance::test_concurrency<oneapi::tbb::flow::function_node<int, int>>();
}
//! Test calling function body
//! Test node Input class meet the DefaultConstructible and CopyConstructible requirements and Output class meet the CopyConstructible requirements.
//! \brief \ref interface \ref requirement
TEST_CASE("Test function_node body") {
test_func_body();
TEST_CASE("Test function_node Output and Input class") {
using Body = conformance::copy_counting_object<int>;
conformance::test_output_input_class<oneapi::tbb::flow::function_node<Body, Body>, Body>();
}
+217 -82
View File
@@ -18,144 +18,280 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_graph.cpp
//! \brief Test for [flow_graph.graph] specification
using namespace oneapi::tbb::flow;
using namespace std;
//! Graph reset
//! \brief \ref requirement
TEST_CASE("graph reset") {
void test_continue_node_rf_reset_protocol(){
using namespace oneapi::tbb::flow;
graph g;
size_t concurrency_limit = 1;
oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, concurrency_limit);
// Functional nodes
// TODO: Check input_node, multifunction_node, async_node similarly
// continue_node
bool flag = false;
std::atomic<bool> flag = {false};
continue_node<int> source(g, 2, [&](const continue_msg&){ flag = true; return 1;});
source.try_put(continue_msg());
g.wait_for_all();
CHECK_MESSAGE( (flag == false), "Should still be false");
CHECK_MESSAGE((flag == false), "Should be false");
g.reset(rf_reset_protocol);
source.try_put(continue_msg());
g.wait_for_all();
CHECK_MESSAGE( (flag == false), "Should still be false");
CHECK_MESSAGE((flag == false), "Internal number of predecessors reinitialized");
source.try_put(continue_msg());
g.wait_for_all();
CHECK_MESSAGE( (flag == true), "Should be true");
CHECK_MESSAGE((flag == true), "Should be true");
}
// functional_node
int flag_fun = 0;
function_node<int, int, queueing> f(g, serial, [&](const int& v){ flag_fun++; return v;});
void test_input_node_rf_reset_protocol(){
oneapi::tbb::flow::graph g;
f.try_put(0);
f.try_put(0);
conformance::copy_counting_object<int> fun;
CHECK_MESSAGE( (flag_fun == 0), "Should not be updated");
g.reset(rf_reset_protocol);
oneapi::tbb::flow::input_node<int> node(g, fun);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.activate();
g.wait_for_all();
CHECK_MESSAGE( (flag_fun == 1), "Should be updated");
// Buffering nodes
// TODO: Check overwrite_node write_once_node priority_queue_node sequencer_node similarly
// buffer_node
buffer_node<int> buff(g);
g.reset(oneapi::tbb::flow::rf_reset_protocol);
int tmp = -1;
CHECK_MESSAGE( (buff.try_get(tmp) == false), "try_get should not succeed");
CHECK_MESSAGE( (tmp == -1), "Value should not be updated");
CHECK_MESSAGE((node.try_get(tmp) == false), "Should be false");
}
buff.try_put(1);
template<typename Node>
void test_functional_nodes_rf_reset_protocol(){
oneapi::tbb::flow::graph g;
size_t concurrency_limit = 1;
oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, concurrency_limit);
g.reset();
conformance::counting_functor<int> counting_body;
Node f(g, oneapi::tbb::flow::serial, counting_body);
f.try_put(0);
f.try_put(0);
CHECK_MESSAGE((counting_body.execute_count == 0), "Body should not be executed");
g.reset(oneapi::tbb::flow::rf_reset_protocol);
g.wait_for_all();
CHECK_MESSAGE((counting_body.execute_count == 1), "Body should be executed");
}
template<typename Node, typename ...Args>
void test_buffering_nodes_rf_reset_protocol(Args... node_body){
oneapi::tbb::flow::graph g;
Node testing_node(g, node_body...);
int tmp = -1;
CHECK_MESSAGE((testing_node.try_get(tmp) == false), "try_get should not succeed");
CHECK_MESSAGE((tmp == -1), "Value should not be updated");
testing_node.try_put(1);
g.wait_for_all();
g.reset(oneapi::tbb::flow::rf_reset_protocol);
tmp = -1;
CHECK_MESSAGE( (buff.try_get(tmp) == false), "try_get should not succeed");
CHECK_MESSAGE( (tmp == -1), "Value should not be updated");
CHECK_MESSAGE((testing_node.try_get(tmp) == false), "try_get should not succeed");
CHECK_MESSAGE((tmp == -1), "Value should not be updated");
g.wait_for_all();
}
template<typename Node, typename InputType, typename ...Args>
void test_nodes_with_body_rf_reset_bodies(Args... node_args){
oneapi::tbb::flow::graph g;
conformance::counting_functor<int> counting_body(5);
Node testing_node(g, node_args..., counting_body);
testing_node.try_put(InputType());
g.wait_for_all();
// queue_node
queue_node<int> q(g);
CHECK_MESSAGE((counting_body.execute_count == 1), "Body should be executed");
tmp = -1;
CHECK_MESSAGE( (q.try_get(tmp) == false), "try_get should not succeed");
CHECK_MESSAGE( (tmp == -1), "Value should not be updated");
q.try_put(1);
g.reset();
tmp = -1;
CHECK_MESSAGE( (q.try_get(tmp) == false), "try_get should not succeed");
CHECK_MESSAGE( (tmp == -1), "Value should not be updated");
g.reset(oneapi::tbb::flow::rf_reset_bodies);
testing_node.try_put(InputType());
g.wait_for_all();
// Check rf_clear_edges
continue_node<int> src(g, [&](const continue_msg&){ return 1;});
queue_node<int> dest(g);
make_edge(src, dest);
CHECK_MESSAGE((counting_body.execute_count == 1), "Body should be replaced with a copy of the body");
}
src.try_put(continue_msg());
void test_limiter_node_rf_reset_protocol(){
oneapi::tbb::flow::graph g;
constexpr int limit = 5;
oneapi::tbb::flow::limiter_node<int> testing_node(g, limit);
conformance::test_push_receiver<int> suc_node(g);
oneapi::tbb::flow::make_edge(testing_node, suc_node);
for(int i = 0; i < limit * 2; ++i)
testing_node.try_put(1);
g.wait_for_all();
tmp = -1;
CHECK_MESSAGE( (dest.try_get(tmp)== true), "Message should pass when edge exists");
CHECK_MESSAGE( (tmp == 1 ), "Message should pass when edge exists");
CHECK_MESSAGE( (dest.try_get(tmp)== false), "Message should not pass after item is consumed");
CHECK_MESSAGE((conformance::get_values(suc_node).size() == limit), "Descendant of the node needs be receive limited number of messages");
g.reset(rf_clear_edges);
g.reset(oneapi::tbb::flow::rf_reset_protocol);
tmp = -1;
src.try_put(continue_msg());
for(int i = 0; i < limit * 2; ++i)
testing_node.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (dest.try_get(tmp)== false), "Message should not pass when edge doesn't exist");
CHECK_MESSAGE( (tmp == -1), "Value should not be altered");
CHECK_MESSAGE((conformance::get_values(suc_node).size() == limit), "Descendant of the node needs be receive limited number of messages");
}
// TODO: Add check that default invocaiton is the same as with rf_reset_protocol
// TODO: See if specification for broadcast_node and other service nodes is sufficient for reset checks
void test_join_node_rf_reset_protocol(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::join_node<std::tuple<int>, oneapi::tbb::flow::queueing> testing_node(g);
oneapi::tbb::flow::input_port<0>(testing_node).try_put(1);
g.wait_for_all();
g.reset(oneapi::tbb::flow::rf_reset_protocol);
std::tuple<int> tmp(0);
CHECK_MESSAGE((!testing_node.try_get(tmp)), "All buffers must be emptied");
}
//! Graph reset
//! \brief \ref requirement
TEST_CASE("graph reset with rf_reset_protocol") {
using namespace oneapi::tbb::flow;
test_continue_node_rf_reset_protocol();
test_input_node_rf_reset_protocol();
test_functional_nodes_rf_reset_protocol<function_node<int, int, queueing>>();
test_functional_nodes_rf_reset_protocol<multifunction_node<int, std::tuple<int>, queueing>>();
test_functional_nodes_rf_reset_protocol<async_node<int, int, queueing>>();
test_buffering_nodes_rf_reset_protocol<buffer_node<int>>();
test_buffering_nodes_rf_reset_protocol<queue_node<int>>();
test_buffering_nodes_rf_reset_protocol<overwrite_node<int>>();
test_buffering_nodes_rf_reset_protocol<write_once_node<int>>();
test_buffering_nodes_rf_reset_protocol<priority_queue_node<int>>();
conformance::sequencer_functor<int> sequencer;
test_buffering_nodes_rf_reset_protocol<sequencer_node<int>>(sequencer);
test_limiter_node_rf_reset_protocol();
test_join_node_rf_reset_protocol();
}
//! Graph reset rf_clear_edges
//! \brief \ref requirement
TEST_CASE("graph reset with rf_clear_edges") {
oneapi::tbb::flow::graph g;
using body = conformance::dummy_functor<int>;
oneapi::tbb::flow::queue_node<int> successor(g);
oneapi::tbb::flow::queue_node<std::tuple<int>> successor2(g);
oneapi::tbb::flow::queue_node<oneapi::tbb::flow::indexer_node<int>::output_type> successor3(g);
//node types
oneapi::tbb::flow::continue_node<int> ct(g, body());
oneapi::tbb::flow::split_node< std::tuple<int> > s(g);
oneapi::tbb::flow::input_node<int> src(g, body());
oneapi::tbb::flow::function_node<int, int> fxn(g, oneapi::tbb::flow::unlimited, body());
oneapi::tbb::flow::multifunction_node<int, std::tuple<int, int> > m_fxn(g, oneapi::tbb::flow::unlimited, body());
oneapi::tbb::flow::broadcast_node<int> bc(g);
oneapi::tbb::flow::limiter_node<int> lim(g, 2);
oneapi::tbb::flow::indexer_node<int> ind(g);
oneapi::tbb::flow::join_node< std::tuple< int >, oneapi::tbb::flow::queueing > j(g);
oneapi::tbb::flow::buffer_node<int> bf(g);
oneapi::tbb::flow::priority_queue_node<int> pq(g);
oneapi::tbb::flow::write_once_node<int> wo(g);
oneapi::tbb::flow::overwrite_node<int> ovw(g);
oneapi::tbb::flow::sequencer_node<int> seq(g, conformance::sequencer_functor<int>());
oneapi::tbb::flow::make_edge(ct, successor);
oneapi::tbb::flow::make_edge(s, successor);
oneapi::tbb::flow::make_edge(src, successor);
oneapi::tbb::flow::make_edge(fxn, successor);
oneapi::tbb::flow::make_edge(m_fxn, successor);
oneapi::tbb::flow::make_edge(bc, successor);
oneapi::tbb::flow::make_edge(lim, successor);
oneapi::tbb::flow::make_edge(ind, successor3);
oneapi::tbb::flow::make_edge(j, successor2);
oneapi::tbb::flow::make_edge(bf, successor);
oneapi::tbb::flow::make_edge(pq, successor);
oneapi::tbb::flow::make_edge(wo, successor);
oneapi::tbb::flow::make_edge(ovw, successor);
oneapi::tbb::flow::make_edge(seq, successor);
g.wait_for_all();
g.reset(oneapi::tbb::flow::rf_clear_edges);
ct.try_put(oneapi::tbb::flow::continue_msg());
s.try_put(std::tuple<int>{1});
src.activate();
fxn.try_put(1);
m_fxn.try_put(1);
bc.try_put(1);
lim.try_put(1);
oneapi::tbb::flow::input_port<0>(ind).try_put(1);
oneapi::tbb::flow::input_port<0>(j).try_put(1);
bf.try_put(1);
pq.try_put(1);
wo.try_put(1);
ovw.try_put(1);
seq.try_put(0);
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(successor).size() == 0), "Message should not pass when edges doesn't exist");
CHECK_MESSAGE((conformance::get_values(successor2).size() == 0), "Message should not pass when edge doesn't exist");
CHECK_MESSAGE((conformance::get_values(successor3).size() == 0), "Message should not pass when edge doesn't exist");
}
//! Graph reset rf_reset_bodies
//! \brief \ref requirement
TEST_CASE("graph reset with rf_reset_bodies") {
using namespace oneapi::tbb::flow;
test_nodes_with_body_rf_reset_bodies<continue_node<int>, continue_msg>(serial);
test_nodes_with_body_rf_reset_bodies<function_node<int, int>, int>(serial);
test_nodes_with_body_rf_reset_bodies<multifunction_node<int, std::tuple<int>>, int>(serial);
test_nodes_with_body_rf_reset_bodies<async_node<int, int>, int>(serial);
graph g;
conformance::counting_functor<int> counting_body(1);
input_node<int> testing_node(g,counting_body);
queue_node<int> q_node(g);
make_edge(testing_node, q_node);
testing_node.activate();
g.wait_for_all();
CHECK_MESSAGE((counting_body.execute_count == 2), "Body should be executed");
g.reset(rf_reset_bodies);
testing_node.activate();
g.wait_for_all();
CHECK_MESSAGE((counting_body.execute_count == 2), "Body should be replaced with a copy of the body");
}
//! Graph cancel
//! \brief \ref requirement
TEST_CASE("graph cancel") {
graph g;
CHECK_MESSAGE( !g.is_cancelled(), "Freshly created graph should not be cancelled." );
oneapi::tbb::flow::graph g;
CHECK_MESSAGE(!g.is_cancelled(), "Freshly created graph should not be cancelled." );
g.cancel();
CHECK_MESSAGE( !g.is_cancelled(), "Cancelled status should appear only after the wait_for_all() call." );
CHECK_MESSAGE(!g.is_cancelled(), "Cancelled status should appear only after the wait_for_all() call." );
g.wait_for_all();
CHECK_MESSAGE( g.is_cancelled(), "Waiting should allow checking the cancellation status." );
CHECK_MESSAGE(g.is_cancelled(), "Waiting should allow checking the cancellation status." );
g.reset();
CHECK_MESSAGE( !g.is_cancelled(), "Resetting must reset the cancellation status." );
CHECK_MESSAGE(!g.is_cancelled(), "Resetting must reset the cancellation status." );
std::atomic<bool> cancelled(false);
std::atomic<unsigned> executed(0);
function_node<int> f(g, serial, [&](int) {
oneapi::tbb::flow::function_node<int> f(g, oneapi::tbb::flow::serial, [&](int) {
++executed;
while( !cancelled.load(std::memory_order_relaxed) )
std::this_thread::sleep_for(std::chrono::milliseconds(1));
@@ -173,7 +309,6 @@ TEST_CASE("graph cancel") {
});
g.wait_for_all();
thr.join();
CHECK_MESSAGE( g.is_cancelled(), "Wait for all should not change the cancellation status." );
CHECK_MESSAGE( 1 == executed, "Buffered messages should be dropped by the cancelled graph." );
CHECK_MESSAGE(g.is_cancelled(), "Wait for all should not change the cancellation status." );
CHECK_MESSAGE(1 == executed, "Buffered messages should be dropped by the cancelled graph." );
}
+117 -105
View File
@@ -18,134 +18,146 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_indexer_node.cpp
//! \brief Test for [flow_graph.indexer_node] specification
/*
TODO: implement missing conformance tests for buffer_node:
- [ ] The copy constructor is called for the node's type template parameter.
- [ ] Improve `test_forwarding' by checking that the value passed is the actual one received.
- [ ] Improve `test_buffering' by checking that additional `try_get()' does not receive the same value.
- [ ] Improve tests of the constructors.
- [ ] Based on the decision about the details for `try_put()' and `try_get()' write corresponding tests.
- [ ] Fix description in `TEST_CASEs'.
*/
using namespace oneapi::tbb::flow;
using namespace std;
using input_msg = conformance::message</*default_ctor*/false, /*copy_ctor*/true, /*copy_assign*/false>;
using my_indexer_type = oneapi::tbb::flow::indexer_node<int, float, input_msg>;
using my_output_type = my_indexer_type::output_type;
template<typename I1, typename I2>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, indexer_node<I1, I2>>::value), "indexer_node should be derived from graph_node");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
indexer_node<int, int> fn(g);
indexer_node<int, int> f2(fn);
}
//! Test body copying and copy_body logic
//! \brief \ref interface
TEST_CASE("indexer_node and body copying"){
test_copies();
}
void test_broadcasting(){
oneapi::tbb::flow::graph g;
typedef indexer_node<int,float> my_indexer_type;
typedef my_indexer_type::output_type my_output_type;
my_indexer_type o(g);
my_indexer_type node1(g);
queue_node<my_output_type> node2(g);
queue_node<my_output_type> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
input_port<0>(node1).try_put(6);
input_port<1>(node1).try_put(1.5);
g.wait_for_all();
my_output_type tmp;
CHECK_MESSAGE( (node2.try_get(tmp)), "Descendant of the node needs to receive message once");
CHECK_MESSAGE( (node3.try_get(tmp)), "Descendant of the node needs to receive message once");
}
//! Test broadcasting property
//! Test node broadcast messages to successors
//! \brief \ref requirement
TEST_CASE("indexer_node broadcasts"){
test_broadcasting();
oneapi::tbb::flow::graph g;
my_indexer_type testing_node(g);
std::vector<conformance::test_push_receiver<my_output_type>*> receiver_nodes;
for(std::size_t i = 0; i < 3; ++i) {
receiver_nodes.emplace_back(new conformance::test_push_receiver<my_output_type>(g));
oneapi::tbb::flow::make_edge(testing_node, *receiver_nodes.back());
}
oneapi::tbb::flow::input_port<0>(testing_node).try_put(6);
oneapi::tbb::flow::input_port<1>(testing_node).try_put(1.5);
oneapi::tbb::flow::input_port<2>(testing_node).try_put(input_msg(1));
g.wait_for_all();
for(auto receiver: receiver_nodes) {
auto values = conformance::get_values(*receiver);
CHECK_MESSAGE((values.size() == 3), std::string("Descendant of the node must receive 3 messages."));
for(auto& value : values){
if(value.is_a<int>()){
CHECK_MESSAGE((value.cast_to<int>() == 6), "Value passed is the actual one received.");
} else if(value.is_a<float>()){
CHECK_MESSAGE((value.cast_to<float>() == 1.5), "Value passed is the actual one received.");
} else {
CHECK_MESSAGE((value.cast_to<input_msg>() == 1), "Value passed is the actual one received.");
}
}
delete receiver;
}
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("indexer_node superclasses"){
test_inheritance<int, int>();
CHECK_MESSAGE((std::is_base_of<oneapi::tbb::flow::graph_node, my_indexer_type>::value), "indexer_node should be derived from graph_node");
}
//! Test discarding property
//! Test node not buffered unsuccessful message, and try_get after rejection should not succeed.
//! \brief \ref requirement
TEST_CASE("indexer_node discarding") {
graph g;
TEST_CASE("indexer_node buffering") {
oneapi::tbb::flow::graph g;
typedef indexer_node<int,float> my_indexer_type;
my_indexer_type o(g);
my_indexer_type testing_node(g);
limiter_node< my_indexer_type::output_type > rejecter( g,0);
make_edge( o, rejecter );
oneapi::tbb::flow::limiter_node<my_output_type> rejecter(g,0);
oneapi::tbb::flow::make_edge(testing_node, rejecter);
input_port<0>(o).try_put(6);
input_port<1>(o).try_put(1.5);
oneapi::tbb::flow::input_port<0>(testing_node).try_put(6);
oneapi::tbb::flow::input_port<1>(testing_node).try_put(1.5);
oneapi::tbb::flow::input_port<2>(testing_node).try_put(input_msg(1));
my_indexer_type::output_type tmp;
CHECK_MESSAGE((o.try_get(tmp) == false), "Value should be discarded after rejection");
g.wait_for_all();
my_output_type tmp;
CHECK_MESSAGE((testing_node.try_get(tmp) == false), "Value should be discarded after rejection");
g.wait_for_all();
}
//! Test indexer body
//! Test indexer behaviour
//! \brief \ref requirement
TEST_CASE("indexer_node body") {
graph g;
function_node<int,int> f1( g, unlimited,
[](const int &i) { return 2*i; } );
function_node<float,float> f2( g, unlimited,
[](const float &f) { return f/2; } );
TEST_CASE("indexer_node behaviour") {
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::function_node<int, int> f1( g, oneapi::tbb::flow::unlimited,
[](const int &i) { return 2*i; } );
oneapi::tbb::flow::function_node<float, float> f2( g, oneapi::tbb::flow::unlimited,
[](const float &f) { return f/2; } );
oneapi::tbb::flow::continue_node<input_msg> c1( g,
[](oneapi::tbb::flow::continue_msg) { return input_msg(5); } );
typedef indexer_node<int,float> my_indexer_type;
my_indexer_type o(g);
my_indexer_type testing_node(g);
function_node< my_indexer_type::output_type >
f3( g, unlimited,
[]( const my_indexer_type::output_type &v ) {
if (v.tag() == 0) {
CHECK_MESSAGE( (cast_to<int>(v) == 6), "Expected to receive 6" );
} else {
CHECK_MESSAGE( (cast_to<float>(v) == 1.5), "Expected to receive 1.5" );
}
}
);
make_edge( f1, input_port<0>(o) );
make_edge( f2, input_port<1>(o) );
make_edge( o, f3 );
oneapi::tbb::flow::function_node<my_output_type>
f3( g, oneapi::tbb::flow::unlimited,
[]( const my_output_type &v ) {
if (v.tag() == 0) {
CHECK_MESSAGE((v.is_a<int>()), "Expected to int" );
CHECK_MESSAGE((oneapi::tbb::flow::cast_to<int>(v) == 6), "Expected to receive 6" );
} else if (v.tag() == 1) {
CHECK_MESSAGE((v.is_a<float>()), "Expected to float" );
CHECK_MESSAGE((oneapi::tbb::flow::cast_to<float>(v) == 1.5), "Expected to receive 1.5" );
} else {
CHECK_MESSAGE((v.is_a<input_msg>()), "Expected to float" );
CHECK_MESSAGE((oneapi::tbb::flow::cast_to<input_msg>(v) == 5), "Expected to receive input_msg(5)" );
}
}
);
f1.try_put( 3 );
f2.try_put( 3 );
g.wait_for_all();
oneapi::tbb::flow::make_edge(f1, oneapi::tbb::flow::input_port<0>(testing_node));
oneapi::tbb::flow::make_edge(f2, oneapi::tbb::flow::input_port<1>(testing_node));
oneapi::tbb::flow::make_edge(c1, oneapi::tbb::flow::input_port<2>(testing_node));
oneapi::tbb::flow::make_edge(testing_node, f3);
f1.try_put(3);
f2.try_put(3);
c1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
}
//! The node that is constructed has a reference to the same graph object as src.
//! The list of predecessors, messages in the input ports, and successors are not copied.
//! \brief \ref interface
TEST_CASE("indexer_node copy constructor"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::continue_node<int> node0( g,
[](oneapi::tbb::flow::continue_msg) { return 1; } );
my_indexer_type node1(g);
conformance::test_push_receiver<my_output_type> node2(g);
conformance::test_push_receiver<my_output_type> node3(g);
oneapi::tbb::flow::make_edge(node0, oneapi::tbb::flow::input_port<0>(node1));
oneapi::tbb::flow::make_edge(node1, node2);
my_indexer_type node_copy(node1);
oneapi::tbb::flow::make_edge(node_copy, node3);
oneapi::tbb::flow::input_port<0>(node_copy).try_put(1);
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 1), "Copied node doesn`t copy successor");
node0.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 1 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy predecessor");
}
//! Test indexer_node output_type
//! \brief \ref interface \ref requirement
TEST_CASE("indexer_node output_type") {
CHECK_MESSAGE((conformance::check_output_type<my_output_type, oneapi::tbb::flow::tagged_msg<size_t, int, float, input_msg>>()), "indexer_node output_type should returns a tagged_msg");
}
+168 -135
View File
@@ -18,159 +18,104 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#define CONFORMANCE_INPUT_NODE
#include "conformance_flowgraph.h"
//! \file conformance_input_node.cpp
//! \brief Test for [flow_graph.input_node] specification
/*
TODO: implement missing conformance tests for input_node:
- [ ] The `copy_body' function copies altered body (e.g. after its successful invocation).
- [ ] Check that in `test_forwarding' the value passed is the actual one received.
- [ ] Improve CTAD test to assert result node type.
- [ ] Explicit test for copy constructor of the node.
- [ ] Check `Output' type indeed copy-constructed and copy-assigned while working with the node.
- [ ] Check node cannot have predecessors (Will argument-dependent lookup be of any help here?)
- [ ] Check the node is serial and its body never invoked concurrently.
- [ ] `try_get()' call testing: a call to body is made only when the internal buffer is empty.
*/
std::atomic<size_t> global_execute_count;
template<typename OutputType>
struct input_functor {
const size_t n;
input_functor( ) : n(10) { }
input_functor( const input_functor &f ) : n(f.n) { }
void operator=(const input_functor &f) { n = f.n; }
OutputType operator()( oneapi::tbb::flow_control & fc ) {
++global_execute_count;
if(global_execute_count > n){
fc.stop();
return OutputType();
}
return OutputType(global_execute_count.load());
}
};
template<typename O>
struct CopyCounterBody{
size_t copy_count;
CopyCounterBody():
copy_count(0) {}
CopyCounterBody(const CopyCounterBody<O>& other):
copy_count(other.copy_count + 1) {}
CopyCounterBody& operator=(const CopyCounterBody<O>& other) {
copy_count = other.copy_count + 1; return *this;
}
O operator()(oneapi::tbb::flow_control & fc){
fc.stop();
return O();
}
};
void test_input_body(){
oneapi::tbb::flow::graph g;
input_functor<int> fun;
global_execute_count = 0;
oneapi::tbb::flow::input_node<int> node1(g, fun);
test_push_receiver<int> node2(g);
oneapi::tbb::flow::make_edge(node1, node2);
node1.activate();
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 10), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE( (global_execute_count == 10 + 1), "Body of the node needs to be executed N + 1 times");
}
using output_msg = conformance::message<true, true, true>;
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
void test_deduction_guides(){
oneapi::tbb::flow::graph g;
input_functor<int> fun;
oneapi::tbb::flow::input_node node1(g, fun);
}
int input_body_f(tbb::flow_control&) { return 42; }
void test_deduction_guides() {
using namespace tbb::flow;
graph g;
auto lambda = [](tbb::flow_control&) { return 42; };
auto non_const_lambda = [](tbb::flow_control&) mutable { return 42; };
// Tests for input_node(graph&, Body)
input_node s1(g, lambda);
static_assert(std::is_same_v<decltype(s1), input_node<int>>);
input_node s2(g, non_const_lambda);
static_assert(std::is_same_v<decltype(s2), input_node<int>>);
input_node s3(g, input_body_f);
static_assert(std::is_same_v<decltype(s3), input_node<int>>);
input_node s4(s3);
static_assert(std::is_same_v<decltype(s4), input_node<int>>);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
broadcast_node<int> bc(g);
// Tests for input_node(const node_set<Args...>&, Body)
input_node s5(precedes(bc), lambda);
static_assert(std::is_same_v<decltype(s5), input_node<int>>);
input_node s6(precedes(bc), non_const_lambda);
static_assert(std::is_same_v<decltype(s6), input_node<int>>);
input_node s7(precedes(bc), input_body_f);
static_assert(std::is_same_v<decltype(s7), input_node<int>>);
#endif
void test_buffering(){
oneapi::tbb::flow::graph g;
input_functor<int> fun;
global_execute_count = 0;
oneapi::tbb::flow::input_node<int> source(g, fun);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(source, rejecter);
source.activate();
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (source.try_get(tmp) == true), "try_get after rejection should succeed");
CHECK_MESSAGE( (tmp == 1), "try_get should return correct value");
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
input_functor<int> fun;
global_execute_count = 0;
oneapi::tbb::flow::input_node<int> node1(g, fun);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.activate();
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 10), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE( (get_count(node3) == 10), "Descendant of the node needs to be receive N messages");
}
#endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
template<typename O>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, input_node<O>>::value), "input_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<sender<O>, input_node<O>>::value), "input_node should be derived from sender<Output>");
CHECK_MESSAGE((std::is_base_of<graph_node, input_node<O>>::value), "input_node should be derived from graph_node");
CHECK_MESSAGE((std::is_base_of<sender<O>, input_node<O>>::value), "input_node should be derived from sender<Output>");
CHECK_MESSAGE((!std::is_base_of<receiver<O>, input_node<O>>::value), "input_node cannot have predecessors");
}
void test_copies(){
using namespace oneapi::tbb::flow;
CopyCounterBody<int> b;
graph g;
input_node<int> fn(g, b);
CopyCounterBody<int> b2 = copy_body<CopyCounterBody<int>, input_node<int>>(fn);
CHECK_MESSAGE( (b.copy_count + 2 <= b2.copy_count), "copy_body and constructor should copy bodies");
}
//! Test body copying and copy_body logic
//! Test the body object passed to a node is copied
//! \brief \ref interface
TEST_CASE("input_node and body copying"){
test_copies();
conformance::test_copy_body_function<oneapi::tbb::flow::input_node<int>, conformance::copy_counting_object<int>>();
}
//! The node that is constructed has a reference to the same graph object as src,
//! has a copy of the initial body used by src.
//! The successors of src are not copied.
//! \brief \ref requirement
TEST_CASE("input_node copy constructor"){
using namespace oneapi::tbb::flow;
graph g;
conformance::copy_counting_object<output_msg> fun2;
input_node<output_msg> node1(g, fun2);
conformance::test_push_receiver<output_msg> node2(g);
conformance::test_push_receiver<output_msg> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
input_node<output_msg> node_copy(node1);
conformance::copy_counting_object<output_msg> b2 = copy_body<conformance::copy_counting_object<output_msg>, input_node<output_msg>>(node_copy);
CHECK_MESSAGE((fun2.copy_count + 1 < b2.copy_count), "constructor should copy bodies");
oneapi::tbb::flow::make_edge(node_copy, node3);
node_copy.activate();
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 1), "Copied node doesn`t copy successor");
node1.activate();
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 1 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy successor");
}
//! Test inheritance relations
@@ -178,24 +123,40 @@ TEST_CASE("input_node and body copying"){
TEST_CASE("input_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
test_inheritance<output_msg>();
}
//! Test input_node forwarding
//! \brief \ref requirement
TEST_CASE("input_node forwarding"){
test_forwarding();
conformance::counting_functor<output_msg> fun(conformance::expected);
conformance::test_forwarding<oneapi::tbb::flow::input_node<output_msg>, void, output_msg>(5, fun);
}
//! Test input_node buffering
//! \brief \ref requirement
TEST_CASE("input_node buffering"){
test_buffering();
conformance::dummy_functor<int> fun;
conformance::test_buffering<oneapi::tbb::flow::input_node<int>, int>(fun);
}
//! Test calling input_node body
//! \brief \ref interface \ref requirement
TEST_CASE("input_node body") {
test_input_body();
oneapi::tbb::flow::graph g;
constexpr std::size_t counting_threshold = 10;
conformance::counting_functor<output_msg> fun(counting_threshold);
oneapi::tbb::flow::input_node<output_msg> node1(g, fun);
conformance::test_push_receiver<output_msg> node2(g);
oneapi::tbb::flow::make_edge(node1, node2);
node1.activate();
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == counting_threshold), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE((fun.execute_count == counting_threshold + 1), "Body of the node needs to be executed N + 1 times");
}
//! Test deduction guides
@@ -205,3 +166,75 @@ TEST_CASE("Deduction guides"){
test_deduction_guides();
#endif
}
//! Test that measured concurrency respects set limits
//! \brief \ref requirement
TEST_CASE("concurrency follows set limits"){
oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism,
oneapi::tbb::this_task_arena::max_concurrency());
utils::ConcurrencyTracker::Reset();
oneapi::tbb::flow::graph g;
conformance::concurrency_peak_checker_body counter(1);
oneapi::tbb::flow::input_node<int> testing_node(g, counter);
conformance::test_push_receiver<int> sink(g);
make_edge(testing_node, sink);
testing_node.activate();
g.wait_for_all();
}
//! Test node Output class meet the CopyConstructible requirements.
//! \brief \ref interface \ref requirement
TEST_CASE("Test input_node Output class") {
conformance::test_output_class<oneapi::tbb::flow::input_node<conformance::copy_counting_object<int>>>();
}
struct input_node_counter{
static int count;
int N;
input_node_counter(int n) : N(n){};
int operator()( oneapi::tbb::flow_control & fc ) {
++count;
if(count > N){
fc.stop();
return N;
}
return N;
}
};
struct function_node_counter{
static int count;
int operator()( int ) {
++count;
utils::doDummyWork(1000000);
CHECK_MESSAGE((input_node_counter::count <= function_node_counter::count + 1), "input_node `try_get()' call testing: a call to body is made only when the internal buffer is empty");
return 1;
}
};
int input_node_counter::count = 0;
int function_node_counter::count = 0;
//! Test input_node `try_get()' call testing: a call to body is made only when the internal buffer is empty.
//! \brief \ref requirement
TEST_CASE("input_node `try_get()' call testing: a call to body is made only when the internal buffer is empty.") {
oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, 1);
oneapi::tbb::flow::graph g;
input_node_counter fun1(500);
function_node_counter fun2;
oneapi::tbb::flow::function_node <int, int, oneapi::tbb::flow::rejecting> fnode(g, oneapi::tbb::flow::serial, fun2);
oneapi::tbb::flow::input_node<int> testing_node(g, fun1);
make_edge(testing_node, fnode);
testing_node.activate();
g.wait_for_all();
}
+193 -119
View File
@@ -18,175 +18,249 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_join_node.cpp
//! \brief Test for [flow_graph.join_node] specification
/*
TODO: implement missing conformance tests for join_node:
- [ ] Check that `OutputTuple' is an instantiation of a tuple.
- [ ] The copy constructor and copy assignment are called for each type within the `OutputTuple'.
- [ ] Check all possible policies of the node: `reserving', `key_matching', `queueing',
`tag_matching'. Check the semantics the node has with each policy separately.
- [ ] Check that corresponding methods are invoked in specified `KHash' type.
- [ ] Improve test for constructors, including their availability based on used Policy for the
node.
- [ ] Unify code style in the test by extracting the implementation from the `TEST_CASE' scope
into separate functions.
- [ ] Check that corresponding methods mentioned in the requirements are called for `Bi' types.
- [ ] Explicitly check that `input_ports_type' is defined, accessible and is a tuple of
corresponding to `OutputTuple' receivers.
- [ ] Explicitly check the method `join_node::input_ports()' exists, is accessible and it returns
a reference to the `input_ports_type' type.
- [ ] Implement `test_buffering' (for node policy).
- [ ] Check `try_get()' copies the generated tuple into passed argument and returns `true'. If
node is empty returns `false'.
- [ ] Check `tag_value' is defined and has properties specified.
- [ ] Add test for CTAD.
*/
using input_msg = conformance::message</*default_ctor*/true, /*copy_ctor*/true, /*copy_assign*/true>;
using my_input_tuple = std::tuple<int, float, input_msg>;
using namespace oneapi::tbb::flow;
using namespace std;
template<typename T>
void test_inheritance(){
CHECK_MESSAGE( (std::is_base_of<graph_node, join_node<std::tuple<T, T>>>::value), "join_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<sender<std::tuple<T, T>>, join_node<std::tuple<T, T>>>::value), "join_node should be derived from graph_node");
std::vector<my_input_tuple> get_values( conformance::test_push_receiver<my_input_tuple>& rr ) {
std::vector<my_input_tuple> messages;
int val = 0;
for(my_input_tuple tmp(0, 0.f, input_msg(0)); rr.try_get(tmp); ++val) {
messages.push_back(tmp);
}
return messages;
}
void test_copies(){
using namespace oneapi::tbb::flow;
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
void test_deduction_guides() {
using namespace tbb::flow;
graph g;
join_node<std::tuple<int, int>> n(g);
join_node<std::tuple<int, int>> n2(n);
using tuple_type = std::tuple<int, int, int>;
broadcast_node<int> b1(g), b2(g), b3(g);
broadcast_node<tuple_type> b4(g);
join_node<tuple_type> j0(g);
join_node <std::tuple<int, int, oneapi::tbb::flow::reserving>> nr(g);
join_node <std::tuple<int, int, oneapi::tbb::flow::reserving>> nr2(nr);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
join_node j1(follows(b1, b2, b3));
static_assert(std::is_same_v<decltype(j1), join_node<tuple_type>>);
join_node j2(follows(b1, b2, b3), reserving());
static_assert(std::is_same_v<decltype(j2), join_node<tuple_type, reserving>>);
join_node j3(precedes(b4));
static_assert(std::is_same_v<decltype(j3), join_node<tuple_type>>);
join_node j4(precedes(b4), reserving());
static_assert(std::is_same_v<decltype(j4), join_node<tuple_type, reserving>>);
#endif
join_node j5(j0);
static_assert(std::is_same_v<decltype(j5), join_node<tuple_type>>);
}
void test_forwarding(){
#endif
//! The node that is constructed has a reference to the same graph object as src.
//! The list of predecessors, messages in the input ports, and successors are not copied.
//! \brief \ref interface
TEST_CASE("join_node copy constructor"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::continue_node<int> node0( g,
[](oneapi::tbb::flow::continue_msg) { return 1; } );
join_node<std::tuple<int, int>> node1(g);
using output_t = join_node<std::tuple<int, int>>::output_type;
test_push_receiver<output_t> node2(g);
test_push_receiver<output_t> node3(g);
oneapi::tbb::flow::join_node<std::tuple<int>> node1(g);
conformance::test_push_receiver<std::tuple<int>> node2(g);
conformance::test_push_receiver<std::tuple<int>> node3(g);
oneapi::tbb::flow::make_edge(node0, oneapi::tbb::flow::input_port<0>(node1));
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
oneapi::tbb::flow::join_node<std::tuple<int>> node_copy(node1);
input_port<0>(node1).try_put(1);
input_port<1>(node1).try_put(1);
oneapi::tbb::flow::make_edge(node_copy, node3);
oneapi::tbb::flow::input_port<0>(node_copy).try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
}
auto values = conformance::get_values(node3);
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && values.size() == 1), "Copied node doesn`t copy successor");
//! Test broadcast
//! \brief \ref interface
TEST_CASE("join_node broadcast") {
test_forwarding();
}
node0.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 1 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy predecessor");
//! Test copy constructor
//! \brief \ref interface
TEST_CASE("join_node copy constructor") {
test_copies();
oneapi::tbb::flow::remove_edge(node1, node2);
oneapi::tbb::flow::input_port<0>(node1).try_put(1);
g.wait_for_all();
oneapi::tbb::flow::join_node<std::tuple<int>> node_copy2(node1);
oneapi::tbb::flow::make_edge(node_copy2, node3);
oneapi::tbb::flow::input_port<0>(node_copy2).try_put(2);
g.wait_for_all();
CHECK_MESSAGE((std::get<0>(conformance::get_values(node3)[0]) == 2), "Copied node doesn`t copy messages in the input ports");
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("join_node inheritance"){
test_inheritance<int>();
CHECK_MESSAGE((std::is_base_of<oneapi::tbb::flow::graph_node,
oneapi::tbb::flow::join_node<my_input_tuple>>::value),
"join_node should be derived from graph_node");
CHECK_MESSAGE((std::is_base_of<oneapi::tbb::flow::sender<my_input_tuple>,
oneapi::tbb::flow::join_node<my_input_tuple>>::value),
"join_node should be derived from sender<input_tuple>");
}
//! Test join_node behavior
//! Test join_node<queueing> behavior and broadcast property
//! \brief \ref requirement
TEST_CASE("join_node") {
graph g;
function_node<int,int>
f1( g, unlimited, [](const int &i) { return 2*i; } );
function_node<float,float>
f2( g, unlimited, [](const float &f) { return f/2; } );
TEST_CASE("join_node queueing policy and broadcast property") {
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::function_node<int, int>
f1( g, oneapi::tbb::flow::unlimited, [](const int &i) { return i; } );
oneapi::tbb::flow::function_node<float, float>
f2( g, oneapi::tbb::flow::unlimited, [](const float &f) { return f; } );
oneapi::tbb::flow::continue_node<input_msg> c1( g,
[](oneapi::tbb::flow::continue_msg) { return input_msg(1); } );
join_node< std::tuple<int,float> > j(g);
oneapi::tbb::flow::join_node<my_input_tuple, oneapi::tbb::flow::queueing> testing_node(g);
function_node< std::tuple<int,float> >
f3( g, unlimited,
[]( const std::tuple<int,float> &t ) {
CHECK_MESSAGE( (std::get<0>(t) == 6), "Expected to receive 6" );
CHECK_MESSAGE( (std::get<1>(t) == 1.5), "Expected to receive 1.5" );
conformance::test_push_receiver<my_input_tuple> q_node(g);
std::atomic<int> number{1};
oneapi::tbb::flow::function_node<my_input_tuple, my_input_tuple>
f3( g, oneapi::tbb::flow::unlimited,
[&]( const my_input_tuple &t ) {
CHECK_MESSAGE((std::get<0>(t) == number), "Messages must be in first-in first-out order" );
CHECK_MESSAGE((std::get<1>(t) == static_cast<float>(number) + 0.5f), "Messages must be in first-in first-out order" );
CHECK_MESSAGE((std::get<2>(t) == 1), "Messages must be in first-in first-out order" );
++number;
return t;
} );
make_edge( f1, input_port<0>( j ) );
make_edge( f2, input_port<1>( j ) );
make_edge( j, f3 );
oneapi::tbb::flow::make_edge(f1, oneapi::tbb::flow::input_port<0>(testing_node));
oneapi::tbb::flow::make_edge(f2, oneapi::tbb::flow::input_port<1>(testing_node));
oneapi::tbb::flow::make_edge(c1, oneapi::tbb::flow::input_port<2>(testing_node));
make_edge(testing_node, f3);
make_edge(f3, q_node);
f1.try_put( 3 );
f2.try_put( 3 );
g.wait_for_all( );
}
f1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((get_values(q_node).size() == 0),
"join_node must broadcast when there is at least one message at each input port");
f1.try_put(2);
f2.try_put(1.5f);
g.wait_for_all();
CHECK_MESSAGE((get_values(q_node).size() == 0),
"join_node must broadcast when there is at least one message at each input port");
f1.try_put(3);
f2.try_put(2.5f);
c1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((get_values(q_node).size() == 1),
"join_node must broadcast when there is at least one message at each input port");
f2.try_put(3.5f);
c1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((get_values(q_node).size() == 1),
"If at least one successor accepts the tuple, the head of each input port’s queue is removed");
c1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((get_values(q_node).size() == 1),
"If at least one successor accepts the tuple, the head of each input port’s queue is removed");
c1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
CHECK_MESSAGE((get_values(q_node).size() == 0),
"join_node must broadcast when there is at least one message at each input port");
//! Test join_node key matching behavior
//! \brief \ref requirement
TEST_CASE("remove edge to join_node"){
graph g;
continue_node<int> c(g, [](const continue_msg&){ return 1; });
join_node<tuple<int> > jn(g);
queue_node<tuple<int> > q(g);
oneapi::tbb::flow::remove_edge(testing_node, f3);
make_edge(jn, q);
make_edge(c, jn);
c.try_put(continue_msg());
f1.try_put(1);
f2.try_put(1);
c1.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
tuple<int> tmp = tuple<int>(0);
CHECK_MESSAGE( (q.try_get(tmp)== true), "Message should pass when edge exists");
CHECK_MESSAGE( (tmp == tuple<int>(1) ), "Message should pass when edge exists");
CHECK_MESSAGE( (q.try_get(tmp)== false), "Message should not pass after item is consumed");
my_input_tuple tmp(0, 0.f, input_msg(0));
CHECK_MESSAGE((testing_node.try_get(tmp)), "If no one successor accepts the tuple the messages\
must remain in their respective input port queues");
CHECK_MESSAGE((tmp == my_input_tuple(1, 1.f, input_msg(1))), "If no one successor accepts the tuple\
the messages must remain in their respective input port queues");
}
remove_edge(c, jn);
//! Test join_node<reserving> behavior
//! \brief \ref requirement
TEST_CASE("join_node reserving policy") {
conformance::test_with_reserving_join_node_class<oneapi::tbb::flow::write_once_node<int>>();
}
template<typename KeyType>
struct MyHash{
std::size_t hash(const KeyType &k) const {
return k * 2000 + 3;
}
bool equal(const KeyType &k1, const KeyType &k2) const{
return hash(k1) == hash(k2);
}
};
//! Test join_node<key_matching> behavior
//! \brief \ref requirement
TEST_CASE("join_node key_matching policy"){
oneapi::tbb::flow::graph g;
auto body1 = [](const oneapi::tbb::flow::continue_msg &) -> int { return 1; };
auto body2 = [](const float &val) -> int { return static_cast<int>(val); };
oneapi::tbb::flow::join_node<std::tuple<oneapi::tbb::flow::continue_msg, float>,
oneapi::tbb::flow::key_matching<int, MyHash<int>>> testing_node(g, body1, body2);
oneapi::tbb::flow::input_port<0>(testing_node).try_put(oneapi::tbb::flow::continue_msg());
oneapi::tbb::flow::input_port<1>(testing_node).try_put(1.3f);
c.try_put(continue_msg());
g.wait_for_all();
tmp = tuple<int>(0);
CHECK_MESSAGE( (q.try_get(tmp)== false), "Message should not pass when edge doesn't exist");
CHECK_MESSAGE( (tmp == tuple<int>(0)), "Value should not be altered");
std::tuple<oneapi::tbb::flow::continue_msg, float> tmp;
CHECK_MESSAGE((testing_node.try_get(tmp)), "Mapped keys should match.\
If no successor accepts the tuple, it is must been saved and will be forwarded on a subsequent try_get");
CHECK_MESSAGE((!testing_node.try_get(tmp)), "Message should not exist after item is consumed");
}
//! Test join_node key matching behavior
//! Test join_node<tag_matching> behavior
//! \brief \ref requirement
TEST_CASE("join_node key_matching"){
graph g;
auto body1 = [](const continue_msg &) -> int { return 1; };
auto body2 = [](const double &val) -> int { return int(val); };
TEST_CASE("join_node tag_matching policy"){
oneapi::tbb::flow::graph g;
auto body1 = [](const oneapi::tbb::flow::continue_msg &) -> oneapi::tbb::flow::tag_value { return 1; };
auto body2 = [](const float &val) -> oneapi::tbb::flow::tag_value { return static_cast<oneapi::tbb::flow::tag_value>(val); };
join_node<std::tuple<continue_msg, double>, key_matching<int>> jn(g, body1, body2);
oneapi::tbb::flow::join_node<std::tuple<oneapi::tbb::flow::continue_msg, float>,
oneapi::tbb::flow::tag_matching> testing_node(g, body1, body2);
input_port<0>(jn).try_put(continue_msg());
input_port<1>(jn).try_put(1.3);
oneapi::tbb::flow::input_port<0>(testing_node).try_put(oneapi::tbb::flow::continue_msg());
oneapi::tbb::flow::input_port<1>(testing_node).try_put(1.3f);
g.wait_for_all( );
g.wait_for_all();
tuple<continue_msg, double> tmp;
CHECK_MESSAGE( (jn.try_get(tmp) == true), "Mapped keys should match");
std::tuple<oneapi::tbb::flow::continue_msg, float> tmp;
CHECK_MESSAGE((testing_node.try_get(tmp) == true), "Mapped keys should match");
}
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
//! Test deduction guides
//! \brief \ref requirement
TEST_CASE("Deduction guides test"){
test_deduction_guides();
}
#endif
//! Test join_node input_ports() returns a tuple of input ports.
//! \brief \ref interface \ref requirement
TEST_CASE("join_node output_ports") {
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::join_node<std::tuple<int>> node(g);
CHECK_MESSAGE((std::is_same<oneapi::tbb::flow::join_node<std::tuple<int>>::input_ports_type&,
decltype(node.input_ports())>::value), "join_node input_ports should returns a tuple of input ports");
}
+136 -91
View File
@@ -18,122 +18,167 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_limiter_node.cpp
//! \brief Test for [flow_graph.limiter_node] specification
/*
TODO: implement missing conformance tests for limiter_node:
- [ ] The copy constructor and copy assignment are called for the node's type template parameter.
- [ ] Add use of `decrement' member into the `test_limiting' and see how positive and negative
values sent to `decrement's' port affect node's internal threshold.
- [ ] Add test checking the node gets value from the predecessors when threshold decreases enough.
- [ ] Add test that `continue_msg' decreases the threshold by one.
*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, limiter_node<T>>::value), "sequencer_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, limiter_node<T>>::value), "sequencer_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, limiter_node<T>>::value), "sequencer_node should be derived from sender<T>");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
limiter_node<int> n(g, 5);
limiter_node<int> n2(n);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::limiter_node<int> node(g, 5);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == false), "try_get after rejection should not succeed");
CHECK_MESSAGE( (tmp == -1), "try_get after rejection should not set value");
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::limiter_node<int> node1(g, 5);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
}
void test_limiting(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::limiter_node<int> node1(g, 5);
test_push_receiver<int> node2(g);
oneapi::tbb::flow::make_edge(node1, node2);
for(int i = 0; i < 10; ++i)
node1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 5), "Descendant of the node needs be receive limited number of messages");
}
using input_msg = conformance::message</*default_ctor*/true, /*copy_ctor*/true/*enable for queue_node successor*/, /*copy_assign*/true/*enable for queue_node successor*/>;
//! Test limiter_node limiting
//! \brief \ref requirement
TEST_CASE("limiter_node limiting"){
test_limiting();
oneapi::tbb::flow::graph g;
constexpr int limit = 5;
oneapi::tbb::flow::limiter_node<input_msg> node1(g, limit);
conformance::test_push_receiver<input_msg> node2(g);
oneapi::tbb::flow::make_edge(node1, node2);
for(int i = 0; i < limit * 2; ++i)
node1.try_put(input_msg(1));
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == limit), "Descendant of the node needs be receive limited number of messages");
}
//! Test function_node broadcast
//! Test node broadcast messages to successors
//! \brief \ref requirement
TEST_CASE("limiter_node broadcast"){
test_forwarding();
conformance::test_forwarding<oneapi::tbb::flow::limiter_node<int>, int>(1, 5);
conformance::test_forwarding<oneapi::tbb::flow::limiter_node<input_msg>, input_msg>(1, 5);
}
//! Test limiter_node buffering
//! Test node not buffered unsuccessful message, and try_get after rejection should not succeed.
//! \brief \ref requirement
TEST_CASE("limiter_node buffering"){
test_buffering();
conformance::test_buffering<oneapi::tbb::flow::limiter_node<int>, int>(5);
conformance::test_buffering<oneapi::tbb::flow::limiter_node<int, int>, int>(5);
}
//! Test copy constructor
//! The node that is constructed has a reference to the same graph object as src, has the same threshold.
//! The predecessors and successors of src are not copied.
//! \brief \ref interface
TEST_CASE("limiter_node copy constructor"){
test_copies();
using namespace oneapi::tbb::flow;
graph g;
limiter_node<int> node0(g, 1);
limiter_node<int> node1(g, 1);
conformance::test_push_receiver<int> node2(g);
conformance::test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node0, node1);
oneapi::tbb::flow::make_edge(node1, node2);
limiter_node<int> node_copy(node1);
oneapi::tbb::flow::make_edge(node_copy, node3);
node_copy.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 1), "Copied node doesn`t copy successor");
node_copy.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 0), "Copied node copy threshold");
node0.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((conformance::get_values(node2).size() == 1 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy predecessor");
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("limiter_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
conformance::test_inheritance<oneapi::tbb::flow::limiter_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::limiter_node<float>, float, float>();
conformance::test_inheritance<oneapi::tbb::flow::limiter_node<input_msg>, input_msg, input_msg>();
}
//! Test limiter_node decrementer
//! \brief \ref interface
TEST_CASE("limiter_node decrementer"){
const int threshold = 5;
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::limiter_node<int, int> limit(g, threshold);
oneapi::tbb::flow::queue_node<int> queue(g);
make_edge(limit, queue);
int m = 0;
CHECK_MESSAGE(( limit.try_put( m++ )), "Newly constructed limiter node does not accept message." );
CHECK_MESSAGE(limit.decrementer().try_put( -threshold ), // close limiter's gate
"Limiter node decrementer's port does not accept message." );
CHECK_MESSAGE(( !limit.try_put( m++ )), "Closed limiter node's accepts message." );
CHECK_MESSAGE(limit.decrementer().try_put( threshold + 5 ), // open limiter's gate
"Limiter node decrementer's port does not accept message." );
for( int i = 0; i < threshold; ++i )
CHECK_MESSAGE(( limit.try_put( m++ )), "Limiter node does not accept message while open." );
CHECK_MESSAGE(( !limit.try_put( m )), "Limiter node's gate is not closed." );
g.wait_for_all();
int expected[] = {0, 2, 3, 4, 5, 6};
int actual = -1; m = 0;
while( queue.try_get(actual) )
CHECK_MESSAGE(actual == expected[m++], "" );
CHECK_MESSAGE(( sizeof(expected) / sizeof(expected[0]) == m), "Not all messages have been processed." );
g.wait_for_all();
const size_t threshold2 = size_t(-1);
oneapi::tbb::flow::limiter_node<int, long long> limit2(g, threshold2);
make_edge(limit2, queue);
CHECK_MESSAGE(( limit2.try_put( 1 )), "Newly constructed limiter node does not accept message." );
long long decrement_value = (long long)( size_t(-1)/2 );
CHECK_MESSAGE(limit2.decrementer().try_put( -decrement_value ),
"Limiter node decrementer's port does not accept message" );
CHECK_MESSAGE(( limit2.try_put( 2 )), "Limiter's gate should not be closed yet." );
CHECK_MESSAGE(limit2.decrementer().try_put( -decrement_value ),
"Limiter node decrementer's port does not accept message" );
CHECK_MESSAGE(( !limit2.try_put( 3 )), "Overflow happened for internal counter." );
int expected2[] = {1, 2};
actual = -1; m = 0;
while( queue.try_get(actual) )
CHECK_MESSAGE(actual == expected2[m++], "" );
CHECK_MESSAGE(( sizeof(expected2) / sizeof(expected2[0]) == m), "Not all messages have been processed." );
g.wait_for_all();
const size_t threshold3 = 10;
oneapi::tbb::flow::limiter_node<int, long long> limit3(g, threshold3);
make_edge(limit3, queue);
long long decrement_value3 = 3;
CHECK_MESSAGE(limit3.decrementer().try_put( -decrement_value3 ),
"Limiter node decrementer's port does not accept message" );
m = 0;
while( limit3.try_put( m ) ){ m++; };
CHECK_MESSAGE(m == threshold3 - decrement_value3, "Not all messages have been accepted." );
actual = -1; m = 0;
while( queue.try_get(actual) ){
CHECK_MESSAGE(actual == m++, "Not all messages have been processed." );
}
g.wait_for_all();
CHECK_MESSAGE(m == threshold3 - decrement_value3, "Not all messages have been processed." );
const size_t threshold4 = 10;
oneapi::tbb::flow::limiter_node<int> limit4(g, threshold4);
make_edge(limit4, queue);
limit4.try_put(-1);
CHECK_MESSAGE(limit4.decrementer().try_put(oneapi::tbb::flow::continue_msg()),
"Limiter node decrementer's port does not accept continue_msg" );
m = 0;
while( limit4.try_put( m ) ){ m++; };
CHECK_MESSAGE(m == threshold4, "Not all messages have been accepted." );
actual = -1; m = -1;
while( queue.try_get(actual) ){
CHECK_MESSAGE(actual == m++, "Not all messages have been processed." );
}
g.wait_for_all();
}
@@ -18,308 +18,107 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#define CONFORMANCE_MULTIFUNCTION_NODE
#include "conformance_flowgraph.h"
//! \file conformance_multifunction_node.cpp
//! \brief Test for [flow_graph.function_node] specification
using input_msg = conformance::message</*default_ctor*/true, /*copy_ctor*/true, /*copy_assign*/false>;
using output_msg = conformance::message</*default_ctor*/false, /*copy_ctor*/false, /*copy_assign*/false>;
/*
TODO: implement missing conformance tests for multifunction_node:
- [ ] Implement test_forwarding that checks messages are broadcast to all the successors connected
to the output port the message is being sent to. And check that the value passed is the
actual one received.
- [ ] Explicit test for copy constructor of the node.
- [ ] Constructor with explicitly passed Policy parameter: `template<typename Body>
multifunction_node( graph &g, size_t concurrency, Body body, Policy(), node_priority_t priority = no_priority )'.
- [ ] Concurrency testing of the node: make a loop over possible concurrency levels. It is
important to test at least on five values: 1, oneapi::tbb::flow::serial, `max_allowed_parallelism'
obtained from `oneapi::tbb::global_control', `oneapi::tbb::flow::unlimited', and, if `max allowed
parallelism' is > 2, use something in the middle of the [1, max_allowed_parallelism]
interval. Use `utils::ExactConcurrencyLevel' entity (extending it if necessary).
- [ ] make `test_rejecting' deterministic, i.e. avoid dependency on OS scheduling of the threads;
add check that `try_put()' returns `false'
- [ ] The `copy_body' function copies altered body (e.g. after successful `try_put()' call).
- [ ] `output_ports_type' is defined and accessible by the user.
- [ ] Explicit test on `mfn::output_ports()' method.
- [ ] The copy constructor and copy assignment are called for the node's input and output types.
- [ ] Add CTAD test.
Test function_node is a graph_node, receiver<Input>, and sender<Output>
*/
template< typename OutputType >
struct mf_functor {
std::atomic<std::size_t>& local_execute_count;
mf_functor(std::atomic<std::size_t>& execute_count ) :
local_execute_count (execute_count)
{ }
mf_functor( const mf_functor &f ) : local_execute_count(f.local_execute_count) { }
void operator=(const mf_functor &f) { local_execute_count = std::size_t(f.local_execute_count); }
void operator()( const int& argument, oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
++local_execute_count;
std::get<0>(op).try_put(argument);
}
};
template<typename I, typename O>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, multifunction_node<I, O>>::value), "multifunction_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<I>, multifunction_node<I, O>>::value), "multifunction_node should be derived from receiver<Input>");
CHECK_MESSAGE((std::is_base_of<graph_node, multifunction_node<I, O>>::value), "multifunction_node should be derived from graph_node");
CHECK_MESSAGE((std::is_base_of<receiver<I>, multifunction_node<I, O>>::value), "multifunction_node should be derived from receiver<Input>");
}
void test_multifunc_body(){
oneapi::tbb::flow::graph g;
std::atomic<size_t> local_count(0);
mf_functor<std::tuple<int>> fun(local_count);
oneapi::tbb::flow::multifunction_node<int, std::tuple<int>, oneapi::tbb::flow::rejecting> node1(g, oneapi::tbb::flow::unlimited, fun);
const size_t n = 10;
for(size_t i = 0; i < n; ++i) {
CHECK_MESSAGE((node1.try_put(1) == true), "try_put needs to return true");
}
g.wait_for_all();
CHECK_MESSAGE( (local_count == n), "Body of the node needs to be executed N times");
//! Test node reject the incoming message if the concurrency limit achieved.
//! \brief \ref interface
TEST_CASE("multifunction_node with rejecting policy"){
conformance::test_rejecting<oneapi::tbb::flow::multifunction_node<int, std::tuple<int>, oneapi::tbb::flow::rejecting>>();
}
template<typename I, typename O>
struct CopyCounterBody{
size_t copy_count;
CopyCounterBody():
copy_count(0) {}
CopyCounterBody(const CopyCounterBody<I, O>& other):
copy_count(other.copy_count + 1) {}
CopyCounterBody& operator=(const CopyCounterBody<I, O>& other)
{ copy_count = other.copy_count + 1; return *this;}
void operator()( const I& argument, oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
std::get<0>(op).try_put(argument);
}
};
void test_copies(){
using namespace oneapi::tbb::flow;
CopyCounterBody<int, std::tuple<int>> b;
graph g;
multifunction_node<int, std::tuple<int>> fn(g, unlimited, b);
CopyCounterBody<int, std::tuple<int>> b2 = copy_body<CopyCounterBody<int, std::tuple<int>>,
multifunction_node<int, std::tuple<int>>>(fn);
CHECK_MESSAGE( (b.copy_count + 2 <= b2.copy_count), "copy_body and constructor should copy bodies");
//! Test nodes for execution with priority in single-threaded configuration
//! \brief \ref interface
TEST_CASE("multifunction_node priority"){
conformance::test_priority<oneapi::tbb::flow::multifunction_node<input_msg, std::tuple<int>>, input_msg>(oneapi::tbb::flow::unlimited);
}
template< typename OutputType >
struct id_functor {
void operator()( const int& argument, oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
std::get<0>(op).try_put(argument);
}
};
void test_forwarding(){
oneapi::tbb::flow::graph g;
id_functor<int> fun;
oneapi::tbb::flow::multifunction_node<int, std::tuple<int>> node1(g, oneapi::tbb::flow::unlimited, fun);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node must receive one message.");
//! Test function_node has a user-settable concurrency limit. It can be set to one of predefined values.
//! The user can also provide a value of type std::size_t to limit concurrency.
//! Test that not more than limited threads works in parallel.
//! \brief \ref interface
TEST_CASE("multifunction_node concurrency"){
conformance::test_concurrency<oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>>();
}
void test_rejecting_buffering(){
oneapi::tbb::flow::graph g;
id_functor<int> fun;
oneapi::tbb::flow::multifunction_node<int, std::tuple<int>, oneapi::tbb::flow::rejecting> node(g, oneapi::tbb::flow::unlimited, fun);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
int tmp = -1;
CHECK_MESSAGE( (std::get<0>(node.output_ports()).try_get(tmp) == false), "try_get after rejection should not succeed");
CHECK_MESSAGE( (tmp == -1), "try_get after rejection should alter passed value");
g.wait_for_all();
}
void test_policy_ctors(){
//! Test all node constructors
//! \brief \ref interface
TEST_CASE("multifunction_node constructors"){
using namespace oneapi::tbb::flow;
graph g;
id_functor<int> fun;
conformance::counting_functor<int> fun;
multifunction_node<int, std::tuple<int>, lightweight> lw_node(g, oneapi::tbb::flow::serial, fun);
multifunction_node<int, std::tuple<int>, queueing_lightweight> qlw_node(g, oneapi::tbb::flow::serial, fun);
multifunction_node<int, std::tuple<int>, rejecting_lightweight> rlw_node(g, oneapi::tbb::flow::serial, fun);
multifunction_node<int, std::tuple<int>> fn1(g, unlimited, fun);
multifunction_node<int, std::tuple<int>> fn2(g, unlimited, fun, oneapi::tbb::flow::node_priority_t(1));
multifunction_node<int, std::tuple<int>, lightweight> lw_node1(g, serial, fun, lightweight());
multifunction_node<int, std::tuple<int>, lightweight> lw_node2(g, serial, fun, lightweight(), oneapi::tbb::flow::node_priority_t(1));
}
std::atomic<size_t> my_concurrency;
std::atomic<size_t> my_max_concurrency;
struct concurrency_functor {
void operator()( const int& argument, oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
++my_concurrency;
size_t old_value = my_max_concurrency;
while(my_max_concurrency < my_concurrency &&
!my_max_concurrency.compare_exchange_weak(old_value, my_concurrency))
;
size_t ms = 1000;
std::chrono::milliseconds sleep_time( ms );
std::this_thread::sleep_for( sleep_time );
--my_concurrency;
std::get<0>(op).try_put(argument);
}
};
void test_node_concurrency(){
my_concurrency = 0;
my_max_concurrency = 0;
oneapi::tbb::flow::graph g;
concurrency_functor counter;
oneapi::tbb::flow::multifunction_node <int, std::tuple<int>> fnode(g, oneapi::tbb::flow::serial, counter);
test_push_receiver<int> sink(g);
make_edge(std::get<0>(fnode.output_ports()), sink);
for(int i = 0; i < 10; ++i){
fnode.try_put(i);
}
g.wait_for_all();
CHECK_MESSAGE( ( my_max_concurrency.load() == 1), "Measured parallelism over limit");
}
void test_priority(){
size_t concurrency_limit = 1;
oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, concurrency_limit);
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::continue_node<int> source(g,
[](oneapi::tbb::flow::continue_msg){ return 1;});
source.try_put(oneapi::tbb::flow::continue_msg());
first_functor<int>::first_id = -1;
first_functor<int> low_functor(1);
first_functor<int> high_functor(2);
oneapi::tbb::flow::multifunction_node<int, std::tuple<int>> high(g, oneapi::tbb::flow::unlimited, high_functor, oneapi::tbb::flow::node_priority_t(1));
oneapi::tbb::flow::multifunction_node<int, std::tuple<int>> low(g, oneapi::tbb::flow::unlimited, low_functor);
make_edge(source, low);
make_edge(source, high);
g.wait_for_all();
CHECK_MESSAGE( (first_functor<int>::first_id == 2), "High priority node should execute first");
}
void test_rejecting(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::multifunction_node <int, std::tuple<int>, oneapi::tbb::flow::rejecting> fnode(g, oneapi::tbb::flow::serial,
[&](const int& argument, oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ){
size_t ms = 50;
std::chrono::milliseconds sleep_time( ms );
std::this_thread::sleep_for( sleep_time );
std::get<0>(op).try_put(argument);
});
test_push_receiver<int> sink(g);
make_edge(std::get<0>(fnode.output_ports()), sink);
for(int i = 0; i < 10; ++i){
fnode.try_put(i);
}
g.wait_for_all();
CHECK_MESSAGE( (get_count(sink) == 1), "Messages should be rejected while the first is being processed");
}
//! Test multifunction_node with rejecting policy
//! The node that is constructed has a reference to the same graph object as src, has a copy of the initial body used by src, and has the same concurrency threshold as src.
//! The predecessors and successors of src are not copied.
//! \brief \ref interface
TEST_CASE("multifunction_node with rejecting policy"){
test_rejecting();
TEST_CASE("multifunction_node copy constructor"){
conformance::test_copy_ctor<oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>>();
}
//! Test priorities
//! \brief \ref interface
TEST_CASE("multifunction_node priority"){
test_priority();
}
//! Test concurrency
//! \brief \ref interface
TEST_CASE("multifunction_node concurrency"){
test_node_concurrency();
}
//! Test constructors
//! \brief \ref interface
TEST_CASE("multifunction_node constructors"){
test_policy_ctors();
}
//! Test function_node buffering
//! Test node not buffered unsuccessful message, and try_get after rejection should not succeed.
//! \brief \ref requirement
TEST_CASE("multifunction_node buffering"){
test_rejecting_buffering();
conformance::dummy_functor<int> fun;
conformance::test_buffering<oneapi::tbb::flow::multifunction_node<input_msg, std::tuple<int>,
oneapi::tbb::flow::rejecting>, input_msg>(oneapi::tbb::flow::unlimited, fun);
}
//! Test function_node broadcasting
//! Test multifunction_node broadcasting
//! \brief \ref requirement
TEST_CASE("multifunction_node broadcast"){
test_forwarding();
conformance::counting_functor<int> fun(conformance::expected);
conformance::test_forwarding<oneapi::tbb::flow::multifunction_node<input_msg, std::tuple<int>>, input_msg, int>(1, oneapi::tbb::flow::unlimited, fun);
}
//! Test body copying and copy_body logic
//! Test the body object passed to a node is copied
//! \brief \ref interface
TEST_CASE("multifunction_node constructors"){
test_copies();
TEST_CASE("multifunction_node copy body"){
conformance::test_copy_body_function<oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>, conformance::copy_counting_object<int>>(oneapi::tbb::flow::unlimited);
}
//! Test calling function body
//! Test execution of node body
//! Test node can do try_put call
//! \brief \ref interface \ref requirement
TEST_CASE("multifunction_node body") {
test_multifunc_body();
conformance::test_body_exec<oneapi::tbb::flow::multifunction_node<input_msg, std::tuple<output_msg>, oneapi::tbb::flow::rejecting>, input_msg, output_msg>(oneapi::tbb::flow::unlimited);
}
//! Test multifunction_node output_ports() returns a tuple of output ports.
//! \brief \ref interface \ref requirement
TEST_CASE("multifunction_node output_ports") {
using namespace oneapi::tbb::flow;
graph g;
conformance::dummy_functor<int> fun;
oneapi::tbb::flow::multifunction_node <int, std::tuple<int>> node(g, unlimited, fun);
CHECK_MESSAGE((std::is_same<oneapi::tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type&,
decltype(node.output_ports())>::value), "multifunction_node output_ports should returns a tuple of output ports");
}
//! Test inheritance relations
@@ -327,4 +126,12 @@ TEST_CASE("multifunction_node body") {
TEST_CASE("multifunction_node superclasses"){
test_inheritance<int, std::tuple<int>>();
test_inheritance<void*, std::tuple<float>>();
test_inheritance<input_msg, std::tuple<output_msg>>();
}
//! Test node Input class meet the DefaultConstructible and CopyConstructible requirements and Output class meet the CopyConstructible requirements.
//! \brief \ref interface \ref requirement
TEST_CASE("Test function_node Output and Input class") {
using Body = conformance::copy_counting_object<int>;
conformance::test_output_input_class<oneapi::tbb::flow::multifunction_node<Body, std::tuple<Body>>, Body>();
}
+10 -2
View File
@@ -40,8 +40,12 @@ TEST_CASE("Basic Locable requirement test") {
GeneralTest<oneapi::tbb::spin_rw_mutex>("Spin RW Mutex");
GeneralTest<oneapi::tbb::queuing_mutex>("Queuing Mutex");
GeneralTest<oneapi::tbb::queuing_rw_mutex>("Queuing RW Mutex");
// TODO: Consider adding Thread Sanitizer (note that accesses inside the transaction
// considered as races by Thread Sanitizer)
#if !__TBB_USE_THREAD_SANITIZER
GeneralTest<oneapi::tbb::speculative_spin_mutex>("Speculative Spin Mutex");
GeneralTest<oneapi::tbb::speculative_spin_rw_mutex>("Speculative Spin RW Mutex");
#endif
// NullMutexes
GeneralTest<oneapi::tbb::null_mutex, utils::AtomicCounter<oneapi::tbb::null_mutex>>("Null Mutex", false);
GeneralTest<oneapi::tbb::null_rw_mutex, utils::AtomicCounter<oneapi::tbb::null_rw_mutex>>("Null RW Mutex", false);
@@ -56,8 +60,10 @@ TEST_CASE("Lockable requirement test") {
TestTryAcquire<oneapi::tbb::spin_rw_mutex>("Spin RW Mutex");
TestTryAcquire<oneapi::tbb::queuing_mutex>("Queuing Mutex");
TestTryAcquire<oneapi::tbb::queuing_rw_mutex>("Queuing RW Mutex");
#if !__TBB_USE_THREAD_SANITIZER
TestTryAcquire<oneapi::tbb::speculative_spin_mutex>("Speculative Spin Mutex");
TestTryAcquire<oneapi::tbb::speculative_spin_rw_mutex>("Speculative Spin RW Mutex");
#endif
TestTryAcquire<oneapi::tbb::null_mutex>("Null Mutex");
}
@@ -67,16 +73,18 @@ TEST_CASE("Shared mutexes (reader/writer) test") {
// General reader writer capabilities + upgrade/downgrade
TestReaderWriterLock<oneapi::tbb::spin_rw_mutex>("Spin RW Mutex");
TestReaderWriterLock<oneapi::tbb::queuing_rw_mutex>("Queuing RW Mutex");
TestReaderWriterLock<oneapi::tbb::speculative_spin_rw_mutex>("Speculative Spin RW Mutex");
TestNullRWMutex<oneapi::tbb::null_rw_mutex>("Null RW Mutex");
// Single threaded read/write try_acquire operations
TestTryAcquireReader<oneapi::tbb::spin_rw_mutex>("Spin RW Mutex");
TestTryAcquireReader<oneapi::tbb::queuing_rw_mutex>("Queuing RW Mutex");
TestRWStateMultipleChange<oneapi::tbb::spin_rw_mutex>("Spin RW Mutex");
TestRWStateMultipleChange<oneapi::tbb::queuing_rw_mutex>("Queuing RW Mutex");
TestTryAcquireReader<oneapi::tbb::null_rw_mutex>("Null RW Mutex");
#if !__TBB_USE_THREAD_SANITIZER
TestReaderWriterLock<oneapi::tbb::speculative_spin_rw_mutex>("Speculative Spin RW Mutex");
TestTryAcquireReader<oneapi::tbb::speculative_spin_rw_mutex>("Speculative Spin RW Mutex");
TestRWStateMultipleChange<oneapi::tbb::speculative_spin_rw_mutex>("Speculative Spin RW Mutex");
TestTryAcquireReader<oneapi::tbb::null_rw_mutex>("Null RW Mutex");
#endif
}
//! Testing ISO C++ Mutex and Shared Mutex requirements.
+61 -98
View File
@@ -18,129 +18,92 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#define CONFORMANCE_BUFFERING_NODES
#define CONFORMANCE_OVERWRITE_NODE
#include "conformance_flowgraph.h"
//! \file conformance_overwrite_node.cpp
//! \brief Test for [flow_graph.overwrite_node] specification
/*
TODO: implement missing conformance tests for overwrite_node:
- [ ] The copy constructor and copy assignment are called for the node's type template parameter.
- [ ] Improve copy constructor test and general constructor tests.
- [ ] Write test checking the value is initially invalid.
- [ ] Write test checking that the gets from the node are non-destructive, but the first `try_get'
fails.
- [ ] Test that first `try_get' fails.
- [ ] Add test on `overwrite_node::is_valid()' method.
- [ ] Add test on `overwrite_node::clear()' method.
- [ ] Add test with reserving `join_node' as node's successor. Use example from the spec.
*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, overwrite_node<T>>::value), "overwrite_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, overwrite_node<T>>::value), "overwrite_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, overwrite_node<T>>::value), "overwrite_node should be derived from sender<T>");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
overwrite_node<int> fn(g);
overwrite_node<int> fn2(fn);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::overwrite_node<int> node(g);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == true), "try_get after rejection should succeed");
CHECK_MESSAGE( (tmp == 1), "try_get after rejection should set value");
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::overwrite_node<int> node1(g);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
}
void test_overwriting(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::overwrite_node<int> node1(g);
int tmp = -1;
node1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (node1.try_get(tmp) == true), "Descendant needs to receive a message");
CHECK_MESSAGE( (tmp == 1), "Descendant needs to receive a correct value");
node1.try_put(2);
g.wait_for_all();
CHECK_MESSAGE( (node1.try_get(tmp) == true), "Descendant needs to receive a message");
CHECK_MESSAGE( (tmp == 2), "Descendant needs to receive a correct value");
}
//! Test overwrite_node behavior
//! \brief \ref requirement
TEST_CASE("overwrite_node messages"){
test_overwriting();
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::overwrite_node<int> testing_node(g);
int tmp = -1;
testing_node.try_put(1);
g.wait_for_all();
CHECK_MESSAGE((testing_node.try_get(tmp) == true), "Descendant needs to receive a message");
CHECK_MESSAGE((tmp == 1), "Descendant needs to receive a correct value");
testing_node.try_put(2);
g.wait_for_all();
CHECK_MESSAGE((testing_node.try_get(tmp) == true), "Descendant needs to receive a message");
CHECK_MESSAGE((tmp == 2), "Descendant needs to receive a correct value");
}
//! Test function_node broadcast
//! \brief \ref requirement
TEST_CASE("overwrite_node broadcast"){
test_forwarding();
conformance::test_forwarding<oneapi::tbb::flow::overwrite_node<int>, int>(1);
}
//! Test function_node buffering
//! \brief \ref requirement
TEST_CASE("overwrite_node buffering"){
test_buffering();
conformance::test_buffering<oneapi::tbb::flow::overwrite_node<int>, int>();
}
//! Test copy constructor
//! \brief \ref interface
//! The node that is constructed has a reference to the same graph object as src,with an invalid internal buffer item.
//! The buffered value and list of successors are not copied from src.
//! \brief \ref requirement
TEST_CASE("overwrite_node copy constructor"){
test_copies();
conformance::test_copy_ctor_for_buffering_nodes<oneapi::tbb::flow::overwrite_node<int>>();
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("overwrite_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
conformance::test_inheritance<oneapi::tbb::flow::overwrite_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::overwrite_node<void*>, void*, void*>();
}
//! Test overwrite_node node constructor
//! \brief \ref requirement
TEST_CASE("overwrite_node constructor"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::overwrite_node<int> testing_node(g);
int tmp = -1;
CHECK_MESSAGE((!testing_node.is_valid()), "Constructed node has invalid internal buffer item");
CHECK_MESSAGE((!testing_node.try_get(tmp)), "Gets from the node are non-destructive, but the first `try_get' fails");
}
//! Test overwrite_node node `is_valid()` and `clear()`
//! \brief \ref requirement
TEST_CASE("overwrite_node methods"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::overwrite_node<int> testing_node(g);
CHECK_MESSAGE((!testing_node.is_valid()), "Constructed node has invalid internal buffer item");
testing_node.try_put(1);
CHECK_MESSAGE((testing_node.is_valid()), "Buffer must be valid after try_put call");
testing_node.clear();
CHECK_MESSAGE((!testing_node.is_valid()), "call `clear` invalidates the value held in the buffer.");
}
//! The following test shows the possibility to connect the node to a reserving join_node,
//! avoiding direct calls to the try_get() method from the body of the successor node
//! \brief \ref requirement
TEST_CASE("overwrite_node with reserving join_node as successor"){
conformance::test_with_reserving_join_node_class<oneapi::tbb::flow::overwrite_node<int>>();
}
@@ -18,128 +18,57 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#define CONFORMANCE_BUFFERING_NODES
#define CONFORMANCE_QUEUE_NODE
#include "conformance_flowgraph.h"
//! \file conformance_priority_queue_node.cpp
//! \brief Test for [flow_graph.priority_queue_node] specification
/*
TODO: implement missing conformance tests for priority_queue_node:
- [ ] Explicit test that `size_type' is defined and accessible.
- [ ] The copy constructor and copy assignment are called for the node's type template parameter.
- [ ] Check `Compare' type requirements from [alg.sorting] ISO C++.
- [ ] Write tests for the constructors.
- [ ] Based on the reconsideration of the `try_put()' and `try_get()' methods, rethink the testing of these methods. - [ ] Improve `test_buffering' by checking that additional `try_get()' does not receive the same
value.
*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, priority_queue_node<T>>::value), "priority_queue_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, priority_queue_node<T>>::value), "priority_queue_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, priority_queue_node<T>>::value), "priority_queue_node should be derived from sender<T>");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
priority_queue_node<int> n(g);
priority_queue_node<int> n2(n);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::priority_queue_node<int> node(g);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == true), "try_get after rejection should succeed");
CHECK_MESSAGE( (tmp == 1), "try_get after rejection should set value");
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::priority_queue_node<int> node1(g);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
int c2 = get_count(node2), c3 = get_count(node3);
CHECK_MESSAGE( (c2 != c3 ), "Only one descendant the node needs to receive");
CHECK_MESSAGE( (c2 + c3 == 1 ), "All messages need to be received");
}
void test_behavior(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::priority_queue_node<int, std::greater<int>> node(g);
node.try_put(2);
node.try_put(3);
node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp)), "Get should succeed");
CHECK_MESSAGE( (tmp == 1), "Values should get sorted");
CHECK_MESSAGE( (node.try_get(tmp)), "Get should succeed");
CHECK_MESSAGE( (tmp == 2), "Values should get sorted");
CHECK_MESSAGE( (node.try_get(tmp)), "Get should succeed");
CHECK_MESSAGE( (tmp == 3), "Values should get sorted");
}
//! Test priority_queue_node messages
//! Test priority_queue_node single_push
//! \brief \ref requirement
TEST_CASE("priority_queue_node messages"){
test_behavior();
TEST_CASE("priority_queue_node single_push"){
conformance::test_forwarding_single_push<oneapi::tbb::flow::priority_queue_node<int>>();
}
//! Test priority_queue_node single-push
//! \brief \ref requirement
TEST_CASE("priority_queue_node single-push"){
test_forwarding();
}
//! Test priority_queue_node buffering
//! Test function_node buffering
//! \brief \ref requirement
TEST_CASE("priority_queue_node buffering"){
test_buffering();
conformance::test_buffering<oneapi::tbb::flow::priority_queue_node<int>, int>();
}
//! Test copy constructor
//! \brief \ref interface
//! Constructs an empty priority_queue_node that belongs to the same graph g as src.
//! Any intermediate state of src, including its links to predecessors and successors, is not copied.
//! \brief \ref requirement
TEST_CASE("priority_queue_node copy constructor"){
test_copies();
conformance::test_copy_ctor_for_buffering_nodes<oneapi::tbb::flow::priority_queue_node<int>>();
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("priority_queue_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
conformance::test_inheritance<oneapi::tbb::flow::priority_queue_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::priority_queue_node<void*>, void*, void*>();
}
//! Test priority_queue_node node `try_put()` and `try_get()`
//! \brief \ref requirement
TEST_CASE("priority_queue_node methods"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::priority_queue_node<int, std::greater<int>> testing_node(g);
testing_node.try_put(2);
testing_node.try_put(3);
testing_node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE((testing_node.try_get(tmp)), "Get should succeed");
CHECK_MESSAGE((tmp == 1), "Values should get sorted");
CHECK_MESSAGE((testing_node.try_get(tmp)), "Get should succeed");
CHECK_MESSAGE((tmp == 2), "Values should get sorted");
CHECK_MESSAGE((testing_node.try_get(tmp)), "Get should succeed");
CHECK_MESSAGE((tmp == 3), "Values should get sorted");
}
+33 -127
View File
@@ -18,152 +18,58 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#define CONFORMANCE_BUFFERING_NODES
#define CONFORMANCE_QUEUE_NODE
#include "conformance_flowgraph.h"
//! \file conformance_queue_node.cpp
//! \brief Test for [flow_graph.queue_node] specification
/*
TODO: implement missing conformance tests for queue_node:
- [ ] The copy constructor and copy assignment are called for the node's type template parameter.
- [ ] Improve `test_forwarding()'.
- [ ] Improve tests of the constructors.
- [ ] Improve `test_buffering' by checking that additional `try_get()' does not receive the same
value.
- [ ] Based on the decision about the details for `try_put()' and `try_get()' write corresponding
tests.
*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, queue_node<T>>::value), "queue_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, queue_node<T>>::value), "queue_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, queue_node<T>>::value), "queue_node should be derived from sender<T>");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
queue_node<int> n(g);
queue_node<int> n2(n);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::queue_node<int> node(g);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == true), "try_get after rejection should succeed");
CHECK_MESSAGE( (tmp == 1), "try_get after rejection should set value");
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::queue_node<int> node1(g);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
int c2 = get_count(node2), c3 = get_count(node3);
CHECK_MESSAGE( (c2 != c3 ), "Only one descendant the node needs to receive");
CHECK_MESSAGE( (c2 + c3 == 1 ), "All messages need to be received");
}
void test_queue_node(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::queue_node<int> node(g);
node.try_put(1);
node.try_put(2);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == true), "try_get should succeed");
CHECK_MESSAGE( (tmp == 1), "try_get should set correct value");
tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == true), "try_get should succeed");
CHECK_MESSAGE( (tmp == 2), "try_get should set correct value");
}
void test_double_reserve(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::queue_node<int> node(g);
int tmp = -1;
node.try_reserve(tmp);
CHECK_MESSAGE((tmp == -1), "Should not be delivered");
node.try_reserve(tmp);
CHECK_MESSAGE((tmp == -1), "Should not be delivered");
g.reset();
node.try_reserve(tmp);
CHECK_MESSAGE((tmp == -1), "Should not be delivered");
node.try_reserve(tmp);
CHECK_MESSAGE((tmp == -1), "Should not be delivered");
}
//! Test multiple reserves
//! \brief \ref error_guessing
TEST_CASE("queue_node double reserve"){
test_double_reserve();
}
//! Test message logic
//! Test queue_node single_push
//! \brief \ref requirement
TEST_CASE("queue_node messages"){
test_queue_node();
TEST_CASE("queue_node single_push"){
conformance::test_forwarding_single_push<oneapi::tbb::flow::queue_node<int>>();
}
//! Test single-push
//! Test function_node buffering
//! \brief \ref requirement
TEST_CASE("queue_node buffering"){
test_forwarding();
conformance::test_buffering<oneapi::tbb::flow::queue_node<int>, int>();
}
//! Test buffering
//! Constructs an empty queue_node that belongs to the same graph g as src.
//! Any intermediate state of src, including its links to predecessors and successors, is not copied.
//! \brief \ref requirement
TEST_CASE("queue_node buffering"){
test_buffering();
}
//! Test copy constructor
//! \brief \ref interface
TEST_CASE("queue_node copy constructor"){
test_copies();
conformance::test_copy_ctor_for_buffering_nodes<oneapi::tbb::flow::queue_node<int>>();
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("queue_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
conformance::test_inheritance<oneapi::tbb::flow::queue_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::queue_node<void*>, void*, void*>();
}
//! Test queue_node node `try_put()` and `try_get()`
//! \brief \ref requirement
TEST_CASE("queue_node methods"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::queue_node<int> testing_node(g);
int tmp1 = -1;
int tmp2 = -1;
CHECK_MESSAGE((!testing_node.try_get(tmp1) && tmp1 == -1), "`try_get` must returns false if there is no non-reserved item currently in the node.");
testing_node.try_put(1);
testing_node.try_put(2);
g.wait_for_all();
testing_node.try_get(tmp1);
CHECK_MESSAGE((tmp1 == 1), "Messages must be an FIFO order");
testing_node.try_get(tmp2);
CHECK_MESSAGE((tmp2 == 2), "Additional `try_get()' does not receive the same value as previous");
}
+102 -120
View File
@@ -18,107 +18,98 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#define SEQUENCER_NODE
#include "conformance_flowgraph.h"
//! \file conformance_sequencer_node.cpp
//! \brief Test for [flow_graph.sequencer_node] specification
/*
TODO: implement missing conformance tests for sequencer_node:
- [ ] The copy constructor and copy assignment are called for the node's type template parameter.
- [ ] Explicit test that `Sequencer' requirements are necessary.
- [ ] Write tests for the constructors.
- [ ] Add CTAD test.
- [ ] Improve `test_buffering' by checking that additional `try_get()' does not receive the same
value.
- [ ] Add explicit test on the example from the specification.
*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, sequencer_node<T>>::value), "sequencer_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, sequencer_node<T>>::value), "sequencer_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, sequencer_node<T>>::value), "sequencer_node should be derived from sender<T>");
}
template<typename T>
struct id_sequencer{
using input_type = T;
std::size_t operator()(T v) {
return v;
}
};
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
id_sequencer<int> sequencer;
sequencer_node<int> n(g, sequencer);
sequencer_node<int> n2(n);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
id_sequencer<int> sequencer;
oneapi::tbb::flow::sequencer_node<int> node(g, sequencer);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == false), "try_get after rejection should not succeed");
CHECK_MESSAGE( (tmp == -1), "try_get after rejection should not set value");
}
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
void test_deduction_guides(){
// oneapi::tbb::flow::graph g;
// id_sequencer<int> sequ;
// oneapi::tbb::flow::sequencer_node node1(g, sequ);
template <typename Body>
void test_deduction_guides_common(Body body) {
using namespace tbb::flow;
graph g;
broadcast_node<int> br(g);
sequencer_node s1(g, body);
static_assert(std::is_same_v<decltype(s1), sequencer_node<int>>);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
sequencer_node s2(follows(br), body);
static_assert(std::is_same_v<decltype(s2), sequencer_node<int>>);
#endif
sequencer_node s3(s1);
static_assert(std::is_same_v<decltype(s3), sequencer_node<int>>);
}
std::size_t sequencer_body_f(const int&) { return 1; }
void test_deduction_guides() {
test_deduction_guides_common([](const int&)->std::size_t { return 1; });
test_deduction_guides_common([](const int&) mutable ->std::size_t { return 1; });
test_deduction_guides_common(sequencer_body_f);
}
#endif
void test_forwarding(){
oneapi::tbb::flow::graph g;
id_sequencer<int> sequencer;
oneapi::tbb::flow::sequencer_node<int> node1(g, sequencer);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(0);
g.wait_for_all();
int c2 = get_count(node2), c3 = get_count(node3);
CHECK_MESSAGE( (c2 != c3 ), "Only one descendant the node needs to receive");
CHECK_MESSAGE( (c2 + c3 == 1 ), "Messages need to be received");
//! Test deduction guides
//! \brief \ref interface \ref requirement
TEST_CASE("Deduction guides"){
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
test_deduction_guides();
#endif
}
void test_sequencer(){
//! Test sequencer_node single_push
//! \brief \ref requirement
TEST_CASE("sequencer_node single_push"){
conformance::sequencer_functor<int> sequencer;
conformance::test_forwarding_single_push<oneapi::tbb::flow::sequencer_node<int>>(sequencer);
}
//! Test function_node buffering
//! \brief \ref requirement
TEST_CASE("sequencer_node buffering"){
conformance::sequencer_functor<int> sequencer;
conformance::test_buffering<oneapi::tbb::flow::sequencer_node<int>, int>(sequencer);
}
//! Constructs an empty sequencer_node that belongs to the same graph g as src.
//! Any intermediate state of src, including its links to predecessors and successors, is not copied.
//! \brief \ref requirement
TEST_CASE("sequencer_node copy constructor"){
conformance::sequencer_functor<int> sequencer;
conformance::test_copy_ctor_for_buffering_nodes<oneapi::tbb::flow::sequencer_node<int>>(sequencer);
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("sequencer_node superclasses"){
conformance::test_inheritance<oneapi::tbb::flow::sequencer_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::sequencer_node<void*>, void*, void*>();
}
//! Test the sequencer_node rejects duplicate sequencer numbers
//! \brief \ref interface
TEST_CASE("sequencer_node rejects duplicate"){
oneapi::tbb::flow::graph g;
id_sequencer<int> sequencer;
conformance::sequencer_functor<int> sequencer;
oneapi::tbb::flow::sequencer_node<int> node(g, sequencer);
node.try_put(1);
CHECK_MESSAGE((node.try_put(1) == false), "sequencer_node must rejects duplicate sequencer numbers");
g.wait_for_all();
}
//! Test queue_node node `try_put()` and `try_get()`
//! \brief \ref requirement
TEST_CASE("queue_node methods"){
oneapi::tbb::flow::graph g;
conformance::sequencer_functor<int> sequencer;
oneapi::tbb::flow::sequencer_node<int> node(g, sequencer);
@@ -137,45 +128,36 @@ void test_sequencer(){
tmp = -1;
CHECK_MESSAGE((node.try_get(tmp) == false), "Getting from sequencer should not succeed");
}
//! Test function_node buffering
//! The example demonstrates ordering capabilities of the sequencer_node.
//! While being processed in parallel, the data is passed to the successor node in the exact same order it was read.
//! \brief \ref requirement
TEST_CASE("sequencer_node buffering"){
test_sequencer();
}
TEST_CASE("sequencer_node ordering"){
using namespace oneapi::tbb::flow;
using message = conformance::sequencer_functor<int>::seq_message;
graph g;
//! Test function_node buffering
//! \brief \ref requirement
TEST_CASE("sequencer_node buffering"){
test_forwarding();
}
// Due to parallelism the node can push messages to its successors in any order
function_node<message, message> process(g, unlimited, [] (message msg) {
msg.data++;
return msg;
});
//! Test deduction guides
//! \brief \ref interface \ref requirement
TEST_CASE("Deduction guides"){
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
test_deduction_guides();
#endif
}
sequencer_node<message> ordering(g, conformance::sequencer_functor<int>());
//! Test priority_queue_node buffering
//! \brief \ref requirement
TEST_CASE("sequencer_node buffering"){
test_buffering();
}
std::atomic<std::size_t> counter{0};
function_node<message> writer(g, tbb::flow::serial, [&] (const message& msg) {
CHECK_MESSAGE((msg.id == counter++), "The data is passed to the successor node in the exact same order it was read");
});
//! Test copy constructor
//! \brief \ref interface
TEST_CASE("sequencer_node copy constructor"){
test_copies();
}
tbb::flow::make_edge(process, ordering);
tbb::flow::make_edge(ordering, writer);
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("sequencer_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
}
for (std::size_t i = 0; i < 100; ++i) {
message msg = {i, 0};
process.try_put(msg);
}
g.wait_for_all();
}
+85 -104
View File
@@ -18,138 +18,119 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#include "conformance_flowgraph.h"
//! \file conformance_split_node.cpp
//! \brief Test for [flow_graph.split_node] specification
/*
TODO: implement missing conformance tests for split_node:
- [ ] Check that copy constructor and copy assignment is called for each type the tuple stores.
- [ ] Rewrite `test_forwarding' to check broadcast semantics of the node.
- [ ] Improve test for constructors.
- [ ] Unify code style in the test by extracting the implementation from the `TEST_CASE' scope
into separate functions.
- [ ] Rename discarding test to `test_buffering' and add checking that the value does not change
in the `try_get()' method of the output ports of the node.
- [ ] Add checking of the unlimited concurrency.
- [ ] Check that `try_put()' always returns `true'.
- [ ] Explicitly check that `output_ports_type' is defined, accessible.
- [ ] Explicitly check the method `indexer_node::output_ports()' exists, is accessible and it
returns a reference to the `output_ports_type' type.
*/
using input_msg = conformance::message</*default_ctor*/true, /*copy_ctor*/true, /*copy_assign*/true/*enable for queue_node successor*/>;
using my_input_tuple = std::tuple<int, float, input_msg>;
using my_split_type = oneapi::tbb::flow::split_node<my_input_tuple>;
using namespace oneapi::tbb::flow;
using namespace std;
template<typename T>
void test_inheritance(){
CHECK_MESSAGE( (std::is_base_of<graph_node, split_node<std::tuple<T,T>>>::value), "split_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<std::tuple<T,T>>, split_node<std::tuple<T,T>>>::value), "split_node should be derived from receiver<T>");
}
void test_split(){
graph g;
queue_node<int> first_queue(g);
queue_node<int> second_queue(g);
split_node< std::tuple<int,int> > my_split_node(g);
make_edge(output_port<0>(my_split_node), first_queue);
make_edge(output_port<1>(my_split_node), second_queue);
tuple<int, int> my_tuple(0, 1);
my_split_node.try_put(my_tuple);
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE((first_queue.try_get(tmp) == true), "Getting from target queue should succeed");
CHECK_MESSAGE((tmp == 0), "Received value should be correct");
tmp = -1;
CHECK_MESSAGE((second_queue.try_get(tmp) == true), "Getting from target queue should succeed");
CHECK_MESSAGE((tmp == 1), "Received value should be correct");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
split_node<std::tuple<int, int>> n(g);
split_node<std::tuple<int, int>> n2(n);
}
void test_forwarding(){
//! Test node not buffered unsuccessful message, and try_get after rejection should not succeed.
//! \brief \ref requirement
TEST_CASE("split_node buffering") {
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::split_node<std::tuple<int, int>> node1(g);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
my_split_type testing_node(g);
oneapi::tbb::flow::make_edge(output_port<0>(node1), node2);
oneapi::tbb::flow::make_edge(output_port<1>(node1), node3);
oneapi::tbb::flow::limiter_node<int> rejecter1(g,0);
oneapi::tbb::flow::limiter_node<float> rejecter2(g,0);
oneapi::tbb::flow::limiter_node<input_msg> rejecter3(g,0);
tuple<int, int> my_tuple(0, 1);
node1.try_put(my_tuple);
oneapi::tbb::flow::make_edge(oneapi::tbb::flow::output_port<0>(testing_node), rejecter1);
oneapi::tbb::flow::make_edge(oneapi::tbb::flow::output_port<1>(testing_node), rejecter2);
oneapi::tbb::flow::make_edge(oneapi::tbb::flow::output_port<2>(testing_node), rejecter3);
my_input_tuple my_tuple(1, 1.5f, input_msg(2));
testing_node.try_put(my_tuple);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
int tmp1 = -1;
float tmp2 = -1;
input_msg tmp3(-1);
CHECK_MESSAGE((oneapi::tbb::flow::output_port<0>(testing_node).try_get(tmp1) == false
&& tmp1 == -1), "Value should be discarded after rejection");
CHECK_MESSAGE((oneapi::tbb::flow::output_port<1>(testing_node).try_get(tmp2) == false
&& tmp2 == -1.f), "Value should be discarded after rejection");
CHECK_MESSAGE((oneapi::tbb::flow::output_port<2>(testing_node).try_get(tmp3) == false
&& tmp3 == -1), "Value should be discarded after rejection");
}
//! Test broadcast
//! \brief \ref interface
TEST_CASE("split_node broadcast") {
test_forwarding();
}
//! Test discarding property
//! Test node broadcast messages to successors and splitting them in correct order
//! \brief \ref requirement
TEST_CASE("split_node discarding") {
graph g;
TEST_CASE("split_node broadcast and splitting"){
using namespace oneapi::tbb::flow;
oneapi::tbb::flow::graph g;
split_node< std::tuple<int,int> > my_split_node(g);
my_split_type testing_node(g);
conformance::test_push_receiver<int> node2(g);
conformance::test_push_receiver<float> node3(g);
conformance::test_push_receiver<input_msg> node4(g);
limiter_node< int > rejecter1( g,0);
limiter_node< int > rejecter2( g,0);
oneapi::tbb::flow::make_edge(output_port<0>(testing_node), node2);
oneapi::tbb::flow::make_edge(output_port<1>(testing_node), node3);
oneapi::tbb::flow::make_edge(output_port<2>(testing_node), node4);
make_edge(output_port<0>(my_split_node), rejecter2);
make_edge(output_port<1>(my_split_node), rejecter1);
my_input_tuple my_tuple(1, 1.5f, input_msg(2));
tuple<int, int> my_tuple(0, 1);
my_split_node.try_put(my_tuple);
CHECK_MESSAGE((testing_node.try_put(my_tuple)), "`try_put()' must always returns `true'");
g.wait_for_all();
auto values1 = conformance::get_values(node2);
auto values2 = conformance::get_values(node3);
auto values3 = conformance::get_values(node4);
CHECK_MESSAGE((values1.size() == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE((values2.size() == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE((values3.size() == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE((values1[0] == 1), "Descendant of the node needs to be receive N messages");
CHECK_MESSAGE((values2[0] == 1.5f), "Descendant of the node must receive one message.");
CHECK_MESSAGE((values3[0] == 2), "Descendant of the node must receive one message.");
}
//! The node that is constructed has a reference to the same graph object as src.
//! The predecessors and successors of src are not copied.
//! \brief \ref interface
TEST_CASE("split_node copy constructor"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::continue_node<std::tuple<int>> node0( g,
[](oneapi::tbb::flow::continue_msg) { return std::tuple<int>(1); } );
oneapi::tbb::flow::split_node<std::tuple<int>> node1(g);
conformance::test_push_receiver<int> node2(g);
conformance::test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node0, node1);
oneapi::tbb::flow::make_edge(oneapi::tbb::flow::output_port<0>(node1), node2);
oneapi::tbb::flow::split_node<std::tuple<int>> node_copy(node1);
oneapi::tbb::flow::make_edge(oneapi::tbb::flow::output_port<0>(node_copy), node3);
node_copy.try_put(std::tuple<int>(1));
g.wait_for_all();
int tmp = -1;
CHECK_MESSAGE((output_port<0>(my_split_node).try_get(tmp) == false), "Value should be discarded after rejection");
CHECK_MESSAGE((output_port<1>(my_split_node).try_get(tmp) == false), "Value should be discarded after rejection");
}
CHECK_MESSAGE((conformance::get_values(node2).size() == 0 && conformance::get_values(node3).size() == 1), "Copied node doesn`t copy successor");
//! Test copy constructor
//! \brief \ref interface
TEST_CASE("split_node copy constructor") {
test_copies();
}
node0.try_put(oneapi::tbb::flow::continue_msg());
g.wait_for_all();
//! Test copy constructor
//! \brief \ref interface \ref requirement
TEST_CASE("split_node messages") {
test_split();
CHECK_MESSAGE((conformance::get_values(node2).size() == 1 && conformance::get_values(node3).size() == 0), "Copied node doesn`t copy predecessor");
}
//! Test copy constructor
//! \brief \ref interface
TEST_CASE("split_node superclasses") {
test_inheritance<int>();
CHECK_MESSAGE((std::is_base_of<oneapi::tbb::flow::graph_node, my_split_type>::value), "split_node should be derived from graph_node");
CHECK_MESSAGE((std::is_base_of<oneapi::tbb::flow::receiver<my_input_tuple>, my_split_type>::value), "split_node should be derived from receiver<T>");
}
//! Test split_node output_ports() returns a tuple of output ports.
//! \brief \ref interface \ref requirement
TEST_CASE("split_node output_ports") {
oneapi::tbb::flow::graph g;
my_split_type node(g);
CHECK_MESSAGE((std::is_same<my_split_type::output_ports_type&,
decltype(node.output_ports())>::value), "split_node output_ports should returns a tuple of output ports");
}
@@ -25,6 +25,13 @@
//! \file conformance_task_arena.cpp
//! \brief Test for [scheduler.task_arena scheduler.task_scheduler_observer] specification
// This test requires TBB in an uninitialized state
//! Test for uninitilized arena
//! \brief \ref requirement \ref interface
TEST_CASE("Test current_thread_index") {
REQUIRE_MESSAGE((tbb::this_task_arena::current_thread_index() == tbb::task_arena::not_initialized), "TBB was initialized state");
}
//! Test task arena interfaces
//! \brief \ref requirement \ref interface
TEST_CASE("Arena interfaces") {
+45 -103
View File
@@ -18,129 +18,71 @@
#pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
#endif
#include "common/test.h"
#include "common/utils.h"
#include "common/graph_utils.h"
#include "oneapi/tbb/flow_graph.h"
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/global_control.h"
#define CONFORMANCE_BUFFERING_NODES
#define CONFORMANCE_OVERWRITE_NODE
#include "conformance_flowgraph.h"
//! \file conformance_write_once_node.cpp
//! \brief Test for [flow_graph.write_once_node] specification
/*
TODO: implement missing conformance tests for write_once_node:
- [ ] The copy constructor and copy assignment are called for the node's type template parameter.
- [ ] Improve copy constructor and general constructor tests.
- [ ] Write test checking the value is initially invalid.
- [ ] Write test checking that the gets from the node are non-destructive, but the first `try_get'
fails.
- [ ] Add test on `write_once_node::is_valid()' method.
- [ ] Add test on `write_once_node::clear()' method.
- [ ] Add test with reserving `join_node' as node's successor. Use example from the spec.
*/
template<typename T>
void test_inheritance(){
using namespace oneapi::tbb::flow;
CHECK_MESSAGE( (std::is_base_of<graph_node, write_once_node<T>>::value), "write_once_node should be derived from graph_node");
CHECK_MESSAGE( (std::is_base_of<receiver<T>, write_once_node<T>>::value), "write_once_node should be derived from receiver<T>");
CHECK_MESSAGE( (std::is_base_of<sender<T>, write_once_node<T>>::value), "write_once_node should be derived from sender<T>");
}
void test_copies(){
using namespace oneapi::tbb::flow;
graph g;
write_once_node<int> fn(g);
write_once_node<int> fn2(fn);
}
void test_buffering(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::write_once_node<int> node(g);
oneapi::tbb::flow::limiter_node<int> rejecter(g, 0);
oneapi::tbb::flow::make_edge(node, rejecter);
node.try_put(1);
int tmp = -1;
CHECK_MESSAGE( (node.try_get(tmp) == true), "try_get after rejection should succeed");
CHECK_MESSAGE( (tmp == 1), "try_get after rejection should set value");
g.wait_for_all();
}
void test_forwarding(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::write_once_node<int> node1(g);
test_push_receiver<int> node2(g);
test_push_receiver<int> node3(g);
oneapi::tbb::flow::make_edge(node1, node2);
oneapi::tbb::flow::make_edge(node1, node3);
node1.try_put(1);
g.wait_for_all();
CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node must receive one message.");
CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
}
void test_writing_once(){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::write_once_node<int> node1(g);
int tmp = -1;
node1.try_put(1);
CHECK_MESSAGE( (node1.try_get(tmp) == true), "Descendant needs to receive");
CHECK_MESSAGE( (tmp == 1), "Descendant needs be receive correct message");
CHECK_MESSAGE( (node1.try_put(2) == false), "Putting again should not succeed");
CHECK_MESSAGE( (node1.try_get(tmp) == true), "try_get should still succeed");
CHECK_MESSAGE( (tmp == 1), "try_get should receive initial value");
g.wait_for_all();
}
//! Test overwrite_node behavior
//! \brief \ref requirement
TEST_CASE("write_once_node messages"){
test_writing_once();
}
//! Test function_node broadcast
//! \brief \ref requirement
TEST_CASE("overwrite_node broadcast"){
test_forwarding();
TEST_CASE("write_once_node broadcast"){
conformance::test_forwarding<oneapi::tbb::flow::write_once_node<int>, int>(1);
}
//! Test write_once_node buffering
//! \brief \ref requirement
TEST_CASE("write_once_node buffering"){
test_buffering();
conformance::test_buffering<oneapi::tbb::flow::write_once_node<int>, int>();
}
//! Test copy constructor
//! \brief \ref interface
//! The node that is constructed has a reference to the same graph object as src,with an invalid internal buffer item.
//! The buffered value and list of successors are not copied from src.
//! \brief \ref requirement
TEST_CASE("write_once_node copy constructor"){
test_copies();
conformance::test_copy_ctor_for_buffering_nodes<oneapi::tbb::flow::write_once_node<int>>();
}
//! Test inheritance relations
//! \brief \ref interface
TEST_CASE("write_once_node superclasses"){
test_inheritance<int>();
test_inheritance<void*>();
conformance::test_inheritance<oneapi::tbb::flow::write_once_node<int>, int, int>();
conformance::test_inheritance<oneapi::tbb::flow::write_once_node<void*>, void*, void*>();
}
//! Test overwrite_node node constructor
//! \brief \ref requirement
TEST_CASE("write_once_node constructor"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::overwrite_node<int> testing_node(g);
int tmp = -1;
CHECK_MESSAGE((!testing_node.is_valid()), "Constructed node has invalid internal buffer item");
CHECK_MESSAGE((!testing_node.try_get(tmp)), "Gets from the node are non-destructive, but the first `try_get' fails");
}
//! Test write_once_node node `is_valid()` and `clear()`
//! \brief \ref requirement
TEST_CASE("write_once_node methods"){
oneapi::tbb::flow::graph g;
oneapi::tbb::flow::write_once_node<int> testing_node(g);
CHECK_MESSAGE((!testing_node.is_valid()), "Constructed node has invalid internal buffer item");
testing_node.try_put(1);
CHECK_MESSAGE((testing_node.is_valid()), "Buffer must be valid after try_put call");
testing_node.clear();
CHECK_MESSAGE((!testing_node.is_valid()), "call `clear` invalidates the value held in the buffer.");
}
//! The following test shows the possibility to connect the node to a reserving join_node,
//! avoiding direct calls to the try_get() method from the body of the successor node
//! \brief \ref requirement
TEST_CASE("write_once_node with reserving join_node as successor"){
conformance::test_with_reserving_join_node_class<oneapi::tbb::flow::write_once_node<int>>();
}
+7
View File
@@ -212,3 +212,10 @@ TEST_CASE("Test concurrency getters output for constraints with custom concurren
c.set_max_threads_per_core(1);
check_concurrency_level(c);
}
//! Testing constraints_threads_per_core() reserved entry point
//! \brief \ref error_guessing
TEST_CASE("Testing constraints_threads_per_core() reserved entry point") {
tbb::task_arena::constraints c{};
tbb::detail::r1::constraints_threads_per_core(c);
}
+6 -4
View File
@@ -22,6 +22,7 @@
#include "common/spin_barrier.h"
#include "common/utils.h"
#include "common/utils_concurrency_limit.h"
#include <cstddef>
#include <algorithm>
@@ -177,13 +178,14 @@ void wait_work_completion(
void test() {
const std::size_t max_num_threads = tbb::global_control::active_value(
tbb::global_control::max_allowed_parallelism
);
const std::size_t max_num_threads = utils::get_platform_max_threads();
tbb::global_control control(tbb::global_control::max_allowed_parallelism, max_num_threads + 1);
concurrency_type signed_max_num_threads = static_cast<int>(max_num_threads);
if( 1 == max_num_threads )
if (1 == max_num_threads) {
// Skipping workerless case
return;
}
INFO( "max_num_threads = " << max_num_threads );
// TODO: iterate over threads to see that the work is going on in low priority arena.
+2
View File
@@ -17,6 +17,7 @@
#include "common/config.h"
#include "tbb/flow_graph.h"
#include "tbb/global_control.h"
#include "common/test.h"
#include "common/utils.h"
@@ -460,6 +461,7 @@ void test_deduction_guides() {
//! \brief \ref requirement \ref error_guessing
TEST_CASE("Serial and parallel test"){
for (int p = 2; p <= 4; ++p) {
tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, p);
tbb::task_arena arena(p);
arena.execute(
[&]() {
+2 -90
View File
@@ -89,88 +89,6 @@ struct compare<1, T1, T2> {
}
};
void add_all_nodes (){
tbb::flow::graph g;
typedef std::tuple<tbb::flow::continue_msg, std::tuple<int, int>, int, int, int, int,
int, int, int, int, int, int, int, int > InputTupleType;
typedef std::tuple<tbb::flow::continue_msg, std::tuple<int, int>, tbb::flow::tagged_msg<size_t, int, float>,
int, int, int, int, int, int, int, int, int, int, int, int > OutputTupleType;
typedef std::tuple< > EmptyTupleType;
typedef tbb::flow::composite_node<InputTupleType, OutputTupleType > input_output_type;
typedef tbb::flow::composite_node<InputTupleType, EmptyTupleType > input_only_type;
typedef tbb::flow::composite_node<EmptyTupleType, OutputTupleType > output_only_type;
const size_t NUM_INPUTS = std::tuple_size<InputTupleType>::value;
const size_t NUM_OUTPUTS = std::tuple_size<OutputTupleType>::value;
//node types
tbb::flow::continue_node<tbb::flow::continue_msg> ct(g, ct_body());
tbb::flow::split_node< std::tuple<int, int> > s(g);
tbb::flow::input_node<int> src(g, my_input_body(20,5));
tbb::flow::function_node<int, int> fxn(g, tbb::flow::unlimited, passthru_body());
tbb::flow::multifunction_node<int, std::tuple<int, int> > m_fxn(g, tbb::flow::unlimited, m_fxn_body());
tbb::flow::broadcast_node<int> bc(g);
tbb::flow::limiter_node<int> lim(g, 2);
tbb::flow::indexer_node<int, float> ind(g);
tbb::flow::join_node< std::tuple< int, int >, tbb::flow::queueing > j(g);
tbb::flow::queue_node<int> q(g);
tbb::flow::buffer_node<int> bf(g);
tbb::flow::priority_queue_node<int> pq(g);
tbb::flow::write_once_node<int> wo(g);
tbb::flow::overwrite_node<int> ovw(g);
tbb::flow::sequencer_node<int> seq(g, seq_body());
auto input_tuple = std::tie(ct, s, m_fxn, fxn, bc, tbb::flow::input_port<0>(j), lim, q, tbb::flow::input_port<0>(ind),
pq, ovw, wo, bf, seq);
auto output_tuple = std::tie(ct,j, ind, fxn, src, bc, tbb::flow::output_port<0>(s), lim, tbb::flow::output_port<0>(m_fxn),
q, pq, ovw, wo, bf, seq );
//composite_node with both input_ports and output_ports
input_output_type a_node(g);
a_node.set_external_ports(input_tuple, output_tuple);
a_node.add_visible_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
a_node.add_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
auto a_node_input_ports_ptr = a_node.input_ports();
compare<NUM_INPUTS-1, decltype(a_node_input_ports_ptr), decltype(input_tuple)>::compare_refs(a_node_input_ports_ptr, input_tuple);
CHECK_MESSAGE(NUM_INPUTS == std::tuple_size<decltype(a_node_input_ports_ptr)>::value, "not all declared input ports were bound to nodes");
auto a_node_output_ports_ptr = a_node.output_ports();
compare<NUM_OUTPUTS-1, decltype(a_node_output_ports_ptr), decltype(output_tuple)>::compare_refs(a_node_output_ports_ptr, output_tuple);
CHECK_MESSAGE( (NUM_OUTPUTS == std::tuple_size<decltype(a_node_output_ports_ptr)>::value), "not all declared output ports were bound to nodes");
//composite_node with only input_ports
input_only_type b_node(g);
b_node.set_external_ports(input_tuple);
b_node.add_visible_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
b_node.add_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
auto b_node_input_ports_ptr = b_node.input_ports();
compare<NUM_INPUTS-1, decltype(b_node_input_ports_ptr), decltype(input_tuple)>::compare_refs(b_node_input_ports_ptr, input_tuple);
CHECK_MESSAGE(NUM_INPUTS == std::tuple_size<decltype(b_node_input_ports_ptr)>::value, "not all declared input ports were bound to nodes");
//composite_node with only output_ports
output_only_type c_node(g);
c_node.set_external_ports(output_tuple);
// Reset is not suppose to do anything. Check that it can be called.
g.reset();
c_node.add_visible_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
c_node.add_nodes(src, fxn, m_fxn, bc, lim, ind, s, ct, j, q, bf, pq, wo, ovw, seq);
auto c_node_output_ports_ptr = c_node.output_ports();
compare<NUM_OUTPUTS-1, decltype(c_node_output_ports_ptr), decltype(output_tuple)>::compare_refs(c_node_output_ports_ptr, output_tuple);
CHECK_MESSAGE(NUM_OUTPUTS == std::tuple_size<decltype(c_node_output_ports_ptr)>::value, "not all declared input ports were bound to nodes");
}
struct tiny_node : public tbb::flow::composite_node< std::tuple< int >, std::tuple< int > > {
tbb::flow::function_node< int, int > f1;
tbb::flow::function_node< int, int > f2;
@@ -178,11 +96,11 @@ struct tiny_node : public tbb::flow::composite_node< std::tuple< int >, std::tup
public:
tiny_node(tbb::flow::graph &g, bool hidden = false) : base_type(g), f1(g, tbb::flow::unlimited, passthru_body() ), f2(g, tbb::flow::unlimited, passthru_body() ) {
tbb::flow::make_edge( f1, f2 );
tbb::flow::make_edge(f1, f2);
std::tuple<tbb::flow::function_node< int, int >& > input_tuple(f1);
std::tuple<tbb::flow::function_node< int, int >& > output_tuple(f2);
base_type::set_external_ports( input_tuple, output_tuple );
base_type::set_external_ports(input_tuple, output_tuple);
if(hidden)
base_type::add_nodes(f1, f2);
@@ -568,12 +486,6 @@ void input_only_output_only_composite(bool hidden) {
g.wait_for_all();
}
//! Test all node types inside composite node
//! \brief \ref error_guessing
TEST_CASE("Add all nodes"){
add_all_nodes();
}
//! Test single node inside composite nodes
//! \brief \ref error_guessing
TEST_CASE("Tiny tests"){
-86
View File
@@ -310,86 +310,6 @@ void test_follows_and_precedes_api() {
}
#endif // __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
template <typename ExpectedType, typename Body>
void test_deduction_guides_common(Body body) {
using namespace tbb::flow;
graph g;
continue_node c1(g, body);
static_assert(std::is_same_v<decltype(c1), continue_node<ExpectedType>>);
continue_node c2(g, body, lightweight());
static_assert(std::is_same_v<decltype(c2), continue_node<ExpectedType, lightweight>>);
continue_node c3(g, 5, body);
static_assert(std::is_same_v<decltype(c3), continue_node<ExpectedType>>);
continue_node c4(g, 5, body, lightweight());
static_assert(std::is_same_v<decltype(c4), continue_node<ExpectedType, lightweight>>);
continue_node c5(g, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c5), continue_node<ExpectedType>>);
continue_node c6(g, body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c6), continue_node<ExpectedType, lightweight>>);
continue_node c7(g, 5, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c7), continue_node<ExpectedType>>);
continue_node c8(g, 5, body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c8), continue_node<ExpectedType, lightweight>>);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
broadcast_node<continue_msg> b(g);
continue_node c9(follows(b), body);
static_assert(std::is_same_v<decltype(c9), continue_node<ExpectedType>>);
continue_node c10(follows(b), body, lightweight());
static_assert(std::is_same_v<decltype(c10), continue_node<ExpectedType, lightweight>>);
continue_node c11(follows(b), 5, body);
static_assert(std::is_same_v<decltype(c11), continue_node<ExpectedType>>);
continue_node c12(follows(b), 5, body, lightweight());
static_assert(std::is_same_v<decltype(c12), continue_node<ExpectedType, lightweight>>);
continue_node c13(follows(b), body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c13), continue_node<ExpectedType>>);
continue_node c14(follows(b), body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c14), continue_node<ExpectedType, lightweight>>);
continue_node c15(follows(b), 5, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(c15), continue_node<ExpectedType>>);
continue_node c16(follows(b), 5, body, lightweight(), node_priority_t(5));
static_assert(std::is_same_v<decltype(c16), continue_node<ExpectedType, lightweight>>);
#endif // __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
continue_node c17(c1);
static_assert(std::is_same_v<decltype(c17), continue_node<ExpectedType>>);
}
int continue_body_f(const tbb::flow::continue_msg&) { return 1; }
void continue_void_body_f(const tbb::flow::continue_msg&) {}
void test_deduction_guides() {
using tbb::flow::continue_msg;
test_deduction_guides_common<int>([](const continue_msg&)->int { return 1; } );
test_deduction_guides_common<continue_msg>([](const continue_msg&) {});
test_deduction_guides_common<int>([](const continue_msg&) mutable ->int { return 1; });
test_deduction_guides_common<continue_msg>([](const continue_msg&) mutable {});
test_deduction_guides_common<int>(continue_body_f);
test_deduction_guides_common<continue_msg>(continue_void_body_f);
}
#endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
// TODO: use pass_through from test_function_node instead
template<typename T>
struct passing_body {
@@ -453,12 +373,6 @@ TEST_CASE( "Lightweight policy" ) { test_lightweight_policy(); }
TEST_CASE( "Support for follows and precedes API" ) { test_follows_and_precedes_api(); }
#endif
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
//! Test deduction guides
//! \brief requirement
TEST_CASE( "Deduction guides" ) { test_deduction_guides(); }
#endif
//! Test for successor cache specialization
//! \brief \ref regression
TEST_CASE( "Regression for successor cache specialization" ) {
+2
View File
@@ -23,6 +23,7 @@
#include "tbb/task_scheduler_observer.h"
#endif
#include "tbb/flow_graph.h"
#include "tbb/global_control.h"
#include "common/test.h"
@@ -2029,6 +2030,7 @@ void TestOneThreadNum(int nThread) {
TEST_CASE("Testing several threads"){
// reverse order of tests
for(unsigned int nThread=utils::MaxThread; nThread >= utils::MinThread; --nThread) {
tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, nThread);
TestOneThreadNum(nThread);
}
}
+3 -3
View File
@@ -97,7 +97,7 @@ TEST_CASE("Too many threads") {
std::thread /* isolate test */ ([] {
std::vector<Thread> threads;
stop = false;
auto finilize = [&] {
auto finalize = [&] {
stop = true;
cv.notify_all();
for (auto& t : threads) {
@@ -113,7 +113,7 @@ TEST_CASE("Too many threads") {
threads.push_back(thread);
if (i == 1024) {
WARN_MESSAGE(false, "setrlimit seems having no effect");
finilize();
finalize();
return;
}
}
@@ -132,7 +132,7 @@ TEST_CASE("Too many threads") {
if (!g_exception_caught) {
FAIL("No exception was caught");
}
finilize();
finalize();
}).join();
}
#endif
-54
View File
@@ -469,52 +469,6 @@ void test_follows_and_precedes_api() {
}
#endif
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
int function_body_f(const int&) { return 1; }
template <typename Body>
void test_deduction_guides_common(Body body) {
using namespace tbb::flow;
graph g;
function_node f1(g, unlimited, body);
static_assert(std::is_same_v<decltype(f1), function_node<int, int>>);
function_node f2(g, unlimited, body, rejecting());
static_assert(std::is_same_v<decltype(f2), function_node<int, int, rejecting>>);
function_node f3(g, unlimited, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(f3), function_node<int, int>>);
function_node f4(g, unlimited, body, rejecting(), node_priority_t(5));
static_assert(std::is_same_v<decltype(f4), function_node<int, int, rejecting>>);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
function_node f5(follows(f2), unlimited, body);
static_assert(std::is_same_v<decltype(f5), function_node<int, int>>);
function_node f6(follows(f5), unlimited, body, rejecting());
static_assert(std::is_same_v<decltype(f6), function_node<int, int, rejecting>>);
function_node f7(follows(f6), unlimited, body, node_priority_t(5));
static_assert(std::is_same_v<decltype(f7), function_node<int, int>>);
function_node f8(follows(f7), unlimited, body, rejecting(), node_priority_t(5));
static_assert(std::is_same_v<decltype(f8), function_node<int, int, rejecting>>);
#endif // __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
function_node f9(f1);
static_assert(std::is_same_v<decltype(f9), function_node<int, int>>);
}
void test_deduction_guides() {
test_deduction_guides_common([](const int&)->int { return 1; });
test_deduction_guides_common([](const int&) mutable ->int { return 1; });
test_deduction_guides_common(function_body_f);
}
#endif
//! Test various node bodies with concurrency
//! \brief \ref error_guessing
@@ -538,14 +492,6 @@ TEST_CASE("Flowgraph node set test"){
}
#endif
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
//! Test decution guides
//! \brief \ref requirement
TEST_CASE("Deduction guides test"){
test_deduction_guides();
}
#endif
//! try_release and try_consume test
//! \brief \ref error_guessing
TEST_CASE("try_release try_consume"){
+11
View File
@@ -222,6 +222,17 @@ void TestTerminationAndAutoinit(bool autoinit) {
REQUIRE(res2);
}
//! Check no reference leak for an external thread
//! \brief \ref regression \ref error_guessing
TEST_CASE("test decrease reference") {
tbb::task_scheduler_handle handle = tbb::task_scheduler_handle::get();
std::thread thr([] { tbb::parallel_for(0, 1, [](int) {}); } );
thr.join();
REQUIRE(tbb::finalize(handle, std::nothrow));
}
//! Testing lifetime control conformance
//! \brief \ref interface \ref requirement
TEST_CASE("prolong lifetime simple") {
-49
View File
@@ -254,47 +254,6 @@ void test_reset() {
}
}
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
int input_body_f(tbb::flow_control&) { return 42; }
void test_deduction_guides() {
using namespace tbb::flow;
graph g;
auto lambda = [](tbb::flow_control&) { return 42; };
auto non_const_lambda = [](tbb::flow_control&) mutable { return 42; };
// Tests for input_node(graph&, Body)
input_node s1(g, lambda);
static_assert(std::is_same_v<decltype(s1), input_node<int>>);
input_node s2(g, non_const_lambda);
static_assert(std::is_same_v<decltype(s2), input_node<int>>);
input_node s3(g, input_body_f);
static_assert(std::is_same_v<decltype(s3), input_node<int>>);
input_node s4(s3);
static_assert(std::is_same_v<decltype(s4), input_node<int>>);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
broadcast_node<int> bc(g);
// Tests for input_node(const node_set<Args...>&, Body)
input_node s5(precedes(bc), lambda);
static_assert(std::is_same_v<decltype(s5), input_node<int>>);
input_node s6(precedes(bc), non_const_lambda);
static_assert(std::is_same_v<decltype(s6), input_node<int>>);
input_node s7(precedes(bc), input_body_f);
static_assert(std::is_same_v<decltype(s7), input_node<int>>);
#endif
g.wait_for_all();
}
#endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
#include <array>
void test_follows_and_precedes_api() {
@@ -358,14 +317,6 @@ TEST_CASE("Follows and precedes API"){
}
#endif
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
//! Test deduction guides
//! \brief \ref requirement
TEST_CASE("Deduction guides"){
test_deduction_guides();
}
#endif
//! Test try_get before activation
//! \brief \ref error_guessing
TEST_CASE("try_get before activation"){
+1 -39
View File
@@ -160,36 +160,6 @@ void test_follows_and_precedes_api() {
}
#endif
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
void test_deduction_guides() {
using namespace tbb::flow;
graph g;
using tuple_type = std::tuple<int, int, int>;
broadcast_node<int> b1(g), b2(g), b3(g);
broadcast_node<tuple_type> b4(g);
join_node<tuple_type> j0(g);
#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
join_node j1(follows(b1, b2, b3));
static_assert(std::is_same_v<decltype(j1), join_node<tuple_type>>);
join_node j2(follows(b1, b2, b3), reserving());
static_assert(std::is_same_v<decltype(j2), join_node<tuple_type, reserving>>);
join_node j3(precedes(b4));
static_assert(std::is_same_v<decltype(j3), join_node<tuple_type>>);
join_node j4(precedes(b4), reserving());
static_assert(std::is_same_v<decltype(j4), join_node<tuple_type, reserving>>);
#endif
join_node j5(j0);
static_assert(std::is_same_v<decltype(j5), join_node<tuple_type>>);
}
#endif
namespace multiple_predecessors {
using namespace tbb::flow;
@@ -277,7 +247,7 @@ void test(ConnectJoinNodeFunc&& connect_join_node) {
remove_edge(bn3, input_port<2>(*jn));
remove_edge(bn2, input_port<1>(*jn));
remove_edge(bn1, input_port<0>(*jn));
remove_edge(bn1, *jn); //Removes an edge between a sender and port 0 of a multi-input successor.
remove_edge(*jn, qn);
run_and_check(g, bn1, bn2, bn3, qn, /*expected=*/false);
@@ -293,14 +263,6 @@ TEST_CASE("Test follows and preceedes API"){
}
#endif
#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
//! Test deduction guides
//! \brief \ref requirement
TEST_CASE("Deduction guides test"){
test_deduction_guides();
}
#endif
//! Test hash buffers behavior
//! \brief \ref error_guessing
TEST_CASE("Tagged buffers test"){
+18 -9
View File
@@ -125,6 +125,18 @@ struct put_dec_body : utils::NoAssign {
};
template< typename Sender, typename Receiver >
void make_edge_impl(Sender& sender, Receiver& receiver){
#if __GNUC__ < 12 && !TBB_USE_DEBUG
// Seemingly, GNU compiler generates incorrect code for the call of limiter.register_successor in release (-03)
// The function pointer to make_edge workarounds the issue for unknown reason
auto make_edge_ptr = tbb::flow::make_edge<int>;
make_edge_ptr(sender, receiver); //putting the successor back
#else
tbb::flow::make_edge(sender, receiver); //putting the successor back
#endif
}
template< typename T >
void test_puts_with_decrements( int num_threads, tbb::flow::limiter_node< T >& lim , tbb::flow::graph& g) {
parallel_receiver<T> r(g);
@@ -355,14 +367,7 @@ void test_reserve_release_messages() {
broad.try_put(1); //failed message retrieved.
g.wait_for_all();
#if __GNUC__ && __GNUC__ < 12 && !TBB_USE_DEBUG
// Seemingly, GNU compiler generates incorrect code for the call of limiter.register_successor in release (-03)
// The function pointer to make_edge workarounds the issue for unknown reason
auto make_edge_ptr = make_edge<int>;
make_edge_ptr(limit, output_queue); //putting the successor back
#else
make_edge(limit, output_queue); //putting the successor back
#endif
make_edge_impl(limit, output_queue);
broad.try_put(1); //drop the count
@@ -445,7 +450,9 @@ void test_try_put_without_successors() {
int try_put_num{3};
tbb::flow::buffer_node<int> bn(g);
tbb::flow::limiter_node<int> ln(g, try_put_num);
tbb::flow::make_edge(bn, ln);
int i = 1;
for (; i <= try_put_num; i++)
bn.try_put(i);
@@ -457,7 +464,9 @@ void test_try_put_without_successors() {
return int{};
}
);
tbb::flow::make_edge(ln, fn);
make_edge_impl(ln, fn);
g.wait_for_all();
CHECK((counter == i * try_put_num / 2));
+3 -1
View File
@@ -33,7 +33,9 @@
//! \brief Test for [mutex.spin_mutex mutex.spin_rw_mutex mutex.queuing_mutex mutex.queuing_rw_mutexmutex.speculative_spin_mutex mutex.speculative_spin_rw_mutex] specifications
// TODO: Investigate why RTM doesn't work on some macOS.
#if __TBB_TSX_INTRINSICS_PRESENT && !__APPLE__
// TODO: Consider adding Thread Sanitizer (note that accesses inside the transaction
// considered as races by Thread Sanitizer)
#if __TBB_TSX_INTRINSICS_PRESENT && !__APPLE__ && !__TBB_USE_THREAD_SANITIZER
inline static bool IsInsideTx() {
return _xtest() != 0;
+2
View File
@@ -19,6 +19,7 @@
#include "common/config.h"
#include "tbb/flow_graph.h"
#include "tbb/global_control.h"
#include "common/test.h"
#include "common/utils.h"
@@ -381,6 +382,7 @@ void test_deduction_guides() {
//! \brief \ref requirement \ref error_guessing
TEST_CASE("Serial, parallel and reservation tests"){
for (int p = 2; p <= 4; ++p) {
tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, p);
tbb::task_arena arena(p);
arena.execute(
[&]() {
+2
View File
@@ -19,6 +19,7 @@
#include "common/config.h"
#include "tbb/flow_graph.h"
#include "tbb/global_control.h"
#include "common/test.h"
#include "common/utils.h"
@@ -497,6 +498,7 @@ void test_deduction_guides() {
//! \brief \ref requirement \ref error_guessing
TEST_CASE("Parallel, serial test"){
for (int p = 2; p <= 4; ++p) {
tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, p);
tbb::task_arena arena(p);
arena.execute(
[&]() {
+8 -2
View File
@@ -255,6 +255,12 @@ private:
// Simple test for basic resumable tasks functionality
void TestSuspendResume() {
#if __TBB_USE_SANITIZERS
constexpr int iter_size = 100;
#else
constexpr int iter_size = 50000;
#endif
std::atomic<int> global_epoch; global_epoch = 0;
EpochAsyncActivity async(4, global_epoch);
@@ -263,7 +269,7 @@ void TestSuspendResume() {
inner_par_iters = outer_par_iters = 0;
tbb::parallel_for(0, N, [&](int) {
for (int i = 0; i < 100; ++i) {
for (int i = 0; i < iter_size; ++i) {
ets_fiber.local() = i;
int local_epoch;
@@ -286,7 +292,7 @@ void TestSuspendResume() {
++outer_par_iters;
});
CHECK(outer_par_iters == N);
CHECK(inner_par_iters == N*N*100);
CHECK(inner_par_iters == N*N*iter_size);
}
// During cleanup external thread's local task pool may
+672
View File
@@ -0,0 +1,672 @@
/*
Copyright (c) 2021 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#define TBB_PREVIEW_MUTEXES 1
#define TBB_PREVIEW_WAITING_FOR_WORKERS 1
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
#include "common/config.h"
#include <oneapi/tbb/task_arena.h>
#include <oneapi/tbb/concurrent_vector.h>
#include <oneapi/tbb/rw_mutex.h>
#include <oneapi/tbb/task_group.h>
#include <oneapi/tbb/parallel_for.h>
#include <oneapi/tbb/global_control.h>
#include "common/test.h"
#include "common/utils.h"
#include "common/utils_concurrency_limit.h"
#include "common/spin_barrier.h"
#include <stdlib.h> // C11/POSIX aligned_alloc
#include <random>
//! \file test_scheduler_mix.cpp
//! \brief Test for [scheduler.task_arena scheduler.task_scheduler_observer] specification
const std::uint64_t maxNumActions = 1 * 100 * 1000;
static std::atomic<std::uint64_t> globalNumActions{};
//using Random = utils::FastRandom<>;
class Random {
struct State {
std::random_device rd;
std::mt19937 gen;
std::uniform_int_distribution<> dist;
State() : gen(rd()), dist(0, std::numeric_limits<unsigned short>::max()) {}
int get() {
return dist(gen);
}
};
static thread_local State* mState;
tbb::concurrent_vector<State*> mStateList;
public:
~Random() {
for (auto s : mStateList) {
delete s;
}
}
int get() {
auto& s = mState;
if (!s) {
s = new State;
mStateList.push_back(s);
}
return s->get();
}
};
thread_local Random::State* Random::mState = nullptr;
void* aligned_malloc(std::size_t alignment, std::size_t size) {
#if _WIN32
return _aligned_malloc(size, alignment);
#elif __unix__ || __APPLE__
void* ptr{};
int res = posix_memalign(&ptr, alignment, size);
CHECK(res == 0);
return ptr;
#else
return aligned_alloc(alignment, size);
#endif
}
void aligned_free(void* ptr) {
#if _WIN32
_aligned_free(ptr);
#else
free(ptr);
#endif
}
template <typename T, std::size_t Alignment>
class PtrRWMutex {
static const std::size_t maxThreads = (Alignment >> 1) - 1;
static const std::uintptr_t READER_MASK = maxThreads; // 7F..
static const std::uintptr_t LOCKED = Alignment - 1; // FF..
static const std::uintptr_t LOCKED_MASK = LOCKED; // FF..
static const std::uintptr_t LOCK_PENDING = READER_MASK + 1; // 80..
std::atomic<std::uintptr_t> mState;
T* pointer() {
return reinterpret_cast<T*>(state() & ~LOCKED_MASK);
}
std::uintptr_t state() {
return mState.load(std::memory_order_relaxed);
}
public:
class ScopedLock {
public:
constexpr ScopedLock() : mMutex(nullptr), mIsWriter(false) {}
//! Acquire lock on given mutex.
ScopedLock(PtrRWMutex& m, bool write = true) : mMutex(nullptr) {
CHECK_FAST(write == true);
acquire(m);
}
//! Release lock (if lock is held).
~ScopedLock() {
if (mMutex) {
release();
}
}
//! No Copy
ScopedLock(const ScopedLock&) = delete;
ScopedLock& operator=(const ScopedLock&) = delete;
//! Acquire lock on given mutex.
void acquire(PtrRWMutex& m) {
CHECK_FAST(mMutex == nullptr);
mIsWriter = true;
mMutex = &m;
mMutex->lock();
}
//! Try acquire lock on given mutex.
bool tryAcquire(PtrRWMutex& m, bool write = true) {
bool succeed = write ? m.tryLock() : m.tryLockShared();
if (succeed) {
mMutex = &m;
mIsWriter = write;
}
return succeed;
}
void clear() {
CHECK_FAST(mMutex != nullptr);
CHECK_FAST(mIsWriter);
PtrRWMutex* m = mMutex;
mMutex = nullptr;
m->clear();
}
//! Release lock.
void release() {
CHECK_FAST(mMutex != nullptr);
PtrRWMutex* m = mMutex;
mMutex = nullptr;
if (mIsWriter) {
m->unlock();
}
else {
m->unlockShared();
}
}
protected:
PtrRWMutex* mMutex{};
bool mIsWriter{};
};
bool trySet(T* ptr) {
auto p = reinterpret_cast<std::uintptr_t>(ptr);
CHECK_FAST((p & (Alignment - 1)) == 0);
if (!state()) {
std::uintptr_t expected = 0;
if (mState.compare_exchange_strong(expected, p)) {
return true;
}
}
return false;
}
void clear() {
CHECK_FAST((state() & LOCKED_MASK) == LOCKED);
mState.store(0, std::memory_order_relaxed);
}
bool tryLock() {
auto v = state();
if (v == 0) {
return false;
}
CHECK_FAST((v & LOCKED_MASK) == LOCKED || (v & READER_MASK) < maxThreads);
if ((v & READER_MASK) == 0) {
if (mState.compare_exchange_strong(v, v | LOCKED)) {
return true;
}
}
return false;
}
bool tryLockShared() {
auto v = state();
if (v == 0) {
return false;
}
CHECK_FAST((v & LOCKED_MASK) == LOCKED || (v & READER_MASK) < maxThreads);
if ((v & LOCKED_MASK) != LOCKED && (v & LOCK_PENDING) == 0) {
if (mState.compare_exchange_strong(v, v + 1)) {
return true;
}
}
return false;
}
void lock() {
auto v = state();
mState.compare_exchange_strong(v, v | LOCK_PENDING);
while (!tryLock()) {
utils::yield();
}
}
void unlock() {
auto v = state();
CHECK_FAST((v & LOCKED_MASK) == LOCKED);
mState.store(v & ~LOCKED, std::memory_order_release);
}
void unlockShared() {
auto v = state();
CHECK_FAST((v & LOCKED_MASK) != LOCKED);
CHECK_FAST((v & READER_MASK) > 0);
mState -= 1;
}
operator bool() const {
return pointer() != 0;
}
T* get() {
return pointer();
}
};
class Statistics {
public:
enum ACTION {
ArenaCreate,
ArenaDestroy,
ArenaAcquire,
skippedArenaCreate,
skippedArenaDestroy,
skippedArenaAcquire,
ParallelAlgorithm,
ArenaEnqueue,
ArenaExecute,
numActions
};
static const char* const mStatNames[numActions];
private:
struct StatType {
StatType() : mCounters() {}
std::array<std::uint64_t, numActions> mCounters;
};
tbb::concurrent_vector<StatType*> mStatsList;
static thread_local StatType* mStats;
StatType& get() {
auto& s = mStats;
if (!s) {
s = new StatType;
mStatsList.push_back(s);
}
return *s;
}
public:
~Statistics() {
for (auto s : mStatsList) {
delete s;
}
}
void notify(ACTION a) {
++get().mCounters[a];
}
void report() {
StatType summary;
for (auto& s : mStatsList) {
for (int i = 0; i < numActions; ++i) {
summary.mCounters[i] += s->mCounters[i];
}
}
std::cout << std::endl << "Statistics:" << std::endl;
std::cout << "Total actions: " << globalNumActions << std::endl;
for (int i = 0; i < numActions; ++i) {
std::cout << mStatNames[i] << ": " << summary.mCounters[i] << std::endl;
}
}
};
const char* const Statistics::mStatNames[Statistics::numActions] = {
"Arena create", "Arena destroy", "Arena acquire",
"Skipped arena create", "Skipped arena destroy", "Skipped arena acquire",
"Parallel algorithm", "Arena enqueue", "Arena execute"
};
thread_local Statistics::StatType* Statistics::mStats;
static Statistics gStats;
class LifetimeTracker {
public:
LifetimeTracker() = default;
class Guard {
public:
Guard(LifetimeTracker* obj) {
if (!(obj->mReferences.load(std::memory_order_relaxed) & SHUTDOWN_FLAG)) {
if (obj->mReferences.fetch_add(REFERENCE_FLAG) & SHUTDOWN_FLAG) {
obj->mReferences.fetch_sub(REFERENCE_FLAG);
} else {
mObj = obj;
}
}
}
Guard(Guard&& ing) : mObj(ing.mObj) {
ing.mObj = nullptr;
}
~Guard() {
if (mObj != nullptr) {
mObj->mReferences.fetch_sub(REFERENCE_FLAG);
}
}
bool continue_execution() {
return mObj != nullptr;
}
private:
LifetimeTracker* mObj{nullptr};
};
Guard makeGuard() {
return Guard(this);
}
void signalShutdown() {
mReferences.fetch_add(SHUTDOWN_FLAG);
}
void waitCompletion() {
utils::SpinWaitUntilEq(mReferences, SHUTDOWN_FLAG);
}
private:
friend class Guard;
static constexpr std::uintptr_t SHUTDOWN_FLAG = 1;
static constexpr std::uintptr_t REFERENCE_FLAG = 1 << 1;
std::atomic<std::uintptr_t> mReferences{};
};
class ArenaTable {
static const std::size_t maxArenas = 64;
static const std::size_t maxThreads = 1 << 9;
static const std::size_t arenaAligment = maxThreads << 1;
using ArenaPtrRWMutex = PtrRWMutex<tbb::task_arena, arenaAligment>;
std::array<ArenaPtrRWMutex, maxArenas> mArenaTable;
struct ThreadState {
bool lockedArenas[maxArenas]{};
int arenaIdxStack[maxArenas];
int level{};
};
LifetimeTracker mLifetimeTracker{};
static thread_local ThreadState mThreadState;
template <typename F>
auto find_arena(std::size_t start, F f) -> decltype(f(std::declval<ArenaPtrRWMutex&>(), std::size_t{})) {
for (std::size_t idx = start, i = 0; i < maxArenas; ++i, idx = (idx + 1) % maxArenas) {
auto res = f(mArenaTable[idx], idx);
if (res) {
return res;
}
}
return {};
}
public:
using ScopedLock = ArenaPtrRWMutex::ScopedLock;
void create(Random& rnd) {
auto guard = mLifetimeTracker.makeGuard();
if (guard.continue_execution()) {
int num_threads = rnd.get() % utils::get_platform_max_threads() + 1;
unsigned int num_reserved = rnd.get() % num_threads;
tbb::task_arena::priority priorities[] = { tbb::task_arena::priority::low , tbb::task_arena::priority::normal, tbb::task_arena::priority::high };
tbb::task_arena::priority priority = priorities[rnd.get() % 3];
tbb::task_arena* a = new (aligned_malloc(arenaAligment, arenaAligment)) tbb::task_arena{ num_threads , num_reserved , priority };
if (!find_arena(rnd.get() % maxArenas, [a](ArenaPtrRWMutex& arena, std::size_t) -> bool {
if (arena.trySet(a)) {
return true;
}
return false;
}))
{
gStats.notify(Statistics::skippedArenaCreate);
a->~task_arena();
aligned_free(a);
}
}
}
void destroy(Random& rnd) {
auto guard = mLifetimeTracker.makeGuard();
if (guard.continue_execution()) {
auto& ts = mThreadState;
if (!find_arena(rnd.get() % maxArenas, [&ts](ArenaPtrRWMutex& arena, std::size_t idx) {
if (!ts.lockedArenas[idx]) {
ScopedLock lock;
if (lock.tryAcquire(arena, true)) {
auto a = arena.get();
lock.clear();
a->~task_arena();
aligned_free(a);
return true;
}
}
return false;
}))
{
gStats.notify(Statistics::skippedArenaDestroy);
}
}
}
void shutdown() {
mLifetimeTracker.signalShutdown();
mLifetimeTracker.waitCompletion();
find_arena(0, [](ArenaPtrRWMutex& arena, std::size_t) {
if (arena.get()) {
ScopedLock lock{ arena, true };
auto a = arena.get();
lock.clear();
a->~task_arena();
aligned_free(a);
}
return false;
});
}
std::pair<tbb::task_arena*, std::size_t> acquire(Random& rnd, ScopedLock& lock) {
auto guard = mLifetimeTracker.makeGuard();
tbb::task_arena* a{nullptr};
std::size_t resIdx{};
if (guard.continue_execution()) {
auto& ts = mThreadState;
a = find_arena(rnd.get() % maxArenas,
[&ts, &lock, &resIdx](ArenaPtrRWMutex& arena, std::size_t idx) -> tbb::task_arena* {
if (!ts.lockedArenas[idx]) {
if (lock.tryAcquire(arena, false)) {
ts.lockedArenas[idx] = true;
ts.arenaIdxStack[ts.level++] = int(idx);
resIdx = idx;
return arena.get();
}
}
return nullptr;
});
if (!a) {
gStats.notify(Statistics::skippedArenaAcquire);
}
}
return { a, resIdx };
}
void release(ScopedLock& lock) {
auto& ts = mThreadState;
CHECK_FAST(ts.level > 0);
auto idx = ts.arenaIdxStack[--ts.level];
CHECK_FAST(ts.lockedArenas[idx]);
ts.lockedArenas[idx] = false;
lock.release();
}
};
thread_local ArenaTable::ThreadState ArenaTable::mThreadState;
static ArenaTable arenaTable;
static Random threadRandom;
enum ACTIONS {
arena_create,
arena_destroy,
arena_action,
parallel_algorithm,
// TODO:
// observer_attach,
// observer_detach,
// flow_graph,
// task_group,
// resumable_tasks,
num_actions
};
void global_actor();
template <ACTIONS action>
struct actor;
template <>
struct actor<arena_create> {
static void do_it(Random& r) {
arenaTable.create(r);
}
};
template <>
struct actor<arena_destroy> {
static void do_it(Random& r) {
arenaTable.destroy(r);
}
};
template <>
struct actor<arena_action> {
static void do_it(Random& r) {
static thread_local std::size_t arenaLevel = 0;
ArenaTable::ScopedLock lock;
auto entry = arenaTable.acquire(r, lock);
if (entry.first) {
enum arena_actions {
arena_execute,
arena_enqueue,
num_arena_actions
};
auto process = r.get() % 2;
auto body = [process] {
if (process) {
tbb::detail::d1::wait_context wctx{ 1 };
tbb::task_group_context ctx;
tbb::this_task_arena::enqueue([&wctx] { wctx.release(); });
tbb::detail::d1::wait(wctx, ctx);
} else {
global_actor();
}
};
switch (r.get() % (16*num_arena_actions)) {
case arena_execute:
if (entry.second > arenaLevel) {
gStats.notify(Statistics::ArenaExecute);
auto oldArenaLevel = arenaLevel;
arenaLevel = entry.second;
entry.first->execute(body);
arenaLevel = oldArenaLevel;
break;
}
utils_fallthrough;
case arena_enqueue:
utils_fallthrough;
default:
gStats.notify(Statistics::ArenaEnqueue);
entry.first->enqueue([] { global_actor(); });
break;
}
arenaTable.release(lock);
}
}
};
template <>
struct actor<parallel_algorithm> {
static void do_it(Random& rnd) {
enum PARTITIONERS {
simpl_part,
auto_part,
aff_part,
static_part,
num_parts
};
int sz = rnd.get() % 10000;
auto doGlbAction = rnd.get() % 1000 == 42;
auto body = [doGlbAction, sz](int i) {
if (i == sz / 2 && doGlbAction) {
global_actor();
}
};
switch (rnd.get() % num_parts) {
case simpl_part:
tbb::parallel_for(0, sz, body, tbb::simple_partitioner{}); break;
case auto_part:
tbb::parallel_for(0, sz, body, tbb::auto_partitioner{}); break;
case aff_part:
{
tbb::affinity_partitioner aff;
tbb::parallel_for(0, sz, body, aff); break;
}
case static_part:
tbb::parallel_for(0, sz, body, tbb::static_partitioner{}); break;
}
}
};
void global_actor() {
static thread_local std::uint64_t localNumActions{};
while (globalNumActions < maxNumActions) {
auto& rnd = threadRandom;
switch (rnd.get() % num_actions) {
case arena_create: gStats.notify(Statistics::ArenaCreate); actor<arena_create>::do_it(rnd); break;
case arena_destroy: gStats.notify(Statistics::ArenaDestroy); actor<arena_destroy>::do_it(rnd); break;
case arena_action: gStats.notify(Statistics::ArenaAcquire); actor<arena_action>::do_it(rnd); break;
case parallel_algorithm: gStats.notify(Statistics::ParallelAlgorithm); actor<parallel_algorithm>::do_it(rnd); break;
}
if (++localNumActions == 100) {
localNumActions = 0;
globalNumActions += 100;
// TODO: Enable statistics
// static std::mutex mutex;
// std::lock_guard<std::mutex> lock{ mutex };
// std::cout << globalNumActions << "\r" << std::flush;
}
}
}
#if TBB_USE_EXCEPTIONS
//! \brief \ref stress
TEST_CASE("Stress test with mixing functionality") {
// TODO add thread recreation
// TODO: Enable statistics
// tbb::task_scheduler_handle handle = tbb::task_scheduler_handle::get();
const std::size_t numExtraThreads = 16;
utils::SpinBarrier startBarrier{numExtraThreads};
utils::NativeParallelFor(numExtraThreads, [&startBarrier](std::size_t) {
startBarrier.wait();
global_actor();
});
arenaTable.shutdown();
// tbb::finalize(handle, std::nothrow_t{});
// gStats.report();
}
#endif
+2
View File
@@ -17,6 +17,7 @@
#include "common/config.h"
#include "tbb/flow_graph.h"
#include "tbb/global_control.h"
#include "common/test.h"
#include "common/utils.h"
@@ -440,6 +441,7 @@ void test_deduction_guides() {
//! \brief \ref requirement \ref error_guessing
TEST_CASE("Serial and parallel test"){
for (int p = 2; p <= 4; ++p) {
tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, p);
tbb::task_arena arena(p);
arena.execute(
[&]() {
+19
View File
@@ -1745,7 +1745,26 @@ struct enqueue_test_helper {
tbb::enumerable_thread_specific<bool>& my_ets;
std::atomic<std::size_t>& my_task_counter;
};
//--------------------------------------------------//
// This test requires TBB in an uninitialized state
//! \brief \ref requirement
TEST_CASE("task_arena initialize soft limit ignoring affinity mask") {
REQUIRE_MESSAGE((tbb::this_task_arena::current_thread_index() == tbb::task_arena::not_initialized), "TBB was initialized state");
tbb::enumerable_thread_specific<int> ets;
tbb::task_arena arena(int(utils::get_platform_max_threads() * 2));
arena.execute([&ets] {
tbb::parallel_for(0, 10000000, [&ets](int){
ets.local() = 1;
utils::doDummyWork(100);
});
});
CHECK(ets.combine(std::plus<int>{}) <= int(utils::get_platform_max_threads()));
}
//! Test for task arena in concurrent cases
//! \brief \ref requirement
TEST_CASE("Test for concurrent functionality") {
+18 -1
View File
@@ -1173,6 +1173,23 @@ TEST_CASE("Task handle run"){
CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
}
//! Basic test for task handle
//! \brief \ref interface \ref requirement
TEST_CASE("Task handle run_and_wait"){
tbb::task_handle h;
tbb::task_group tg;
bool run {false};
h = tg.defer([&]{
run = true;
});
CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
tg.run_and_wait(std::move(h));
CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
}
//! Test for empty check
//! \brief \ref interface
TEST_CASE("Task handle empty check"){
@@ -1308,7 +1325,7 @@ TEST_CASE("task_handle cannot be scheduled into different task_group"){
//! The test for error in task_handle being scheduled into task_group different from one it was created from
//! \brief \ref requirement
TEST_CASE("task_handle cannot be scheduled into other task_group of the same context"
* doctest::should_fail() //Implementation is no there yet, as it is not clear that is the expected behaviour
* doctest::should_fail() //Implementation is no there yet, as it is not clear that is the expected behavior
* doctest::skip() //skip the test for now, to not pollute the test log
)
{
+6 -6
View File
@@ -354,13 +354,13 @@ void InvariantDataRealloc(bool aligned, size_t maxAllocSize, bool checkData)
size_t size = 0, start = 0;
char *ptr = NULL,
// external thread to create copies and compare ralloc result against it
*master = (char*)Tmalloc(2*maxAllocSize);
*base = (char*)Tmalloc(2*maxAllocSize);
REQUIRE(master);
REQUIRE(base);
REQUIRE_MESSAGE(!(2*maxAllocSize%sizeof(unsigned short)),
"The loop below expects that 2*maxAllocSize contains sizeof(unsigned short)");
for (size_t k = 0; k<2*maxAllocSize; k+=sizeof(unsigned short))
*(unsigned short*)(master+k) = fastRandom.get();
*(unsigned short*)(base+k) = fastRandom.get();
for (int i=0; i<100; i++) {
// don't want sizeNew==0 here
@@ -371,21 +371,21 @@ void InvariantDataRealloc(bool aligned, size_t maxAllocSize, bool checkData)
REQUIRE(ptrNew);
// check that old data not changed
if (checkData)
REQUIRE_MESSAGE(!memcmp(ptrNew, master+start, utils::min(size, sizeNew)), "broken data");
REQUIRE_MESSAGE(!memcmp(ptrNew, base+start, utils::min(size, sizeNew)), "broken data");
// prepare fresh data, copying them from random position in external
size = sizeNew;
ptr = ptrNew;
if (checkData) {
start = fastRandom.get() % maxAllocSize;
memcpy(ptr, master+start, size);
memcpy(ptr, base+start, size);
}
}
if (aligned)
Taligned_realloc(ptr, 0, choose_random_alignment());
else
Trealloc(ptr, 0);
Tfree(master);
Tfree(base);
}
void CheckReallocLeak()
+1 -1
View File
@@ -142,7 +142,7 @@ struct TestThread: utils::NoAssign {
// intersectingObjects takes into account object shuffle
REQUIRE_MESSAGE((!prevLarge || intersectingObjects(currLarge, prevLarge, 32*1024)), "Possible memory leak");
pthread_key_create( &key, &threadDtor );
pthread_setspecific(key, (const void*)42);
pthread_setspecific(key, (const void*)this);
}
};
+1 -3
View File
@@ -33,9 +33,7 @@
#pragma warning (disable: 4800)
#endif //#if _MSC_VER
#include "../../src/tbb/tls.h"
tbb::detail::r1::tls<bool> new_handler_called;
thread_local bool new_handler_called = false;
void customNewHandler() {
new_handler_called = true;
throw std::bad_alloc();
+21 -4
View File
@@ -135,14 +135,14 @@ using namespace std;
class BackRefIdx { // composite index to backreference array
private:
uint16_t master; // index in BackRefMaster
uint16_t main; // index in BackRefMain
uint16_t largeObj:1; // is this object "large"?
uint16_t offset :15; // offset from beginning of BackRefBlock
public:
BackRefIdx() : master((uint16_t)-1) {}
bool isInvalid() { return master == (uint16_t)-1; }
BackRefIdx() : main((uint16_t)-1) {}
bool isInvalid() { return main == (uint16_t)-1; }
bool isLargeObject() const { return largeObj; }
uint16_t getMaster() const { return master; }
uint16_t getMain() const { return main; }
uint16_t getOffset() const { return offset; }
// only newBackRef can modify BackRefIdx
@@ -500,4 +500,21 @@ TEST_CASE("Main set of tests") {
TestZoneOverload();
TestRuntimeRoutines();
}
//! Test address range tracker in backend that could be
//! broken during remap because of incorrect order of
//! deallocation event and the mremap system call
//! \brief \ref regression
TEST_CASE("Address range tracker regression test") {
int numThreads = 16;
utils::NativeParallelFor(numThreads, [](int) {
void *ptr = nullptr;
for (int i = 0; i < 1000; ++i) {
for (int j = 0; j < 100; ++j) {
ptr = realloc(ptr, 1024*1024 + 4096*j);
}
}
free(ptr);
});
}
#endif // !HARNESS_SKIP_TEST
+8 -8
View File
@@ -279,8 +279,8 @@ public:
static size_t allocatedBackRefCount()
{
size_t cnt = 0;
for (int i=0; i<=backRefMaster.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed); i++)
cnt += backRefMaster.load(std::memory_order_relaxed)->backRefBl[i]->allocatedCount;
for (int i=0; i<=backRefMain.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed); i++)
cnt += backRefMain.load(std::memory_order_relaxed)->backRefBl[i]->allocatedCount;
return cnt;
}
@@ -312,7 +312,7 @@ public:
char *p1 = (char*)scalable_malloc(minLargeObjectSize-1);
LargeObjectHdr *hdr =
(LargeObjectHdr*)(p1+minLargeObjectSize-1 - sizeof(LargeObjectHdr));
hdr->backRefIdx.master = 7;
hdr->backRefIdx.main = 7;
hdr->backRefIdx.largeObj = 1;
hdr->backRefIdx.offset = 2000;
@@ -346,9 +346,9 @@ void TestBackRef() {
REQUIRE_MESSAGE(beforeNumBackRef==afterNumBackRef, "backreference leak detected");
// lastUsed marks peak resource consumption. As we allocate below the mark,
// it must not move up, otherwise there is a resource leak.
int sustLastUsed = backRefMaster.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed);
int sustLastUsed = backRefMain.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed);
utils::NativeParallelFor( 1, BackRefWork<2*BR_MAX_CNT+2>() );
REQUIRE_MESSAGE(sustLastUsed == backRefMaster.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed), "backreference leak detected");
REQUIRE_MESSAGE(sustLastUsed == backRefMain.load(std::memory_order_relaxed)->lastUsed.load(std::memory_order_relaxed), "backreference leak detected");
// check leak of back references while per-thread caches are in use
// warm up needed to cover bootStrapMalloc call
utils::NativeParallelFor( 1, LocalCachesHit() );
@@ -591,9 +591,9 @@ void TestObjectRecognition() {
const int NUM_OF_IDX = BR_MAX_CNT+2;
BackRefIdx idxs[NUM_OF_IDX];
for (int cnt=0; cnt<2; cnt++) {
for (int master = -10; master<10; master++) {
falseBlock->backRefIdx.master = (uint16_t)master;
headerLO->backRefIdx.master = (uint16_t)master;
for (int main = -10; main<10; main++) {
falseBlock->backRefIdx.main = (uint16_t)main;
headerLO->backRefIdx.main = (uint16_t)main;
for (int bl = -10; bl<BR_MAX_CNT+10; bl++) {
falseBlock->backRefIdx.offset = (uint16_t)bl;