├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── LdFlags.R ├── RcppThread-package.R ├── detectCores.R └── inline.R ├── README.md ├── RcppThread.Rproj ├── benchmarks ├── benchmarks.R ├── benchmarks.Rmd ├── benchmarks.cpp ├── benchmarks.md └── benchmarks_files │ └── figure-markdown_strict │ ├── unnamed-chunk-3-1.png │ ├── unnamed-chunk-4-1.png │ ├── unnamed-chunk-5-1.png │ ├── unnamed-chunk-6-1.png │ ├── unnamed-chunk-7-1.png │ ├── unnamed-chunk-8-1.png │ └── unnamed-chunk-9-1.png ├── cran-comments.md ├── docs ├── Doxyfile ├── Doxyfile-mcss ├── annotated.html ├── classRcppThread_1_1ProgressBar.html ├── classRcppThread_1_1ProgressCounter.html ├── classRcppThread_1_1ProgressPrinter.html ├── classRcppThread_1_1RErrPrinter.html ├── classRcppThread_1_1RMonitor.html ├── classRcppThread_1_1RPrinter.html ├── classRcppThread_1_1Thread.html ├── classRcppThread_1_1ThreadPool.html ├── classRcppThread_1_1UserInterruptException.html ├── favicon-dark.png ├── files.html ├── index.html ├── m-dark+documentation.compiled.css ├── mainpage_8h.html ├── modules.html ├── namespaceRcppThread.html ├── namespaces.html ├── overrides.dox ├── overrides.html ├── pages.html ├── search-v2.js ├── searchdata-v2.js └── xml │ ├── Progress_8hpp.xml │ ├── RMonitor_8hpp.xml │ ├── Rcerr_8hpp.xml │ ├── Rcout_8hpp.xml │ ├── RcppThread_8h.xml │ ├── ThreadPool_8hpp.xml │ ├── Thread_8hpp.xml │ ├── classRcppThread_1_1ProgressBar.xml │ ├── classRcppThread_1_1ProgressCounter.xml │ ├── classRcppThread_1_1ProgressPrinter.xml │ ├── classRcppThread_1_1RErrPrinter.xml │ ├── classRcppThread_1_1RMonitor.xml │ ├── classRcppThread_1_1RPrinter.xml │ ├── classRcppThread_1_1Thread.xml │ ├── classRcppThread_1_1ThreadPool.xml │ ├── classRcppThread_1_1UserInterruptException.xml │ ├── combine.xslt │ ├── compound.xsd │ ├── dir_2b5d1a398a9eec011391d543f21db223.xml │ ├── dir_920884c6b3b2f0be94e27d51660adee5.xml │ ├── dir_b2386a6e32c05a6a1e7925d7c95f2160.xml │ ├── index.xml │ ├── index.xsd │ ├── indexpage.xml │ ├── mainpage_8h.xml │ ├── namespaceRcppThread.xml │ ├── overrides.xml │ ├── overrides_8dox.xml │ ├── parallelFor_8hpp.xml │ ├── quickpool_8hpp.xml │ └── xml.xsd ├── inst ├── CITATION └── include │ ├── RcppThread.h │ ├── RcppThread │ ├── Progress.hpp │ ├── RMonitor.hpp │ ├── Rcerr.hpp │ ├── Rcout.hpp │ ├── SafeTypes.hpp │ ├── Thread.hpp │ ├── ThreadPool.hpp │ ├── parallelFor.hpp │ └── quickpool.hpp │ └── mainpage.h ├── man ├── LdFlags.Rd ├── RcppThread.Rd └── detectCores.Rd ├── src ├── .gitignore ├── Makevars ├── detectCores.cpp └── testGlobal.cpp ├── tests ├── tests.cpp ├── testthat.R └── testthat │ └── tests.R └── vignettes ├── RcppThread-vignette.pdf └── RcppThread-vignette.pdf.asis /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | LICENSE.md 4 | examples 5 | Doxyfile.in 6 | inst/include/mainpage.h 7 | ^docs$ 8 | ^\.travis\.yml$ 9 | ^appveyor\.yml$ 10 | ^codecov\.yml$ 11 | ^cran-comments\.md$ 12 | revdep/ 13 | ^\.github$ 14 | ^CRAN-RELEASE$ 15 | .vscode/ 16 | new-benchmarks.R 17 | bench* 18 | ^CRAN-SUBMISSION$ 19 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: This workflow is overkill for most R packages 2 | # check-standard.yaml is likely a better choice 3 | # usethis::use_github_action("check-standard") will install it. 4 | # 5 | # For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. 6 | # https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions 7 | on: [push, pull_request] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macOS-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest", http-user-agent: "R/4.0.0 (ubuntu-16.04) R (4.0.0 x86_64-pc-linux-gnu x86_64 linux-gnu) on GitHub Actions" } 24 | - {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} 25 | - {os: ubuntu-20.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} 26 | 27 | env: 28 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 29 | RSPM: ${{ matrix.config.rspm }} 30 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | 35 | - uses: r-lib/actions/setup-r@v2 36 | with: 37 | r-version: ${{ matrix.config.r }} 38 | http-user-agent: ${{ matrix.config.http-user-agent }} 39 | 40 | - uses: r-lib/actions/setup-pandoc@v2 41 | 42 | - name: Query dependencies 43 | run: | 44 | install.packages('remotes') 45 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 46 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 47 | shell: Rscript {0} 48 | 49 | - name: Cache R packages 50 | if: runner.os != 'Windows' 51 | uses: actions/cache@v1 52 | with: 53 | path: ${{ env.R_LIBS_USER }} 54 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 55 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 56 | 57 | - name: Install system dependencies 58 | if: runner.os == 'Linux' 59 | run: | 60 | while read -r cmd 61 | do 62 | eval sudo $cmd 63 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "16.04"))') 64 | 65 | - name: Install dependencies 66 | run: | 67 | remotes::install_deps(dependencies = TRUE) 68 | remotes::install_cran("rcmdcheck") 69 | shell: Rscript {0} 70 | 71 | - name: Session info 72 | run: | 73 | options(width = 100) 74 | pkgs <- installed.packages()[, "Package"] 75 | sessioninfo::session_info(pkgs, include_base = TRUE) 76 | shell: Rscript {0} 77 | 78 | - name: Check 79 | env: 80 | _R_CHECK_CRAN_INCOMING_: false 81 | run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 82 | shell: Rscript {0} 83 | # run: cd .. && R CMD build RcppThread && R CMD check RcppThread_1.1.0.tar.gz 84 | # shell: bash 85 | 86 | - name: Show testthat output 87 | if: always() 88 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 89 | shell: bash 90 | 91 | - name: Upload check results 92 | if: failure() 93 | uses: actions/upload-artifact@main 94 | with: 95 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 96 | path: check 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | inst/doc 5 | revdep 6 | __pycache__ 7 | .vscode/ 8 | new-benchmarks.R 9 | *.pdf 10 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: RcppThread 2 | Title: R-Friendly Threading in C++ 3 | Version: 2.2.0 4 | Authors@R: c( 5 | person("Thomas", "Nagler",, "mail@tnagler.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0003-1855-0046")) 7 | ) 8 | Description: Provides a C++11-style thread class and thread pool that can safely 9 | be interrupted from R. See Nagler (2021) . 10 | Depends: R (>= 3.3.0) 11 | License: MIT + file LICENSE 12 | Encoding: UTF-8 13 | URL: https://github.com/tnagler/RcppThread 14 | BugReports: https://github.com/tnagler/RcppThread/issues 15 | RoxygenNote: 7.3.2.9000 16 | Suggests: 17 | testthat, 18 | R.rsp, 19 | Rcpp 20 | VignetteBuilder: R.rsp 21 | Roxygen: list(markdown = TRUE) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022 2 | COPYRIGHT HOLDER: Thomas Nagler 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2022 Thomas Nagler. 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(LdFlags) 4 | export(detectCores) 5 | useDynLib(RcppThread, .registration=TRUE) 6 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # RcppThread 2.2.0 2 | 3 | NEW FEATURE 4 | 5 | * Added `RMatrix` class (from the RcppParallel package) to allow 6 | for thread safe access to R matrices (#74, thanks @rorynolan). 7 | 8 | BUG FIX 9 | 10 | * Fixed compilation warning about R_NO_REMAP being defined multiple times 11 | (#77, thanks @tylermorganwall) 12 | 13 | # RcppThread 2.1.7 14 | 15 | BUG FIX 16 | 17 | * enable check for standalone libatomic on Windows (#72, thanks @kalibera) 18 | 19 | 20 | # RcppThread 2.1.6 21 | 22 | NEW FEATURES 23 | 24 | * allow custom message in `ProgessCounter` (thanks @tylermorganwall) 25 | 26 | 27 | # RcppThread 2.1.5 28 | 29 | BUG FIX 30 | 31 | * Fixes static lifetime issue on MacM1. 32 | 33 | # RcppThread 2.1.4 34 | 35 | BUG FIX 36 | 37 | * Fixes order of headers in `detectCores.cpp`. 38 | 39 | REQUIREMENTS 40 | 41 | * Remove `Systemrequirements: C++11` 42 | 43 | 44 | # RcppThread 2.1.3 45 | 46 | BUG FIX 47 | 48 | * Fixes clang-14 warning about bitwise & with Boolean operands. 49 | 50 | 51 | # RcppThread 2.1.2 52 | 53 | NEW FEATURE 54 | 55 | * R function `LdFlags()` to portably generate linker flags for `libatomic`/`pthread`. See README for details. 56 | 57 | 58 | # RcppThread 2.1.0 59 | 60 | NEW FEATURE 61 | 62 | * safe printing to the R error stream with `RcppThread::Rcerr` (#60, thanks to 63 | @appelmar). 64 | 65 | BUG FIX 66 | 67 | * adapt thread affinity to possibly restricted CPU set (#61). 68 | 69 | 70 | # RcppThread 2.0.1 71 | 72 | BUG FIX 73 | 74 | * safeguard construction of memory aligned objects. 75 | 76 | 77 | # RcppThread 2.0.0 78 | 79 | NEW FEATURES 80 | 81 | * Add R function `detectCores()` (#48). 82 | 83 | * Add classes `ProgressCounter` and `ProgressBar` for tracking progress in long- 84 | running loops (#49). 85 | 86 | * Increased speed due to work-stealing and lock-free pops (#51, #52, #53). 87 | 88 | * Free-standing `parallelFor()` and `parallelForEach()` functions now dispatch 89 | to a global thread pool that persists for the entire session. This 90 | significantly speeds up programs that repeatedly call these functions. (#54) 91 | 92 | * New free-standing `push()`, `pushReturn()`/`async()`, and `wait()`, mirroring 93 | functionality from `ThreadPool`. (#56) 94 | 95 | * Option to resize a thread pool (#56). 96 | 97 | 98 | # RcppThread 1.0.0 99 | 100 | * Release for JSS publication https://doi.org/10.18637/jss.v097.c01. 101 | 102 | 103 | # RcppThread 0.5.4 104 | 105 | * Fixed warning for move constructor in ThreadPool (#35, #36, thanks @asardaes 106 | for noticing). 107 | 108 | 109 | # RcppThread 0.5.3 110 | 111 | * Improved handling of exceptions thrown from threads. 112 | 113 | 114 | # RcppThread 0.5.2 115 | 116 | * Limit number of threads in unit tests. 117 | 118 | * Fixed typos in package vignette. 119 | 120 | 121 | # RcppThread 0.5.1 122 | 123 | BUG FIXES 124 | 125 | * Fix portability issues related to `native_handle_type`. 126 | 127 | * Fix signed/unsigned comparison in `parallelFor()`. 128 | 129 | * Fix signed/unsigned warnings in unit tests. 130 | 131 | 132 | # RcppThread 0.5.0 133 | 134 | DEPENDENCIES 135 | 136 | * Rcpp is no longer a hard dependency, but only used for unit tests. This avoids unnecessary compilation time during package installation. 137 | 138 | NEW FEATURES 139 | 140 | * New vignette available, see `browseVignettes("RcppThread")`. 141 | 142 | * New functions `parallelFor()` and `ForEach()` allowing parallel `for` loops 143 | with load balancing. Can also be called as method of a `ThreadPool`. 144 | 145 | * Options to override `std::thread` and `std::cout` with RcppThread equivalents 146 | using preprocessor variables `RCPPTHREAD_OVERRIDE_THREAD` and 147 | `RCPPTHREAD_OVERRIDE_COUT`. 148 | 149 | * Several minor performance optimizations. 150 | 151 | 152 | # RcppThread 0.4.0 153 | 154 | NEW FEATURE 155 | 156 | * New function `ThreadPool::map()` that allows to map a function a list of items. 157 | 158 | 159 | # RcppThread 0.3.0 160 | 161 | NEW FEATURE 162 | 163 | * A `ThreadPool` can now be instantiated with zero threads in the pool. It 164 | will then do all work pushed to it in the main thread. 165 | 166 | 167 | # RcppThread 0.2.0 168 | 169 | NEW FEATURE 170 | 171 | * `ThreadPool` has a new method `wait()` that waits for all jobs to be done 172 | without joining the threads. This way the thread pool can be re-used for 173 | different types of tasks that need to be run sequentially. 174 | 175 | 176 | # RcppThread 0.1.3 177 | 178 | BUG FIX 179 | 180 | * Don't check print output of multi-threaded code b/c of random results. 181 | 182 | 183 | # RcppThread 0.1.2 184 | 185 | DEPENDS 186 | 187 | * Now available for `R (>= 3.3.0)`. 188 | 189 | BUG FIX 190 | 191 | * Fixed a randomly failing unit test. 192 | 193 | 194 | # RcppThread 0.1.1 195 | 196 | BUG FIX 197 | 198 | * Default initialize static `Rcout` instance in header file (#9; couldn't link 199 | shared library on r-hub, see discussion in #8) 200 | 201 | 202 | # RcppThread 0.1.0 203 | 204 | * Initial release. 205 | -------------------------------------------------------------------------------- /R/LdFlags.R: -------------------------------------------------------------------------------- 1 | getCompiler <- function() { 2 | tools::Rcmd(c("config", "CXX"), stdout = TRUE) 3 | } 4 | 5 | runCmd <- function(...) { 6 | system(paste(...), ignore.stdout = TRUE, ignore.stderr = TRUE) 7 | } 8 | 9 | createTestFiles <- function() { 10 | src <- tempfile("test", fileext = ".cpp") 11 | out <- tempfile("test", fileext = ".out") 12 | c(src = src, out = out) 13 | } 14 | 15 | writeLibAtomicTest <- function(file) { 16 | cat( 17 | "#include 18 | std::atomic x; 19 | int main() { 20 | std::atomic_is_lock_free(&x); 21 | return 0; 22 | }", 23 | file = file 24 | ) 25 | } 26 | 27 | checkForLibAtomic <- function() { 28 | tmp <- createTestFiles() 29 | writeLibAtomicTest(tmp["src"]) 30 | failed <- runCmd(getCompiler(), tmp["src"], "-o", tmp["out"], "-latomic") 31 | unlink(tmp) 32 | 33 | !failed 34 | } 35 | 36 | hasAtomicSupport <- function() { 37 | if (checkForLibAtomic()) 38 | return(TRUE) 39 | 40 | tmp <- createTestFiles() 41 | writeLibAtomicTest(tmp["src"]) 42 | failed <- runCmd(getCompiler(), tmp["src"], "-o", tmp["out"]) 43 | unlink(tmp) 44 | 45 | !failed 46 | } 47 | 48 | checkForLibPthread <- function() { 49 | if (.Platform$OS.type == "windows") 50 | return(FALSE) 51 | 52 | tmp <- createTestFiles() 53 | cat("#include \n int main() { return 0; }", file = tmp["src"]) 54 | failed <- runCmd(getCompiler(), tmp["src"], "-o", tmp["out"], "-lpthread") 55 | unlink(tmp) 56 | 57 | !failed 58 | } 59 | 60 | 61 | #' Get portable linker flags for libraries building on RcppThread 62 | #' 63 | #' To be used in `Makevars` on Linux and OSX. Returns a string with 64 | #' linker flags for `pthread` and `libatomic`, if available. 65 | #' 66 | #' Use as 67 | #' `PKG_LIBS = $(R_HOME)/bin/Rscript -e 'RcppThread::LdFlags()'. 68 | #' 69 | #' @export 70 | LdFlags <- function() { 71 | flags <- "" 72 | if (checkForLibAtomic()) 73 | flags <- paste(flags, "-latomic") 74 | if (checkForLibPthread()) 75 | flags <- paste(flags, "-lpthread") 76 | cat(flags) 77 | invisible(flags) 78 | } 79 | 80 | # internal function for testing destructor of global thread pool 81 | testGlobal <- function() { 82 | .Call("testGlobalCpp") 83 | } 84 | 85 | -------------------------------------------------------------------------------- /R/RcppThread-package.R: -------------------------------------------------------------------------------- 1 | #' R-friendly C++11 threads 2 | #' 3 | #' Provides a C++11-style thread class and thread pool that can safely be 4 | #' interrupted from R. 5 | #' 6 | #' @references Nagler, T. (2021). "R-Friendly Multi-Threading in C++." 7 | #' _Journal of Statistical Software, Code Snippets_, *97*(1), 1-18. 8 | #' \doi{10.18637/jss.v097.c01}. 9 | #' 10 | #' @name RcppThread 11 | #' @docType package 12 | #' @useDynLib RcppThread, .registration=TRUE 13 | "_PACKAGE" 14 | -------------------------------------------------------------------------------- /R/detectCores.R: -------------------------------------------------------------------------------- 1 | #' Detect the Number of CPU Cores 2 | #' 3 | #' Detects the number of (logical) CPU cores. 4 | #' 5 | #' @export 6 | detectCores <- function() { 7 | .Call("detectCoresCpp") 8 | } 9 | -------------------------------------------------------------------------------- /R/inline.R: -------------------------------------------------------------------------------- 1 | inlineCxxPlugin <- function(...) { 2 | settings <- Rcpp::Rcpp.plugin.maker( 3 | include.before = "#include ", 4 | package = "RcppThread", 5 | libs = RcppThread::LdFlags() 6 | )() 7 | settings 8 | } 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RcppThread 2 | 3 | 4 | [![R build status](https://github.com/tnagler/RcppThread/workflows/R-CMD-check/badge.svg)](https://github.com/tnagler/RcppThread/actions) 5 | [![CRAN version](https://www.r-pkg.org/badges/version/RcppThread)](https://cran.r-project.org/package=RcppThread) 6 | [![CRAN downloads](https://cranlogs.r-pkg.org/badges/RcppThread)](https://cran.r-project.org/package=RcppThread) 7 | 8 | 9 | Provides R-friendly threading functionality: 10 | 11 | * thread safe versions of [Rcpp's](https://www.rcpp.org/) 12 | `checkUserInterrupt()`, `Rcout`, and `Rcerr`, 13 | * an interruptible thread class that otherwise behaves like 14 | [`std::thread`](https://en.cppreference.com/w/cpp/thread/thread), 15 | * classes for the [thread pool 16 | pattern](https://en.wikipedia.org/wiki/Thread_pool) and parallel for loops 17 | for easy and flexible parallelism, 18 | * thread safe progress tracking, 19 | * state-of-the art speed, see [benchmarks](https://github.com/tnagler/RcppThread/blob/main/benchmarks/benchmarks.md). 20 | 21 | The library is header-only, platform-independent, and only 22 | requires a 23 | [C++11-compatible compiler](https://en.cppreference.com/w/cpp/compiler_support#cpp11). 24 | 25 | ## Functionality 26 | 27 | For a detailed description of its functionality and examples, see the associated 28 | [JSS paper](https://doi.org/10.18637/jss.v097.c01) 29 | or the [API documentation](https://tnagler.github.io/RcppThread/). 30 | 31 | Since then, the following **new features** have been added: 32 | 33 | - Printing to the error stream with `Rcerr`. 34 | 35 | - Free-standing functions like `parallelFor()` now dispatch 36 | to a global thread pool that persists for the entire session. This 37 | significantly speeds up programs that repeatedly call these functions. 38 | 39 | - Faster runtimes due to lock-free work stealing queue and loops (from [quickpool](https://github.com/tnagler/quickpool)). 40 | 41 | - Option to resize a thread pool. 42 | 43 | - An R function `RcppThread::detectCores()` to determine the number of (logical) 44 | cores on your machine. 45 | 46 | - C++ classes `ProgressCounter` and `ProgressBar` for tracking progress in 47 | long-running loops. 48 | **Example usage:** 49 | ``` cpp 50 | // 20 iterations in loop, update progress every 1 sec 51 | RcppThread::ProgressBar bar(20, 1); 52 | RcppThread::parallelFor(0, 20, [&] (int i) { 53 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 54 | bar++; 55 | }); 56 | ``` 57 | **Output:** (just one line that is continuously updated) 58 | ``` 59 | ... 60 | Computing: [========================== ] 65% (~1s remaining) 61 | ... 62 | Computing: [========================================] 100% (done) 63 | ``` 64 | 65 | ## Installation 66 | 67 | Release version from CRAN: 68 | 69 | ``` r 70 | install.packages("RcppThread") 71 | ``` 72 | 73 | Latest development version from github: 74 | 75 | ``` r 76 | # install.packages("devtools") 77 | devtools::install_github("tnagler/RcppThread") 78 | ``` 79 | 80 | ## How to use it 81 | 82 | ###### with cppFunction 83 | 84 | Pass `"RcppThread"` to the `depends` argument and `"cpp11"` to the `plugins` 85 | argument. For example: 86 | ``` r 87 | Rcpp::cppFunction('void func() { /* actual code here */ }', 88 | depends = "RcppThread", plugins = "cpp11") 89 | ``` 90 | 91 | ###### with sourceCpp 92 | 93 | Add 94 | ``` cpp 95 | // [[Rcpp::plugins(cpp11)]] 96 | // [[Rcpp::depends(RcppThread)]] 97 | ``` 98 | before including any headers in your source code. 99 | 100 | ###### in another R package 101 | 102 | 1. Add the line `CXX_STD = CXX11` to the `src/Makevars(.win)` files of your package. 103 | 2. Add `RcppThread` to the `LinkingTo` field of your `DESCRIPTION` file. 104 | 105 | For optimal portability, you might also want to add 106 | ``` 107 | PKG_LIBS = `"$(R_HOME)/bin/Rscript" -e "RcppThread::LdFlags()"` 108 | ``` 109 | to your `src/Makevars` (not `.win`). This adds `-latomic`/`-lpthread` flags as 110 | necessary and available. 111 | 112 | ## Automatic override of `std::cout`, `std::cerr`, and `std::thread` 113 | 114 | There are preprocessor options to replace all occurrences of `std::cout`, `std::cerr`, and `std::thread` with calls to `RcppThread::Rcout`, `RcppThread::Rcerr`, and `RcppThread::Thread` 115 | (provided that the RcppThread headers are included first). To enable this, use 116 | 117 | ``` 118 | #define RCPPTHREAD_OVERRIDE_COUT 1 // std::cout override 119 | #define RCPPTHREAD_OVERRIDE_CERR 1 // std::cerr override 120 | #define RCPPTHREAD_OVERRIDE_THREAD 1 // std::thread override 121 | ``` 122 | before including the RcppThread headers. 123 | 124 | 125 | ## References 126 | 127 | Nagler, T. (2021). "R-Friendly Multi-Threading in C++." _Journal of Statistical 128 | Software, Code Snippets_, *97*(1), 1-18. [doi: 10.18637/jss.v097.c01](https://doi.org/10.18637/jss.v097.c01) 129 | 130 | -------------------------------------------------------------------------------- /RcppThread.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 03c39b4a-0754-48d2-864e-5e4396eea505 3 | 4 | RestoreWorkspace: No 5 | SaveWorkspace: No 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 4 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageCheckArgs: --as-cran 23 | PackageRoxygenize: rd,collate,namespace 24 | -------------------------------------------------------------------------------- /benchmarks/benchmarks.R: -------------------------------------------------------------------------------- 1 | library("tidyverse") 2 | library("Rcpp") 3 | library("RcppParallel") 4 | library("RcppThread") 5 | library("ggthemes") 6 | 7 | 8 | Rcpp::sourceCpp(here::here("benchmarks/benchmarks.cpp")) 9 | 10 | 11 | wait_for <- 5 12 | 13 | plot_df <- function(df, title = NULL) { 14 | p <- df %>% 15 | pivot_longer(-n, "call") %>% 16 | ggplot(aes(n, value, color = call)) + 17 | geom_line(size = 0.6) + 18 | expand_limits(y = 0) + 19 | labs(color = "", linetype = "") + 20 | naglr::theme_naglr(plot_title_size = 12) + 21 | theme(legend.margin = margin(1, 1, 1, 1)) + 22 | theme(legend.position = "bottom") + 23 | scale_x_log10() + 24 | ylab("speedup") + 25 | labs(title = title) 26 | print(p) 27 | } 28 | 29 | 30 | ns <- 10^(2:6) 31 | res <- benchEmpty(rev(ns), wait_for) 32 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 33 | plot_df(df, "empty jobs") 34 | ggsave(here::here("benchmarks/benchEmptyThread.pdf"), width = 7.5, height = 3) 35 | 36 | 37 | ns <- 10^(2:6) 38 | res <- benchSqrt(rev(ns), wait_for) 39 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 40 | plot_df(df, "1000x sqrt") 41 | ggsave(here::here("benchmarks/benchSqrt.pdf"), width = 7.5, height = 3) 42 | 43 | 44 | ns <- 10^(2:6) 45 | res <- benchSqrtWrite(rev(ns), wait_for) 46 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 47 | plot_df(df, "1000x sqrt modify inplace") 48 | ggsave(here::here("benchmarks/benchSqrtWrite.pdf"), width = 7.5, height = 3) 49 | 50 | ns <- 10^(2:4) 51 | res <- benchSqrtImbalanced(rev(ns), wait_for) 52 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 53 | plot_df(df, "n times sqrt (imbalanced)") 54 | ggsave(here::here("benchmarks/benchSqrtImbalanced.pdf"), width = 7.5, height = 3) 55 | 56 | ns <- 10^(2:4) 57 | res <- benchSqrtWriteImbalanced(rev(ns), wait_for) 58 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 59 | plot_df(df, "n times sqrt modify inplace (imbalanced)") 60 | ggsave(here::here("benchmarks/benchSqrtWriteImbalanced.pdf"), width = 7.5, height = 3) 61 | 62 | ns <- 10^(2:5) 63 | res <- benchKDE(rev(ns), 100, wait_for) 64 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 65 | plot_df(df, "kernel density d = 10") 66 | ggsave(here::here("benchmarks/benchKDE-10.pdf"), width = 7.5, height = 3) 67 | 68 | 69 | ns <- 10^(2:5) 70 | res <- benchKDE(rev(ns), 100, wait_for) 71 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 72 | plot_df(df, "kernel density d = 100") 73 | ggsave(here::here("benchmarks/benchKDE-100.pdf"), width = 7.5, height = 3) 74 | 75 | ns <- 10^(2:4) 76 | res <- benchKendall(rev(ns), 10, wait_for) 77 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 78 | plot_df(df, "Kendall matrix (unbalanced) d = 10") 79 | ggsave(here::here("benchmarks/benchKendall-10.pdf"), width = 7.5, height = 3) 80 | 81 | ns <- 10^(2:4) 82 | res <- benchKendall(rev(ns), 100, wait_for) 83 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 84 | plot_df(df, "Kendall matrix (unbalanced) d = 100") 85 | ggsave(here::here("benchmarks/benchKendall-100.pdf"), width = 7.5, height = 3) 86 | -------------------------------------------------------------------------------- /benchmarks/benchmarks.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "RcppThread Benchmarks" 3 | author: "Thomas Nagler" 4 | date: "2022-08-23" 5 | output: md_document 6 | --- 7 | 8 | ```{r setup, include=FALSE} 9 | knitr::opts_chunk$set(echo = TRUE) 10 | ``` 11 | 12 | Graphs show speedup over single-threaded execution (higher is better) 13 | 14 | 15 | #### Libraries 16 | ```{r} 17 | library("tidyverse") 18 | library("Rcpp") 19 | library("RcppParallel") 20 | library("RcppThread") 21 | library("ggthemes") 22 | ``` 23 | 24 | #### Utilities 25 | 26 | ```{r} 27 | Rcpp::sourceCpp(here::here("benchmarks/benchmarks.cpp")) 28 | wait_for <- 5 29 | 30 | plot_df <- function(df, title = NULL) { 31 | p <- df %>% 32 | pivot_longer(-n, "call") %>% 33 | ggplot(aes(n, value, color = call)) + 34 | geom_line(size = 0.6) + 35 | expand_limits(y = 0) + 36 | labs(color = "", linetype = "") + 37 | naglr::theme_naglr(plot_title_size = 12) + 38 | theme(legend.margin = margin(1, 1, 1, 1)) + 39 | theme(legend.position = "bottom") + 40 | scale_x_log10() + 41 | ylab("speedup") + 42 | labs(title = title) 43 | print(p) 44 | } 45 | ``` 46 | 47 | ## Benchmarks 48 | 49 | ### empty threads 50 | 51 | 52 | ```{r} 53 | ns <- 10^(2:6) 54 | res <- benchEmpty(rev(ns), wait_for) 55 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 56 | plot_df(df, "empty jobs") 57 | ggsave(here::here("benchmarks/benchEmptyThread.pdf"), width = 7.5, height = 3) 58 | ``` 59 | 60 | 61 | ### compute 1000 square roots per thread 62 | 63 | ```{r} 64 | ns <- 10^(2:6) 65 | res <- benchSqrt(rev(ns), wait_for) 66 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 67 | plot_df(df, "1000x sqrt") 68 | ggsave(here::here("benchmarks/benchSqrt.pdf"), width = 7.5, height = 3) 69 | ``` 70 | 71 | ### compute and write 1000 square roots per thread 72 | 73 | ```{r} 74 | ns <- 10^(2:6) 75 | res <- benchSqrtWrite(rev(ns), wait_for) 76 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 77 | plot_df(df, "1000x sqrt modify inplace") 78 | ggsave(here::here("benchmarks/benchSqrtWrite.pdf"), width = 7.5, height = 3) 79 | ``` 80 | 81 | 82 | ### kernel density estimation (d = 10) 83 | 84 | ```{r} 85 | ns <- 10^(2:5) 86 | res <- benchKDE(rev(ns), 100, wait_for) 87 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 88 | plot_df(df, "kernel density d = 10") 89 | ggsave(here::here("benchmarks/benchKDE-10.pdf"), width = 7.5, height = 3) 90 | ``` 91 | 92 | 93 | 94 | ### kernel density estimation (d = 100) 95 | 96 | ```{r} 97 | ns <- 10^(2:5) 98 | res <- benchKDE(rev(ns), 100, wait_for) 99 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 100 | plot_df(df, "kernel density d = 100") 101 | ggsave(here::here("benchmarks/benchKDE-100.pdf"), width = 7.5, height = 3) 102 | ``` 103 | 104 | ### Kendall matrix (d = 10, imbalanced) 105 | 106 | ```{r} 107 | ns <- 10^(2:4) 108 | res <- benchKendall(rev(ns), 10, wait_for) 109 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 110 | plot_df(df, "Kendall matrix (unbalanced) d = 10") 111 | ggsave(here::here("benchmarks/benchKendall-10.pdf"), width = 7.5, height = 3) 112 | ``` 113 | 114 | ### Kendall matrix (d = 100, imbalanced) 115 | 116 | ```{r} 117 | ns <- 10^(2:4) 118 | res <- benchKendall(rev(ns), 100, wait_for) 119 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 120 | plot_df(df, "Kendall matrix (unbalanced) d = 100") 121 | ggsave(here::here("benchmarks/benchKendall-100.pdf"), width = 7.5, height = 3) 122 | ``` 123 | -------------------------------------------------------------------------------- /benchmarks/benchmarks.cpp: -------------------------------------------------------------------------------- 1 | // [[Rcpp::plugins(cpp11)]] 2 | // [[Rcpp::plugins(openmp)]] 3 | // [[Rcpp::depends(RcppThread)]] 4 | // [[Rcpp::depends(RcppParallel)]] 5 | // [[Rcpp::depends(RcppEigen)]] 6 | // [[Rcpp::depends(wdm)]] 7 | 8 | #include 9 | #include 10 | #include 11 | // #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace bench { 23 | 24 | double 25 | time_one(const std::function& f) 26 | { 27 | auto t0 = std::chrono::steady_clock::now(); 28 | f(); 29 | auto t1 = std::chrono::steady_clock::now(); 30 | return std::chrono::duration(t1 - t0).count(); 31 | } 32 | 33 | void 34 | time_once(const std::vector>& funcs, 35 | std::vector& times) 36 | { 37 | for (size_t k = 0; k < funcs.size(); k++) { 38 | times[k] += time_one(funcs[k]); 39 | } 40 | } 41 | 42 | std::vector 43 | mark(std::vector> funcs, double min_sec = 1) 44 | { 45 | size_t min_time = 0; 46 | std::vector times(funcs.size(), 0); 47 | while (min_time < min_sec) { 48 | time_once(funcs, times); 49 | min_time = *std::max_element(times.begin(), times.end()); 50 | } 51 | return times; 52 | } 53 | 54 | } // end namespace bench 55 | 56 | class BenchMethods 57 | { 58 | size_t n_; 59 | std::function func_; 60 | 61 | public: 62 | BenchMethods(size_t n, std::function func) 63 | : n_{ n } 64 | , func_(func) 65 | {} 66 | 67 | void singleThreaded(int n) 68 | { 69 | for (size_t i = 0; i < n; ++i) 70 | func_(i); 71 | } 72 | 73 | void ThreadPool(int n) 74 | { 75 | for (size_t i = 0; i < n; ++i) 76 | RcppThread::async(func_, i); 77 | RcppThread::wait(); 78 | } 79 | 80 | void parallelFor(int n) 81 | { 82 | RcppThread::parallelFor(0, n, func_); 83 | } 84 | 85 | void OpenMP_static(int n) 86 | { 87 | #pragma omp parallel for schedule(static) 88 | for (size_t i = 0; i < n; ++i) 89 | func_(i); 90 | } 91 | 92 | void OpenMP_dynamic(int n) 93 | { 94 | #pragma omp parallel for schedule(dynamic, 1) 95 | for (size_t i = 0; i < n; ++i) 96 | func_(i); 97 | } 98 | 99 | // void RcppParallelFor(int n) 100 | // { 101 | // struct Job : public RcppParallel::Worker 102 | // { 103 | // std::function f; 104 | // void operator()(std::size_t begin, std::size_t end) 105 | // { 106 | // for (size_t i = begin; i < end; i++) 107 | // f(i); 108 | // } 109 | // } job; 110 | // job.f = func_; 111 | // RcppParallel::parallelFor(0, n, job); 112 | // } 113 | 114 | void IntelTBB(int n) 115 | { 116 | tbb::parallel_for( 117 | tbb::blocked_range(0, n), 118 | [&](tbb::blocked_range r) { 119 | for (int i = r.begin(); i < r.end(); ++i) { 120 | func_(i); 121 | } 122 | } 123 | ); 124 | 125 | } 126 | }; 127 | 128 | Rcpp::NumericVector 129 | benchMark(std::function task, size_t n, double min_sec = 10) 130 | { 131 | BenchMethods methods(n, task); 132 | auto times = bench::mark({ 133 | [&] { methods.singleThreaded(n); }, 134 | [&] { methods.IntelTBB(n); }, 135 | [&] { methods.parallelFor(n); }, 136 | [&] { methods.ThreadPool(n); }, 137 | [&] { methods.OpenMP_static(n); }, 138 | [&] { methods.OpenMP_dynamic(n); } 139 | }, 140 | min_sec); 141 | 142 | // compute speed up over single threaded 143 | const auto t0 = times[0]; 144 | for (auto& t : times) 145 | t = t0 / t; 146 | 147 | return Rcpp::wrap(times); 148 | } 149 | 150 | void set_colnames(Rcpp::NumericMatrix& times) 151 | { 152 | colnames(times) = Rcpp::CharacterVector{ 153 | "single", 154 | "Intel TBB", 155 | "RcppThread::parallelFor", "RcppThread::async", 156 | "OpenMP static", "OpenMP dynamic", 157 | }; 158 | } 159 | 160 | // [[Rcpp::export]] 161 | Rcpp::NumericMatrix 162 | benchEmpty(Rcpp::IntegerVector ns, double min_sec = 10) 163 | { 164 | Rcpp::NumericMatrix times(ns.size(), 6); 165 | for (int i = 0; i < ns.size(); i++) { 166 | times(i, Rcpp::_) = benchMark([](int i) {}, ns[i], min_sec); 167 | } 168 | 169 | set_colnames(times); 170 | 171 | return times; 172 | } 173 | 174 | // [[Rcpp::export]] 175 | Rcpp::NumericMatrix 176 | benchSqrt(Rcpp::IntegerVector ns, double min_sec = 10) 177 | { 178 | auto op = [](double x) { 179 | double xx = x; 180 | for (int j = 0; j != 1000; j++) { 181 | xx = std::sqrt(xx); 182 | } 183 | }; 184 | Rcpp::NumericMatrix times(ns.size(), 6); 185 | for (int i = 0; i < ns.size(); i++) { 186 | std::vector x(ns[i], 3.14); 187 | times(i, Rcpp::_) = benchMark([&](int i) { op(x[i]); }, ns[i], min_sec); 188 | } 189 | 190 | set_colnames(times); 191 | 192 | return times; 193 | } 194 | 195 | // [[Rcpp::export]] 196 | Rcpp::NumericMatrix 197 | benchSqrtWrite(std::vector ns, double min_sec = 10) 198 | { 199 | auto op = [](double& x) { 200 | for (int j = 0; j != 1000; j++) { 201 | x = std::sqrt(x); 202 | } 203 | }; 204 | Rcpp::NumericMatrix times(ns.size(), 6); 205 | for (int i = 0; i < ns.size(); i++) { 206 | std::vector x(ns[i], 3.14); 207 | times(i, Rcpp::_) = benchMark([&](int i) { op(x[i]); }, ns[i], min_sec); 208 | } 209 | 210 | set_colnames(times); 211 | 212 | return times; 213 | } 214 | 215 | 216 | // [[Rcpp::export]] 217 | Rcpp::NumericMatrix 218 | benchKDE(std::vector ns, size_t d, double min_sec = 10) 219 | { 220 | using namespace Eigen; 221 | auto kernel = [](const VectorXd& x) { 222 | return (-x.array().pow(2) / 2).exp() / std::sqrt(3.14159 * 2); 223 | }; 224 | auto kde = [=](const VectorXd& x) { 225 | double n = x.size(); 226 | double sd = std::sqrt((x.array() - x.mean()).square().sum() / (n - 1)); 227 | double bw = 1.06 * sd * std::pow(n, -0.2); 228 | VectorXd grid = VectorXd::LinSpaced(500, -1, 1); 229 | VectorXd fhat(grid.size()); 230 | for (size_t i = 0; i < grid.size(); ++i) { 231 | fhat(i) = kernel((x.array() - grid(i)) / bw).mean() / bw; 232 | } 233 | return fhat; 234 | }; 235 | 236 | Rcpp::NumericMatrix times(ns.size(), 6); 237 | for (int i = 0; i < ns.size(); i++) { 238 | MatrixXd x = MatrixXd(ns[i], d).setRandom(); 239 | times(i, Rcpp::_) = 240 | benchMark([&](int i) { kde(x.col(i)); }, d, min_sec); 241 | } 242 | 243 | set_colnames(times); 244 | 245 | return times; 246 | } 247 | 248 | // [[Rcpp::export]] 249 | Rcpp::NumericMatrix 250 | benchKendall(std::vector ns, size_t d, double min_sec = 10) 251 | { 252 | using namespace Eigen; 253 | Rcpp::NumericMatrix times(ns.size(), 6); 254 | for (int i = 0; i < ns.size(); i++) { 255 | MatrixXd x = MatrixXd(ns[i], d).setRandom(); 256 | MatrixXd res = MatrixXd::Identity(d, d); 257 | auto ktau = [&](size_t i, size_t j) { 258 | res(i, j) = wdm::wdm(x.col(i), x.col(j), "kendall"); 259 | res(j, i) = res(i, j); 260 | }; 261 | 262 | auto task = [&](int i) { 263 | for (size_t j = i + 1; j < d; ++j) { 264 | ktau(i, j); 265 | } 266 | }; 267 | 268 | times(i, Rcpp::_) = benchMark(task, ns[i], min_sec); 269 | } 270 | 271 | set_colnames(times); 272 | 273 | return times; 274 | } 275 | -------------------------------------------------------------------------------- /benchmarks/benchmarks.md: -------------------------------------------------------------------------------- 1 | Graphs show speedup over single-threaded execution (higher is better) 2 | 3 | #### Libraries 4 | 5 | library("tidyverse") 6 | 7 | ## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ── 8 | 9 | ## ✔ ggplot2 3.3.6 ✔ purrr 0.3.4 10 | ## ✔ tibble 3.1.8 ✔ dplyr 1.0.9 11 | ## ✔ tidyr 1.2.0 ✔ stringr 1.4.0 12 | ## ✔ readr 2.1.2 ✔ forcats 0.5.1 13 | 14 | ## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ── 15 | ## ✖ dplyr::filter() masks stats::filter() 16 | ## ✖ dplyr::lag() masks stats::lag() 17 | 18 | library("Rcpp") 19 | library("RcppParallel") 20 | 21 | ## 22 | ## Attaching package: 'RcppParallel' 23 | 24 | ## The following object is masked from 'package:Rcpp': 25 | ## 26 | ## LdFlags 27 | 28 | library("RcppThread") 29 | 30 | ## 31 | ## Attaching package: 'RcppThread' 32 | 33 | ## The following object is masked from 'package:RcppParallel': 34 | ## 35 | ## LdFlags 36 | 37 | ## The following object is masked from 'package:Rcpp': 38 | ## 39 | ## LdFlags 40 | 41 | library("ggthemes") 42 | 43 | #### Utilities 44 | 45 | Rcpp::sourceCpp(here::here("benchmarks/benchmarks.cpp")) 46 | wait_for <- 5 47 | 48 | plot_df <- function(df, title = NULL) { 49 | p <- df %>% 50 | pivot_longer(-n, "call") %>% 51 | ggplot(aes(n, value, color = call)) + 52 | geom_line(size = 0.6) + 53 | expand_limits(y = 0) + 54 | labs(color = "", linetype = "") + 55 | naglr::theme_naglr(plot_title_size = 12) + 56 | theme(legend.margin = margin(1, 1, 1, 1)) + 57 | theme(legend.position = "bottom") + 58 | scale_x_log10() + 59 | ylab("speedup") + 60 | labs(title = title) 61 | print(p) 62 | } 63 | 64 | ## Benchmarks 65 | 66 | ### empty threads 67 | 68 | ns <- 10^(2:6) 69 | res <- benchEmpty(rev(ns), wait_for) 70 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 71 | plot_df(df, "empty jobs") 72 | 73 | ![](benchmarks_files/figure-markdown_strict/unnamed-chunk-3-1.png) 74 | 75 | ggsave(here::here("benchmarks/benchEmptyThread.pdf"), width = 7.5, height = 3) 76 | 77 | ### compute 1000 square roots per thread 78 | 79 | ns <- 10^(2:6) 80 | res <- benchSqrt(rev(ns), wait_for) 81 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 82 | plot_df(df, "1000x sqrt") 83 | 84 | ![](benchmarks_files/figure-markdown_strict/unnamed-chunk-4-1.png) 85 | 86 | ggsave(here::here("benchmarks/benchSqrt.pdf"), width = 7.5, height = 3) 87 | 88 | ### compute and write 1000 square roots per thread 89 | 90 | ns <- 10^(2:6) 91 | res <- benchSqrtWrite(rev(ns), wait_for) 92 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 93 | plot_df(df, "1000x sqrt modify inplace") 94 | 95 | ![](benchmarks_files/figure-markdown_strict/unnamed-chunk-5-1.png) 96 | 97 | ggsave(here::here("benchmarks/benchSqrtWrite.pdf"), width = 7.5, height = 3) 98 | 99 | ### kernel density estimation (d = 10) 100 | 101 | ns <- 10^(2:5) 102 | res <- benchKDE(rev(ns), 100, wait_for) 103 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 104 | plot_df(df, "kernel density d = 10") 105 | 106 | ![](benchmarks_files/figure-markdown_strict/unnamed-chunk-6-1.png) 107 | 108 | ggsave(here::here("benchmarks/benchKDE-10.pdf"), width = 7.5, height = 3) 109 | 110 | ### kernel density estimation (d = 100) 111 | 112 | ns <- 10^(2:5) 113 | res <- benchKDE(rev(ns), 100, wait_for) 114 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 115 | plot_df(df, "kernel density d = 100") 116 | 117 | ![](benchmarks_files/figure-markdown_strict/unnamed-chunk-7-1.png) 118 | 119 | ggsave(here::here("benchmarks/benchKDE-100.pdf"), width = 7.5, height = 3) 120 | 121 | ### Kendall matrix (d = 10, imbalanced) 122 | 123 | ns <- 10^(2:4) 124 | res <- benchKendall(rev(ns), 10, wait_for) 125 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 126 | plot_df(df, "Kendall matrix (unbalanced) d = 10") 127 | 128 | ![](benchmarks_files/figure-markdown_strict/unnamed-chunk-8-1.png) 129 | 130 | ggsave(here::here("benchmarks/benchKendall-10.pdf"), width = 7.5, height = 3) 131 | 132 | ### Kendall matrix (d = 100, imbalanced) 133 | 134 | ns <- 10^(2:4) 135 | res <- benchKendall(rev(ns), 100, wait_for) 136 | df <- cbind(data.frame(n = rev(ns)), as.data.frame(res[, -1])) 137 | plot_df(df, "Kendall matrix (unbalanced) d = 100") 138 | 139 | ![](benchmarks_files/figure-markdown_strict/unnamed-chunk-9-1.png) 140 | 141 | ggsave(here::here("benchmarks/benchKendall-100.pdf"), width = 7.5, height = 3) 142 | -------------------------------------------------------------------------------- /benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-3-1.png -------------------------------------------------------------------------------- /benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-4-1.png -------------------------------------------------------------------------------- /benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-5-1.png -------------------------------------------------------------------------------- /benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-6-1.png -------------------------------------------------------------------------------- /benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-7-1.png -------------------------------------------------------------------------------- /benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-8-1.png -------------------------------------------------------------------------------- /benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-9-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/benchmarks/benchmarks_files/figure-markdown_strict/unnamed-chunk-9-1.png -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | Also includes a fix for the @doctype isseu (now documenting "_PACKAGE" instead of NULL). 2 | 3 | ## Test environments 4 | 5 | * ubuntu 22.04 (devel, release, old-rel) 6 | * macOS X (release) 7 | * Windows Server 2019 (release) 8 | * CRAN win builder (devel) 9 | 10 | ## Check status summary 11 | 12 | NOTE OK 13 | Source packages 0 1 14 | Reverse depends 19 3 15 | -------------------------------------------------------------------------------- /docs/Doxyfile-mcss: -------------------------------------------------------------------------------- 1 | @INCLUDE = Doxyfile 2 | GENERATE_HTML = NO 3 | GENERATE_XML = YES 4 | XML_PROGRAMLISTING = NO 5 | 6 | -------------------------------------------------------------------------------- /docs/annotated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

Classes

47 |
    48 |
  • 49 | namespace RcppThread RcppThread functionality 50 |
      51 |
    • class ProgressBar A progress bar showing progress in percent.
    • 52 |
    • class ProgressCounter A counter showing progress in percent.
    • 53 |
    • class ProgressPrinter Abstract class for printing progress.
    • 54 |
    • class RErrPrinter Safely printing to the R console from threaded code.
    • 55 |
    • class RMonitor Singleton class for tracking threads and safe communication.
    • 56 |
    • class RPrinter Safely printing to the R console from threaded code.
    • 57 |
    • class Thread R-friendly version of std::thread.
    • 58 |
    • class ThreadPool Implemenation of the thread pool pattern based on Thread.
    • 59 |
    • class UserInterruptException exception class for user interruptions.
    • 60 |
    61 |
  • 62 |
63 | 79 |
80 |
81 |
82 |
83 | 117 | 118 | 119 |
128 | 129 | 130 | -------------------------------------------------------------------------------- /docs/classRcppThread_1_1ProgressBar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread::ProgressBar class | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

47 | RcppThread::ProgressBar class 48 |

49 |

A progress bar showing progress in percent.

50 | 63 |

Prints to the R console in a thread safe manner using Rcout (an instance of RcppThread::Rprinter).

64 |
65 |

Base classes

66 |
67 |
68 | class ProgressPrinter 69 |
70 |
Abstract class for printing progress.
71 |
72 |
73 |
74 |

Constructors, destructors, conversion operators

75 |
76 |
77 | ProgressBar(size_t numIt, 78 | size_t printEvery) 79 |
80 |
81 |
82 |
83 |
84 |

Private functions

85 |
86 |
87 | void printProgress() virtual 88 |
89 |
90 |
91 |
92 |
93 |

Function documentation

94 |
95 |

96 | RcppThread::ProgressBar::ProgressBar(size_t numIt, 97 | size_t printEvery) 98 |

99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
Parameters
numIttotal number of iterations.
printEveryhow regularly to print updates (in seconds).
114 |

constructs a progress bar.

115 |
116 |
117 |

118 | void RcppThread::ProgressBar::printProgress() virtual private 119 |

120 |

prints a progress bar to the R console (+ an estimate of remaining time).

121 |
122 |
123 |
124 |
125 |
126 |
127 | 161 | 162 | 163 |
172 | 173 | 174 | -------------------------------------------------------------------------------- /docs/classRcppThread_1_1Thread.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread::Thread class | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

47 | RcppThread::Thread class 48 |

49 |

R-friendly version of std::thread.

50 | 61 |

Instances of class Thread behave just like instances of std::thread, see http://en.cppreference.com/w/cpp/thread/thread for methods and examples. There is one difference exception: Whenever other threads are doing some work, the main thread periodically synchronizes with R. When the user interrupts a threaded computation, any thread will stop as soon as it encounters a checkUserInterrupt().

62 |
63 |

Public functions

64 |
65 |
66 | void join() 67 |
68 |
69 |
70 |
71 |
72 |

Function documentation

73 |
74 |

75 | void RcppThread::Thread::join() 76 |

77 |

checks for interruptions and messages every 0.25 seconds and after computations have finished.

78 |
79 |
80 |
81 |
82 |
83 |
84 | 118 | 119 | 120 |
129 | 130 | 131 | -------------------------------------------------------------------------------- /docs/classRcppThread_1_1UserInterruptException.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread::UserInterruptException class | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

47 | RcppThread::UserInterruptException class 48 |

49 |

exception class for user interruptions.

50 |
51 |
52 |
53 |
54 | 88 | 89 | 90 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/favicon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/docs/favicon-dark.png -------------------------------------------------------------------------------- /docs/files.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

Files

47 |
    48 |
49 | 65 |
66 |
67 |
68 |
69 | 103 | 104 | 105 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread: R-friendly threading in C++ | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

47 | RcppThread: R-friendly threading in C++ 48 |

49 |

This is the API documentation for the RcppThread C++ library. For a more high-level overview see the vignette and README.

License

The MIT License (MIT)

Copyright © 2022 Thomas Nagler

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

50 |
51 |
52 |
53 |
54 | 88 | 89 | 90 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/mainpage_8h.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | /home/n5/dev/r/RcppThread/inst/include/mainpage.h file | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
54 | 88 | 89 | 90 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

Modules

47 |
    48 |
49 | 65 |
66 |
67 |
68 |
69 | 103 | 104 | 105 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/namespaces.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

Namespaces

47 |
    48 |
  • namespace RcppThread RcppThread functionality
  • 49 |
50 | 66 |
67 |
68 |
69 |
70 | 104 | 105 | 106 |
115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/overrides.dox: -------------------------------------------------------------------------------- 1 | /** @page overrides Automatic overrides 2 | 3 | 4 | @section override-sec Automatic override of std::cout and std::thread 5 | 6 | There are preprocessor options to replace all occurrences of `std::cout`, `std::cerr`, and `std::thread` with calls to `RcppThread::Rcout`, `RcppThread::Rcerr`, and `RcppThread::Thread` 7 | (provided that the RcppThread headers are included first). To enable this, use 8 | 9 | ``` 10 | #define RCPPTHREAD_OVERRIDE_COUT 1 // std::cout override 11 | #define RCPPTHREAD_OVERRIDE_CERR 1 // std::cerr override 12 | #define RCPPTHREAD_OVERRIDE_THREAD 1 // std::thread override 13 | ``` 14 | before including the RcppThread headers. 15 | 16 | */ 17 | -------------------------------------------------------------------------------- /docs/overrides.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Automatic overrides | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

47 | Automatic overrides 48 |

49 |

Automatic override of std::cout and std::thread

There are preprocessor options to replace all occurrences of std::cout, std::cerr, and std::thread with calls to RcppThread::Rcout, RcppThread::Rcerr, and RcppThread::Thread (provided that the RcppThread headers are included first). To enable this, use

#define RCPPTHREAD_OVERRIDE_COUT 1    // std::cout override
 50 | #define RCPPTHREAD_OVERRIDE_CERR 1    // std::cerr override
 51 | #define RCPPTHREAD_OVERRIDE_THREAD 1  // std::thread override

before including the RcppThread headers.

52 |
53 |
54 |
55 |
56 | 90 | 91 | 92 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/pages.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RcppThread R-friendly threading in C++ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
42 |
43 |
44 |
45 |
46 |

Pages

47 | 50 | 66 |
67 |
68 |
69 |
70 | 104 | 105 | 106 |
115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/xml/Progress_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Progress.hpp 5 | RcppThread/Rcout.hpp 6 | atomic 7 | chrono 8 | sstream 9 | cmath 10 | /home/n5/dev/r/RcppThread/inst/include/RcppThread.h 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | RcppThread::ProgressPrinter 38 | RcppThread::ProgressCounter 39 | RcppThread::ProgressBar 40 | RcppThread 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/xml/RMonitor_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RMonitor.hpp 5 | Rinternals.h 6 | R.h 7 | thread 8 | mutex 9 | atomic 10 | stdexcept 11 | sstream 12 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/Rcerr.hpp 13 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/Rcout.hpp 14 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/Thread.hpp 15 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/ThreadPool.hpp 16 | /home/n5/dev/r/RcppThread/inst/include/RcppThread.h 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | RcppThread::UserInterruptException 59 | RcppThread::RMonitor 60 | RcppThread 61 | 62 | 63 | R_NO_REMAP 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/xml/Rcerr_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rcerr.hpp 5 | ostream 6 | RcppThread/RMonitor.hpp 7 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/Thread.hpp 8 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/ThreadPool.hpp 9 | /home/n5/dev/r/RcppThread/inst/include/RcppThread.h 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | RcppThread::RErrPrinter 22 | RcppThread 23 | 24 | 25 | RCPPTHREAD_OVERRIDE_CERR 26 | 0 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/xml/Rcout_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rcout.hpp 5 | ostream 6 | RcppThread/RMonitor.hpp 7 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/Progress.hpp 8 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/Thread.hpp 9 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/ThreadPool.hpp 10 | /home/n5/dev/r/RcppThread/inst/include/RcppThread.h 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | RcppThread::RPrinter 23 | RcppThread 24 | 25 | 26 | RCPPTHREAD_OVERRIDE_COUT 27 | 0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/xml/RcppThread_8h.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RcppThread.h 5 | RcppThread/RMonitor.hpp 6 | RcppThread/Rcout.hpp 7 | RcppThread/Rcerr.hpp 8 | RcppThread/Thread.hpp 9 | RcppThread/ThreadPool.hpp 10 | RcppThread/parallelFor.hpp 11 | RcppThread/Progress.hpp 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/xml/ThreadPool_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ThreadPool.hpp 5 | RcppThread/RMonitor.hpp 6 | RcppThread/Rcerr.hpp 7 | RcppThread/Rcout.hpp 8 | RcppThread/quickpool.hpp 9 | atomic 10 | condition_variable 11 | functional 12 | future 13 | memory 14 | mutex 15 | thread 16 | vector 17 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/parallelFor.hpp 18 | /home/n5/dev/r/RcppThread/inst/include/RcppThread.h 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | RcppThread::ThreadPool 66 | RcppThread 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/xml/Thread_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Thread.hpp 5 | RcppThread/RMonitor.hpp 6 | RcppThread/Rcout.hpp 7 | RcppThread/Rcerr.hpp 8 | thread 9 | future 10 | /home/n5/dev/r/RcppThread/inst/include/RcppThread.h 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | RcppThread::Thread 28 | RcppThread 29 | 30 | 31 | RCPPTHREAD_OVERRIDE_THREAD 32 | 0 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/xml/classRcppThread_1_1RErrPrinter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RcppThread::RErrPrinter 5 | Rcerr.hpp 6 | 7 | 8 | 9 | 10 | class T 11 | 12 | 13 | RErrPrinter & 14 | RErrPrinter& RcppThread::RErrPrinter::operator<< 15 | (T &object) 16 | operator<< 17 | 18 | T & 19 | object 20 | 21 | 22 | 23 | 24 | prints object to R error stream íf called from main thread; otherwise adds a printable version of object to a buffer for deferred printing. 25 | 26 | object 27 | 28 | 29 | a string (or coercible object) to print. 30 | 31 | 32 | 33 | Declared as a friend in RMonitor. 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | class T 43 | 44 | 45 | RErrPrinter & 46 | RErrPrinter& RcppThread::RErrPrinter::operator<< 47 | (const T &object) 48 | operator<< 49 | 50 | const T & 51 | object 52 | 53 | 54 | 55 | 56 | prints object to R error stream íf called from main thread; otherwise adds a printable version of object to a buffer for deferred printing. 57 | 58 | object 59 | 60 | 61 | a string (or coercible object) to print. 62 | 63 | 64 | 65 | Declared as a friend in RMonitor. 66 | 67 | 68 | 69 | 70 | 71 | 72 | RErrPrinter & 73 | RErrPrinter& RcppThread::RErrPrinter::operator<< 74 | (std::ostream &(*object)(std::ostream &)) 75 | operator<< 76 | 77 | std::ostream &(*)(std::ostream &) 78 | object 79 | 80 | 81 | 82 | 83 | prints object to R error stream íf called from main thread; otherwise adds a printable version of object to a buffer for deferred printing. 84 | 85 | object 86 | 87 | 88 | a string (or coercible object) to print. 89 | 90 | 91 | 92 | Declared as a friend in RMonitor. 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Safely printing to the R console from threaded code. 101 | 102 | 103 | 104 | 105 | 106 | RcppThread::RErrPrinteroperator<< 107 | RcppThread::RErrPrinteroperator<< 108 | RcppThread::RErrPrinteroperator<< 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /docs/xml/classRcppThread_1_1RPrinter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RcppThread::RPrinter 5 | Rcout.hpp 6 | 7 | 8 | 9 | 10 | class T 11 | 12 | 13 | RPrinter & 14 | RPrinter& RcppThread::RPrinter::operator<< 15 | (T &object) 16 | operator<< 17 | 18 | T & 19 | object 20 | 21 | 22 | 23 | 24 | prints object to R console íf called from main thread; otherwise adds a printable version of object to a buffer for deferred printing. 25 | 26 | object 27 | 28 | 29 | a string (or coercible object) to print. 30 | 31 | 32 | 33 | Declared as a friend in RMonitor. 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | class T 43 | 44 | 45 | RPrinter & 46 | RPrinter& RcppThread::RPrinter::operator<< 47 | (const T &object) 48 | operator<< 49 | 50 | const T & 51 | object 52 | 53 | 54 | 55 | 56 | prints object to R console íf called from main thread; otherwise adds a printable version of object to a buffer for deferred printing. 57 | 58 | object 59 | 60 | 61 | a string (or coercible object) to print. 62 | 63 | 64 | 65 | Declared as a friend in RMonitor. 66 | 67 | 68 | 69 | 70 | 71 | 72 | RPrinter & 73 | RPrinter& RcppThread::RPrinter::operator<< 74 | (std::ostream &(*object)(std::ostream &)) 75 | operator<< 76 | 77 | std::ostream &(*)(std::ostream &) 78 | object 79 | 80 | 81 | 82 | 83 | prints object to R console íf called from main thread; otherwise adds a printable version of object to a buffer for deferred printing. 84 | 85 | object 86 | 87 | 88 | a string (or coercible object) to print. 89 | 90 | 91 | 92 | Declared as a friend in RMonitor. 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Safely printing to the R console from threaded code. 101 | 102 | 103 | 104 | 105 | 106 | RcppThread::RPrinteroperator<< 107 | RcppThread::RPrinteroperator<< 108 | RcppThread::RPrinteroperator<< 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /docs/xml/classRcppThread_1_1UserInterruptException.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RcppThread::UserInterruptException 5 | std::exception 6 | RMonitor.hpp 7 | 8 | 9 | const char * 10 | const char* RcppThread::UserInterruptException::what 11 | () const 12 | what 13 | throw () 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | exception class for user interruptions. 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | RcppThread::UserInterruptExceptionwhat 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/xml/combine.xslt: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/xml/dir_2b5d1a398a9eec011391d543f21db223.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /home/n5/dev/r/RcppThread/inst/include 5 | /home/n5/dev/r/RcppThread/inst/include/RcppThread 6 | mainpage.h 7 | RcppThread.h 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/xml/dir_920884c6b3b2f0be94e27d51660adee5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /home/n5/dev/r/RcppThread/inst 5 | /home/n5/dev/r/RcppThread/inst/include 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/xml/dir_b2386a6e32c05a6a1e7925d7c95f2160.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /home/n5/dev/r/RcppThread/inst/include/RcppThread 5 | parallelFor.hpp 6 | Progress.hpp 7 | quickpool.hpp 8 | Rcerr.hpp 9 | Rcout.hpp 10 | RMonitor.hpp 11 | Thread.hpp 12 | ThreadPool.hpp 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/xml/index.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /docs/xml/indexpage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index 5 | RcppThread: R-friendly threading in C++ 6 | 7 | 8 | 9 | Thomas Nagler 10 | 11 | This is the API documentation for the RcppThread C++ library. For a more high-level overview see the vignette and README. 12 | 13 | License 14 | The MIT License (MIT) 15 | Copyright © 2022 Thomas Nagler 16 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/xml/mainpage_8h.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mainpage.h 5 | 6 | api_docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/xml/overrides.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | overrides 5 | Automatic overrides 6 | 7 | 8 | 9 | 10 | Automatic override of std::cout and std::thread 11 | There are preprocessor options to replace all occurrences of std::cout, std::cerr, and std::thread with calls to RcppThread::Rcout, RcppThread::Rcerr, and RcppThread::Thread (provided that the RcppThread headers are included first). To enable this, use 12 | #defineRCPPTHREAD_OVERRIDE_COUT1//std::coutoverride 13 | #defineRCPPTHREAD_OVERRIDE_CERR1//std::cerroverride 14 | #defineRCPPTHREAD_OVERRIDE_THREAD1//std::threadoverride 15 | before including the RcppThread headers. 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/xml/overrides_8dox.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | overrides.dox 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/xml/parallelFor_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | parallelFor.hpp 5 | RcppThread/ThreadPool.hpp 6 | algorithm 7 | /home/n5/dev/r/RcppThread/inst/include/RcppThread.h 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | RcppThread 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/xml/quickpool_8hpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | quickpool.hpp 5 | algorithm 6 | atomic 7 | condition_variable 8 | exception 9 | functional 10 | future 11 | memory 12 | mutex 13 | numeric 14 | thread 15 | vector 16 | /home/n5/dev/r/RcppThread/inst/include/RcppThread/ThreadPool.hpp 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/xml/xml.xsd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | bibentry(bibtype = "Article", 2 | title = "{R}-Friendly Multi-Threading in {C++}", 3 | author = person(given = "Thomas", 4 | family = "Nagler", 5 | email = "mail@tnagler.com"), 6 | journal = "Journal of Statistical Software, Code Snippets", 7 | year = "2021", 8 | volume = "97", 9 | number = "1", 10 | pages = "1--18", 11 | doi = "10.18637/jss.v097.c01", 12 | 13 | header = "To cite RcppThread in publications use:" 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /inst/include/RcppThread.h: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | #include "RcppThread/RMonitor.hpp" 10 | #include "RcppThread/Rcout.hpp" 11 | #include "RcppThread/Rcerr.hpp" 12 | #include "RcppThread/Thread.hpp" 13 | #include "RcppThread/ThreadPool.hpp" 14 | #include "RcppThread/parallelFor.hpp" 15 | #include "RcppThread/Progress.hpp" 16 | #include "RcppThread/SafeTypes.hpp" 17 | -------------------------------------------------------------------------------- /inst/include/RcppThread/Progress.hpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | #include "RcppThread/Rcout.hpp" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace RcppThread { 16 | 17 | //! @brief Abstract class for printing progress. 18 | //! 19 | //! This class contains most of the logic for tracking progress in a parallel 20 | //! loop. Child classes must define a method `void printProgress()` that is 21 | //! called whenever an update is required. 22 | class ProgressPrinter { 23 | public: 24 | //! Constructor for abstract class `ProgressPrinter`. 25 | //! @param numIt total number of iterations. 26 | //! @param printEvery how regularly to print updates (in seconds). 27 | ProgressPrinter(size_t numIt, size_t printEvery = 1) 28 | : numIt_(numIt) 29 | , printEvery_(printEvery) 30 | , startTime_(std::chrono::steady_clock::now()) 31 | {} 32 | 33 | //! prints progress whenever an update is necessary. 34 | virtual void printProgress() = 0; 35 | 36 | //! pre-increment operator 37 | size_t operator++ () { 38 | size_t it = it_++; 39 | if (needsPrint()) 40 | printProgress(); 41 | return it + 1; 42 | } 43 | 44 | //! post-increment operator 45 | size_t operator++ (int) { 46 | size_t it = it_++; 47 | if (needsPrint()) 48 | printProgress(); 49 | return it; 50 | } 51 | 52 | protected: 53 | //! checks whether it's time for an update. 54 | bool needsPrint() { 55 | using namespace std::chrono; 56 | auto passed = duration(steady_clock::now() - startTime_).count(); 57 | bool needsUpdate = (passed / printEvery_ > numUpdates_ + 1); 58 | needsUpdate = needsUpdate || (it_ == numIt_); // always print when done 59 | if (needsUpdate) 60 | numUpdates_++; 61 | return needsUpdate; 62 | } 63 | 64 | //! estimates the remaining time in seconds. 65 | size_t remainingSecs() { 66 | using namespace std::chrono; 67 | auto diff = duration(steady_clock::now() - startTime_).count(); 68 | auto remaining = (numIt_ - it_) * diff / it_; 69 | return static_cast(remaining); 70 | } 71 | 72 | //! prints either remaining time or that the computation is done. 73 | std::string progressString() { 74 | std::ostringstream msg; 75 | if (it_ == numIt_) { 76 | msg << "100% (done) \n"; 77 | } else { 78 | double pct = std::round(it_ * 100.0 / numIt_); 79 | msg << pct << "% (~" << formatTime(remainingSecs()) << " remaining) "; 80 | } 81 | return msg.str(); 82 | } 83 | 84 | //! formats time into {days}d{hours}h{minutes}m{seconds}s. 85 | //! @param secs in seconds. 86 | std::string formatTime(size_t secs) { 87 | std::ostringstream msg; 88 | constexpr size_t minute = 60; 89 | constexpr size_t hour = 60 * minute; 90 | constexpr size_t day = 24 * hour; 91 | int numUnits = 0; 92 | if (secs / day > 0) { 93 | msg << secs / day << "d"; 94 | secs = secs % day; 95 | numUnits++; 96 | } 97 | if (secs / hour > 0) { 98 | msg << secs / hour << "h"; 99 | secs = secs % hour; 100 | numUnits++; 101 | } 102 | if ((secs / minute > 0) && (numUnits < 2)) { 103 | msg << secs / minute << "m"; 104 | secs = secs % minute; 105 | numUnits++; 106 | } 107 | if (numUnits < 2) 108 | msg << secs << "s"; 109 | return msg.str(); 110 | } 111 | 112 | std::atomic_size_t it_{0}; //!< iteration counter 113 | std::atomic_size_t numUpdates_{0}; //!< counter for the number of updates 114 | std::atomic_bool isDone_{false}; //!< flag indicating end of iterations 115 | size_t numIt_; //!< total number of iterations 116 | size_t printEvery_; //!< update frequency 117 | //! time at start 118 | std::chrono::time_point startTime_; 119 | }; 120 | 121 | 122 | //! @brief A counter showing progress in percent. 123 | //! 124 | //! Prints to the R console in a thread safe manner using `Rcout` 125 | //! (an instance of `RcppThread::Rprinter`). 126 | class ProgressCounter : public ProgressPrinter { 127 | 128 | public: 129 | //! constructs a progress counter. 130 | //! @param numIt total number of iterations. 131 | //! @param printEvery how regularly to print updates (in seconds). 132 | //! @param custom_msg (optional) a custom message to be printed before the 133 | //! progress count; default is `"Computing: "`. 134 | ProgressCounter(size_t numIt, size_t printEvery, std::string custom_msg = "Computing: ") : 135 | ProgressPrinter(numIt, printEvery), cmsg(custom_msg) 136 | {} 137 | 138 | private: 139 | std::string cmsg; 140 | //! prints progress in percent to the R console (+ an estimate of remaining 141 | //! time). 142 | void printProgress() { 143 | if (isDone_) 144 | return; 145 | if (it_ == numIt_) 146 | isDone_ = true; 147 | std::ostringstream msg; 148 | msg << "\r" << cmsg << progressString(); 149 | Rcout << msg.str(); 150 | } 151 | }; 152 | 153 | //! @brief A progress bar showing progress in percent. 154 | //! 155 | //! Prints to the R console in a thread safe manner using `Rcout` 156 | //! (an instance of `RcppThread::Rprinter`). 157 | class ProgressBar : public ProgressPrinter { 158 | 159 | public: 160 | //! constructs a progress bar. 161 | //! @param numIt total number of iterations. 162 | //! @param printEvery how regularly to print updates (in seconds). 163 | ProgressBar(size_t numIt, size_t printEvery) : 164 | ProgressPrinter(numIt, printEvery) 165 | {} 166 | 167 | private: 168 | //! prints a progress bar to the R console (+ an estimate of remaining 169 | //! time). 170 | void printProgress() { 171 | if (isDone_) 172 | return; 173 | if (it_ == numIt_) 174 | isDone_ = true; 175 | double pct = std::round(it_ * 100.0 / numIt_); 176 | std::ostringstream msg; 177 | msg << "\rComputing: " << makeBar(pct) << progressString(); 178 | Rcout << msg.str(); 179 | } 180 | 181 | //! constructs the progress bar. 182 | //! @param pct progress in percent. 183 | //! @param numBars bar is split into `numBars` units. 184 | std::string makeBar(size_t pct, size_t numBars = 40) { 185 | std::ostringstream msg; 186 | msg << "["; 187 | size_t i = 0; 188 | for (; i < pct / 100.0 * numBars; i++) 189 | msg << "="; 190 | for (; i < numBars; i++) 191 | msg << " "; 192 | msg << "] "; 193 | return msg.str(); 194 | } 195 | }; 196 | 197 | 198 | } 199 | -------------------------------------------------------------------------------- /inst/include/RcppThread/RMonitor.hpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | // R API 10 | #ifndef R_NO_REMAP 11 | #define R_NO_REMAP 12 | #endif 13 | #include "Rinternals.h" 14 | #include "R.h" 15 | 16 | // for tracking threads 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace RcppThread { 24 | 25 | //! global variable holding id of main thread. 26 | static std::thread::id mainThreadID = std::this_thread::get_id(); 27 | 28 | //! exception class for user interruptions. 29 | class UserInterruptException : public std::exception { 30 | const char* what() const throw () 31 | { 32 | return "C++ call interrupted by the user."; 33 | } 34 | }; 35 | 36 | //! Singleton class for tracking threads and safe communication. 37 | class RMonitor { 38 | // user-facing functionality must be friends, so they can access 39 | // protected members of RMonitor. 40 | friend class RPrinter; 41 | friend class RErrPrinter; 42 | friend void checkUserInterrupt(bool condition); 43 | friend bool isInterrupted(bool condition); 44 | 45 | public: 46 | //! copy constructor (forbidden) 47 | RMonitor(RMonitor const&) = delete; 48 | //! move constructor (forbidden) 49 | RMonitor(RMonitor&&) = delete; 50 | //! copy assignment (forbidden) 51 | RMonitor& operator=(RMonitor const&) = delete; 52 | //! move assignment (forbidden) 53 | RMonitor& operator=(RMonitor &&) = delete; 54 | 55 | //! constructs the instance when called for the first time, returns ref 56 | //! to instance. 57 | static RMonitor& instance() 58 | { 59 | static RMonitor instance_; 60 | return instance_; 61 | } 62 | 63 | protected: 64 | //! returns `true` only when called from main thread. 65 | bool calledFromMainThread() 66 | { 67 | return (std::this_thread::get_id() == mainThreadID); 68 | } 69 | 70 | //! checks for user interruptions, but only if called from main thread. 71 | void safelycheckUserInterrupt() 72 | { 73 | if ( safelyIsInterrupted() ) { 74 | if ( calledFromMainThread() ) 75 | isInterrupted_ = false; // reset for next call 76 | throw UserInterruptException(); 77 | } 78 | } 79 | 80 | //! checks for user interruptions, but only if called from main thread 81 | //! (otherwise last known state is returned). 82 | bool safelyIsInterrupted() 83 | { 84 | if (!isInterrupted_ && calledFromMainThread()) 85 | isInterrupted_ = isInterrupted(); 86 | return isInterrupted_; 87 | } 88 | 89 | //! prints `object` to R console íf called from main thread; otherwise 90 | //! adds a printable version of `object` to a buffer for deferred printing. 91 | //! @param object a string or coercible object to print. 92 | template 93 | void safelyPrint(const T& object) 94 | { 95 | std::lock_guard lk(m_); 96 | msgs_ << object; 97 | if ( calledFromMainThread() && (msgs_.str() != std::string("")) ) { 98 | // release messages in buffer 99 | Rprintf("%s", msgs_.str().c_str()); 100 | R_FlushConsole(); 101 | // clear message buffer 102 | msgs_.str(""); 103 | } 104 | } 105 | 106 | //! prints `object` to R error stream íf called from main thread; otherwise 107 | //! adds a printable version of `object` to a buffer for deferred printing. 108 | //! @param object a string or coercible object to print. 109 | template 110 | void safelyPrintErr(const T& object) 111 | { 112 | std::lock_guard lk(m_); 113 | msgsErr_ << object; 114 | if ( calledFromMainThread() && (msgsErr_.str() != std::string("")) ) { 115 | // release messages in buffer 116 | REprintf("%s", msgsErr_.str().c_str()); 117 | // R_FlushConsole(); 118 | // clear message buffer 119 | msgsErr_.str(""); 120 | } 121 | } 122 | 123 | private: 124 | //! Ctors declared private, to instantiate class use `::instance()`. 125 | RMonitor(void) : isInterrupted_(false) {} 126 | 127 | //! calls R's API to check for user interrupt. 128 | //! 129 | //! Since the R API is not thread safe, it must be called in top-level 130 | //! context to avoid long-jumping out of the current context. 131 | static void callRCheck(void *dummy) { 132 | R_CheckUserInterrupt(); 133 | } 134 | 135 | //! checks for R user interruptions in top level context. 136 | inline bool isInterrupted() { 137 | return (R_ToplevelExec(callRCheck, NULL) == FALSE); 138 | } 139 | 140 | std::mutex m_; // mutex for synchronized r/w 141 | std::stringstream msgs_; // string buffer 142 | std::stringstream msgsErr_; // string buffer for stderr 143 | std::atomic_bool isInterrupted_; 144 | }; 145 | 146 | 147 | //! checks for user interruptions, but only if called from main thread. 148 | //! @param condition optional; a condition for the check to be executed. 149 | //! @details Declared as a friend in `RMonitor`. 150 | inline void checkUserInterrupt(bool condition = true) 151 | { 152 | if (condition) 153 | RMonitor::instance().safelycheckUserInterrupt(); 154 | } 155 | 156 | //! checks for user interruptions, but only if called from main thread 157 | //! (otherwise returns `false`). 158 | //! @param condition optional; a condition for the check to be executed. 159 | //! @details Declared as a friend in `RMonitor`. 160 | inline bool isInterrupted(bool condition = true) 161 | { 162 | if (condition) 163 | return RMonitor::instance().safelyIsInterrupted(); 164 | return false; 165 | } 166 | 167 | 168 | } 169 | -------------------------------------------------------------------------------- /inst/include/RcppThread/Rcerr.hpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | #include 10 | #include "RcppThread/RMonitor.hpp" 11 | 12 | namespace RcppThread { 13 | 14 | //! Safely printing to the R console from threaded code. 15 | class RErrPrinter { 16 | public: 17 | 18 | //! prints `object` to R error stream íf called from main thread; otherwise 19 | //! adds a printable version of `object` to a buffer for deferred printing. 20 | //! @param object a string (or coercible object) to print. 21 | //! @details Declared as a friend in `RMonitor`. 22 | template 23 | RErrPrinter& operator<< (T& object) 24 | { 25 | RMonitor::instance().safelyPrintErr(object); 26 | return *this; 27 | } 28 | 29 | //! prints `object` to R error stream íf called from main thread; otherwise 30 | //! adds a printable version of `object` to a buffer for deferred printing. 31 | //! @param object a string (or coercible object) to print. 32 | //! @details Declared as a friend in `RMonitor`. 33 | template 34 | RErrPrinter& operator<< (const T& object) 35 | { 36 | RMonitor::instance().safelyPrintErr(object); 37 | return *this; 38 | } 39 | 40 | //! prints `object` to R error stream íf called from main thread; otherwise 41 | //! adds a printable version of `object` to a buffer for deferred printing. 42 | //! @param object a string (or coercible object) to print. 43 | //! @details Declared as a friend in `RMonitor`. 44 | RErrPrinter& operator<< (std::ostream& (*object)(std::ostream&)) 45 | { 46 | RMonitor::instance().safelyPrintErr(object); 47 | return *this; 48 | } 49 | }; 50 | 51 | //! global `RPrinter` instance called 'Rcerr' (as in Rcpp). 52 | static RErrPrinter Rcerr = RErrPrinter(); 53 | 54 | } 55 | 56 | // override std::cout to use RcppThread::Rcout instead 57 | #ifndef RCPPTHREAD_OVERRIDE_CERR 58 | #define RCPPTHREAD_OVERRIDE_CERR 0 59 | #endif 60 | 61 | #if RCPPTHREAD_OVERRIDE_CERR 62 | #define cerr RcppThreadRcerr 63 | namespace std { 64 | static RcppThread::RErrPrinter RcppThreadRcerr = RcppThread::RErrPrinter(); 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /inst/include/RcppThread/Rcout.hpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | #include 10 | #include "RcppThread/RMonitor.hpp" 11 | 12 | namespace RcppThread { 13 | 14 | //! Safely printing to the R console from threaded code. 15 | class RPrinter { 16 | public: 17 | 18 | //! prints `object` to R console íf called from main thread; otherwise 19 | //! adds a printable version of `object` to a buffer for deferred printing. 20 | //! @param object a string (or coercible object) to print. 21 | //! @details Declared as a friend in `RMonitor`. 22 | template 23 | RPrinter& operator<< (T& object) 24 | { 25 | RMonitor::instance().safelyPrint(object); 26 | return *this; 27 | } 28 | 29 | //! prints `object` to R console íf called from main thread; otherwise 30 | //! adds a printable version of `object` to a buffer for deferred printing. 31 | //! @param object a string (or coercible object) to print. 32 | //! @details Declared as a friend in `RMonitor`. 33 | template 34 | RPrinter& operator<< (const T& object) 35 | { 36 | RMonitor::instance().safelyPrint(object); 37 | return *this; 38 | } 39 | 40 | //! prints `object` to R console íf called from main thread; otherwise 41 | //! adds a printable version of `object` to a buffer for deferred printing. 42 | //! @param object a string (or coercible object) to print. 43 | //! @details Declared as a friend in `RMonitor`. 44 | RPrinter& operator<< (std::ostream& (*object)(std::ostream&)) 45 | { 46 | RMonitor::instance().safelyPrint(object); 47 | return *this; 48 | } 49 | }; 50 | 51 | //! global `RPrinter` instance called 'Rcout' (as in Rcpp). 52 | static RPrinter Rcout = RPrinter(); 53 | 54 | } 55 | 56 | // override std::cout to use RcppThread::Rcout instead 57 | #ifndef RCPPTHREAD_OVERRIDE_COUT 58 | #define RCPPTHREAD_OVERRIDE_COUT 0 59 | #endif 60 | 61 | #if RCPPTHREAD_OVERRIDE_COUT 62 | #define cout RcppThreadRcout 63 | namespace std { 64 | static RcppThread::RPrinter RcppThreadRcout = RcppThread::RPrinter(); 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /inst/include/RcppThread/SafeTypes.hpp: -------------------------------------------------------------------------------- 1 | // Courtesy of RcppParallel: https://github.com/RcppCore/RcppParallel 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace RcppThread { 9 | 10 | 11 | // Thread safe safe matrix wrapper, courtesy of RcppParallel: 12 | // https://github.com/RcppCore/RcppParallel 13 | template class RMatrix { 14 | public: 15 | class Row { 16 | 17 | public: 18 | template class row_iterator { 19 | 20 | public: 21 | using iterator_category = std::random_access_iterator_tag; 22 | using value_type = V; 23 | using difference_type = std::size_t; 24 | using pointer = value_type *; 25 | using reference = value_type &; 26 | 27 | inline row_iterator(Row &row, difference_type i) 28 | : start_(row.start_), parentNrow_(row.parent_.nrow()), index_(i) {} 29 | 30 | inline row_iterator(pointer start, difference_type parentNrow, 31 | difference_type index) 32 | : start_(start), parentNrow_(parentNrow), index_(index) {} 33 | 34 | inline row_iterator(const row_iterator &other) 35 | : start_(other.start_), parentNrow_(other.parentNrow_), 36 | index_(other.index_) {} 37 | 38 | inline row_iterator &operator++() { 39 | index_++; 40 | return *this; 41 | } 42 | 43 | inline row_iterator operator++(int) { 44 | row_iterator tmp(*this); 45 | operator++(); 46 | return tmp; 47 | } 48 | 49 | inline row_iterator &operator--() { 50 | index_--; 51 | return *this; 52 | } 53 | 54 | inline row_iterator operator--(int) { 55 | row_iterator tmp(*this); 56 | index_--; 57 | return tmp; 58 | } 59 | 60 | row_iterator operator+(difference_type n) const { 61 | return row_iterator(start_, parentNrow_, index_ + n); 62 | } 63 | row_iterator operator-(difference_type n) const { 64 | return row_iterator(start_, parentNrow_, index_ - n); 65 | } 66 | 67 | difference_type operator+(const row_iterator &other) const { 68 | return index_ + other.index_; 69 | } 70 | 71 | difference_type operator-(const row_iterator &other) const { 72 | return index_ - other.index_; 73 | } 74 | 75 | row_iterator &operator+=(difference_type n) { 76 | index_ += n; 77 | return *this; 78 | } 79 | row_iterator &operator-=(difference_type n) { 80 | index_ -= n; 81 | return *this; 82 | } 83 | 84 | bool operator==(const row_iterator &other) const { 85 | return index_ == other.index_; 86 | } 87 | bool operator!=(const row_iterator &other) const { 88 | return index_ != other.index_; 89 | } 90 | bool operator<(const row_iterator &other) const { 91 | return index_ < other.index_; 92 | } 93 | bool operator>(const row_iterator &other) const { 94 | return index_ > other.index_; 95 | } 96 | bool operator<=(const row_iterator &other) const { 97 | return index_ <= other.index_; 98 | } 99 | bool operator>=(const row_iterator &other) const { 100 | return index_ >= other.index_; 101 | } 102 | 103 | inline reference operator*() { return start_[index_ * parentNrow_]; } 104 | 105 | inline pointer operator->() { return &(start_[index_ * parentNrow_]); } 106 | 107 | inline reference operator[](int i) { 108 | return start_[(index_ + i) * parentNrow_]; 109 | } 110 | 111 | private: 112 | pointer start_; 113 | difference_type parentNrow_; 114 | difference_type index_; 115 | }; 116 | 117 | typedef row_iterator iterator; 118 | typedef row_iterator const_iterator; 119 | 120 | inline Row(RMatrix &parent, std::size_t i) 121 | : parent_(parent), start_(parent.begin() + i) {} 122 | 123 | inline Row(const Row &other) 124 | : parent_(other.parent_), start_(other.start_) {} 125 | 126 | inline iterator begin() { return iterator(*this, 0); } 127 | 128 | inline iterator end() { return iterator(*this, parent_.ncol()); } 129 | 130 | inline const_iterator begin() const { return const_iterator(*this, 0); } 131 | 132 | inline const_iterator end() const { 133 | return const_iterator(*this, parent_.ncol()); 134 | } 135 | 136 | inline size_t length() const { return parent_.ncol(); } 137 | 138 | inline size_t size() const { return parent_.ncol(); } 139 | 140 | inline T &operator[](std::size_t i) { return start_[i * parent_.nrow()]; } 141 | 142 | inline const T &operator[](std::size_t i) const { 143 | return start_[i * parent_.nrow()]; 144 | } 145 | 146 | private: 147 | RMatrix &parent_; 148 | T *start_; 149 | }; 150 | 151 | class Column { 152 | 153 | public: 154 | typedef T *iterator; 155 | typedef const T *const_iterator; 156 | 157 | inline Column(RMatrix &parent, std::size_t i) 158 | : begin_(parent.begin() + (i * parent.nrow())), 159 | end_(begin_ + parent.nrow()) {} 160 | 161 | inline Column(const Column &other) 162 | : begin_(other.begin_), end_(other.end_) {} 163 | 164 | inline Column &operator=(const Column &rhs) { 165 | begin_ = rhs.begin_; 166 | end_ = rhs.end_; 167 | return *this; 168 | } 169 | 170 | inline iterator begin() { return begin_; } 171 | inline iterator end() { return end_; } 172 | 173 | inline const_iterator begin() const { return begin_; } 174 | inline const_iterator end() const { return end_; } 175 | 176 | inline size_t length() const { return end_ - begin_; } 177 | inline size_t size() const { return end_ - begin_; } 178 | 179 | inline T &operator[](std::size_t i) { return *(begin_ + i); } 180 | 181 | inline const T &operator[](std::size_t i) const { return *(begin_ + i); } 182 | 183 | private: 184 | T *begin_; 185 | T *end_; 186 | }; 187 | 188 | typedef T *iterator; 189 | typedef const T *const_iterator; 190 | 191 | template 192 | inline explicit RMatrix(const Source &source) 193 | : data_(const_cast(source).begin()), nrow_(source.nrow()), 194 | ncol_(source.ncol()) {} 195 | 196 | inline RMatrix(T *data, std::size_t nrow, std::size_t ncol) 197 | : data_(data), nrow_(nrow), ncol_(ncol) {} 198 | 199 | inline iterator begin() { return data_; } 200 | inline iterator end() { return data_ + length(); } 201 | 202 | inline const_iterator begin() const { return data_; } 203 | inline const_iterator end() const { return data_ + length(); } 204 | 205 | inline std::size_t length() const { return nrow_ * ncol_; } 206 | 207 | inline std::size_t nrow() const { return nrow_; } 208 | inline std::size_t ncol() const { return ncol_; } 209 | 210 | inline T &operator()(std::size_t i, std::size_t j) { 211 | return *(data_ + (i + j * nrow_)); 212 | } 213 | 214 | inline const T &operator()(std::size_t i, std::size_t j) const { 215 | return *(data_ + (i + j * nrow_)); 216 | } 217 | 218 | inline Row row(std::size_t i) { return Row(*this, i); } 219 | 220 | inline const Row row(std::size_t i) const { 221 | return Row(*const_cast(this), i); 222 | } 223 | 224 | inline Column column(std::size_t i) { return Column(*this, i); } 225 | 226 | inline const Column column(std::size_t i) const { 227 | return Column(*const_cast(this), i); 228 | } 229 | 230 | inline T &operator[](std::size_t i) { return *(data_ + i); } 231 | 232 | inline const T &operator[](std::size_t i) const { return *(data_ + i); } 233 | 234 | private: 235 | T *data_; 236 | std::size_t nrow_; 237 | std::size_t ncol_; 238 | }; 239 | 240 | } // namespace RcppThread 241 | -------------------------------------------------------------------------------- /inst/include/RcppThread/Thread.hpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | #include "RcppThread/RMonitor.hpp" 10 | #include "RcppThread/Rcout.hpp" 11 | #include "RcppThread/Rcerr.hpp" 12 | 13 | #include 14 | #include 15 | 16 | //! `RcppThread` functionality 17 | namespace RcppThread { 18 | 19 | //! @brief R-friendly version of `std::thread`. 20 | //! 21 | //! Instances of class `Thread` behave just like instances of `std::thread`, 22 | //! see http://en.cppreference.com/w/cpp/thread/thread for methods and examples. 23 | //! There is one difference exception: Whenever other threads are doing some 24 | //! work, the main thread periodically synchronizes with R. When the user 25 | //! interrupts a threaded computation, any thread will stop as soon as it 26 | //! encounters a `checkUserInterrupt()`. 27 | //! 28 | class Thread { 29 | public: 30 | Thread() = default; 31 | Thread(Thread&) = delete; 32 | Thread(const Thread&) = delete; 33 | Thread(Thread&& other) 34 | { 35 | swap(other); 36 | } 37 | 38 | template explicit 39 | Thread(Function&& f, Args&&... args) 40 | { 41 | auto f0 = [=] () { 42 | if ( !isInterrupted() ) 43 | f(args...); 44 | }; 45 | auto task = std::packaged_task(f0); 46 | future_ = task.get_future(); 47 | thread_ = std::thread(std::move(task)); 48 | } 49 | 50 | ~Thread() noexcept 51 | { 52 | try { 53 | if (thread_.joinable()) 54 | thread_.join(); 55 | } catch (...) {} 56 | } 57 | 58 | Thread& operator=(const Thread&) = delete; 59 | Thread& operator=(Thread&& other) 60 | { 61 | if (thread_.joinable()) 62 | std::terminate(); 63 | swap(other); 64 | return *this; 65 | } 66 | 67 | void swap(Thread& other) noexcept 68 | { 69 | std::swap(thread_, other.thread_); 70 | std::swap(future_, other.future_); 71 | } 72 | 73 | bool joinable() const 74 | { 75 | return thread_.joinable(); 76 | } 77 | 78 | //! checks for interruptions and messages every 0.25 seconds and after 79 | //! computations have finished. 80 | void join() 81 | { 82 | auto timeout = std::chrono::milliseconds(250); 83 | while (future_.wait_for(timeout) != std::future_status::ready) { 84 | Rcout << ""; 85 | Rcerr << ""; 86 | if (isInterrupted()) 87 | break; 88 | std::this_thread::yield(); 89 | } 90 | if (thread_.joinable()) 91 | thread_.join(); 92 | Rcout << ""; 93 | Rcerr << ""; 94 | checkUserInterrupt(); 95 | } 96 | 97 | void detach() 98 | { 99 | thread_.detach(); 100 | } 101 | 102 | std::thread::id get_id() const 103 | { 104 | return thread_.get_id(); 105 | } 106 | 107 | auto native_handle() -> decltype(std::thread().native_handle()) 108 | { 109 | return thread_.native_handle(); 110 | } 111 | 112 | static unsigned int hardware_concurrency() 113 | { 114 | return std::thread::hardware_concurrency(); 115 | } 116 | 117 | private: 118 | std::thread thread_; //! underlying std::thread. 119 | std::future future_; //! future result of task passed to the thread. 120 | }; 121 | 122 | } 123 | 124 | // override std::thread to use RcppThread::Thread instead 125 | #ifndef RCPPTHREAD_OVERRIDE_THREAD 126 | #define RCPPTHREAD_OVERRIDE_THREAD 0 127 | #endif 128 | 129 | #if RCPPTHREAD_OVERRIDE_THREAD 130 | #define thread RcppThreadThread 131 | namespace std { 132 | using RcppThreadThread = RcppThread::Thread; 133 | } 134 | #endif 135 | -------------------------------------------------------------------------------- /inst/include/RcppThread/ThreadPool.hpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | #include "RcppThread/RMonitor.hpp" 10 | #include "RcppThread/Rcerr.hpp" 11 | #include "RcppThread/Rcout.hpp" 12 | #include "RcppThread/quickpool.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace RcppThread { 24 | 25 | //! Implemenation of the thread pool pattern based on `Thread`. 26 | class ThreadPool 27 | { 28 | public: 29 | ThreadPool(ThreadPool&&) = delete; 30 | ThreadPool(const ThreadPool&) = delete; 31 | ThreadPool(); 32 | explicit ThreadPool(size_t nWorkers); 33 | 34 | ~ThreadPool() noexcept; 35 | 36 | ThreadPool& operator=(const ThreadPool&) = delete; 37 | ThreadPool& operator=(ThreadPool&& other) = delete; 38 | 39 | //! Access to the global thread pool instance. 40 | static ThreadPool& globalInstance(); 41 | 42 | void resize(size_t threads); 43 | 44 | template 45 | void push(F&& f, Args&&... args); 46 | 47 | template 48 | auto pushReturn(F&& f, Args&&... args) -> std::future; 49 | 50 | template 51 | void map(F&& f, I&& items); 52 | 53 | template 54 | void parallelFor(int begin, int end, F f, size_t nBatches = 0); 55 | 56 | template 57 | void parallelForEach(I& items, F f, size_t nBatches = 0); 58 | 59 | void wait(); 60 | void join(); 61 | 62 | void setNumThreads(size_t threads); 63 | size_t getNumThreads() const; 64 | 65 | private: 66 | std::unique_ptr pool_; 67 | std::thread::id owner_thread_; 68 | }; 69 | 70 | //! constructs a thread pool with as many workers as there are cores. 71 | inline ThreadPool::ThreadPool() 72 | : ThreadPool(std::thread::hardware_concurrency()) 73 | { 74 | } 75 | 76 | //! constructs a thread pool with `nWorkers` threads. 77 | //! @param nWorkers number of worker threads to create; if `nWorkers = 0`, all 78 | //! work pushed to the pool will be done in the main thread. 79 | inline ThreadPool::ThreadPool(size_t nWorkers) 80 | : pool_{ new quickpool::ThreadPool(nWorkers) } 81 | , owner_thread_{ std::this_thread::get_id() } 82 | { 83 | } 84 | 85 | //! destructor joins all threads if possible. 86 | inline ThreadPool::~ThreadPool() noexcept {} 87 | 88 | //! Access to the global thread pool instance. 89 | inline ThreadPool& 90 | ThreadPool::globalInstance() 91 | { 92 | #ifdef _WIN32 93 | // Must leak resource, because windows + R deadlock otherwise. Memory 94 | // is released on shutdown. 95 | static auto ptr = new ThreadPool; 96 | return *ptr; 97 | #else 98 | static ThreadPool instance_; 99 | return instance_; 100 | #endif 101 | } 102 | 103 | //! changes the number of threads in the pool. 104 | //! @param num_threads the new number of threads. 105 | inline void 106 | ThreadPool::resize(size_t num_threads) 107 | { 108 | pool_->set_active_threads(num_threads); 109 | } 110 | 111 | //! pushes jobs to the thread pool. 112 | //! @param f a function taking an arbitrary number of arguments. 113 | //! @param args a comma-seperated list of the other arguments that shall 114 | //! be passed to `f`. 115 | //! 116 | //! The function returns void; if a job returns a result, use 117 | //! `pushReturn()`. 118 | template 119 | void 120 | ThreadPool::push(F&& f, Args&&... args) 121 | { 122 | pool_->push(std::bind(std::forward(f), std::forward(args)...)); 123 | } 124 | 125 | //! pushes jobs returning a value to the thread pool. 126 | //! @param f a function taking an arbitrary number of arguments. 127 | //! @param args a comma-seperated list of the other arguments that shall 128 | //! be passed to `f`. 129 | //! @return an `std::shared_future`, where the user can get the result and 130 | //! rethrow exceptions. 131 | template 132 | auto 133 | ThreadPool::pushReturn(F&& f, Args&&... args) 134 | -> std::future 135 | { 136 | return pool_->async( 137 | std::bind(std::forward(f), std::forward(args)...)); 138 | } 139 | 140 | //! maps a function on a list of items, possibly running tasks in parallel. 141 | //! @param f function to be mapped. 142 | //! @param items an objects containing the items on which `f` shall be 143 | //! mapped; must allow for `auto` loops (i.e., `std::begin(I)`/ 144 | //! `std::end(I)` must be defined). 145 | template 146 | void 147 | ThreadPool::map(F&& f, I&& items) 148 | { 149 | for (auto&& item : items) 150 | this->push(std::forward(f), item); 151 | } 152 | 153 | //! computes an index-based for loop in parallel batches. 154 | //! @param begin first index of the loop. 155 | //! @param end the loop runs in the range `[begin, end)`. 156 | //! @param f an object callable as a function (the 'loop body'); typically 157 | //! a lambda. 158 | //! @param nBatches the number of batches to create; the default (0) 159 | //! uses work stealing to distribute tasks. 160 | //! @details Consider the following code: 161 | //! ``` 162 | //! std::vector x(10); 163 | //! for (size_t i = 0; i < x.size(); i++) { 164 | //! x[i] = i; 165 | //! } 166 | //! ``` 167 | //! The parallel equivalent is given by: 168 | //! ``` 169 | //! ThreadPool pool(2); 170 | //! pool.forIndex(0, 10, [&] (size_t i) { 171 | //! x[i] = i; 172 | //! }); 173 | //! ``` 174 | //! **Caution**: if the iterations are not independent from another, 175 | //! the tasks need to be synchronized manually (e.g., using mutexes). 176 | template 177 | inline void 178 | ThreadPool::parallelFor(int begin, int end, F f, size_t nBatches) 179 | { 180 | if (nBatches == 0) { 181 | // each worker has its dedicated range, but can steal part of another 182 | // worker's ranges when done with own 183 | auto thr = 184 | std::max(pool_->get_active_threads(), static_cast(1)); 185 | auto workers = quickpool::loop::create_workers(f, begin, end, thr); 186 | for (size_t k = 0; k < thr; k++) { 187 | this->push([=] { workers->at(k).run(workers); }); 188 | } 189 | } else { 190 | // manual batching for backwards compatibility 191 | size_t nTasks = std::max(end - begin, static_cast(0)); 192 | if (nTasks <= 0) 193 | return; 194 | nBatches = std::min(nBatches, nTasks); 195 | 196 | size_t sz = nTasks / nBatches; 197 | int rem = nTasks % nBatches; 198 | for (size_t b = 0; b < nBatches; b++) { 199 | int bs = sz + (rem-- > 0); 200 | this->push([=] { 201 | for (int i = begin; i < begin + bs; ++i) 202 | f(i); 203 | }); 204 | begin += bs; 205 | } 206 | } 207 | } 208 | 209 | //! computes a for-each loop in parallel batches. 210 | //! @param items an object allowing for `std::begin()`/`std::end()` and 211 | //! whose elements can be accessed by the `[]` operator. 212 | //! @param f a function (the 'loop body'). 213 | //! @param nBatches the number of batches to create; the default (0) 214 | //! uses work stealing to distribute tasks. 215 | //! @details Consider the following code: 216 | //! ``` 217 | //! std::vector x(10, 1.0); 218 | //! for (auto& xx : x) { 219 | //! xx *= 2; 220 | //! } 221 | //! ``` 222 | //! The parallel `ThreadPool` equivalent is 223 | //! ``` 224 | //! ThreadPool pool(2); 225 | //! pool.parallelForEach(x, [&] (double& xx) { 226 | //! xx *= 2; 227 | //! }); 228 | //! ``` 229 | //! **Caution**: if the iterations are not independent from another, 230 | //! the tasks need to be synchronized manually (e.g., using mutexes). 231 | template 232 | inline void 233 | ThreadPool::parallelForEach(I& items, F f, size_t nBatches) 234 | { 235 | this->parallelFor(0, items.size(), [&items, f](size_t i) { f(items[i]); }); 236 | } 237 | 238 | //! waits for all jobs to finish and checks for interruptions, but only from the 239 | //! thread that created the pool.Does nothing when called from other threads. 240 | inline void 241 | ThreadPool::wait() 242 | { 243 | if (std::this_thread::get_id() != owner_thread_) { 244 | // Only the thread that constructed the pool can wait. 245 | return; 246 | } 247 | do { 248 | pool_->wait(100); 249 | Rcout << ""; 250 | Rcerr << ""; 251 | checkUserInterrupt(); 252 | 253 | } while (!pool_->done()); 254 | Rcout << ""; 255 | Rcerr << ""; 256 | } 257 | 258 | //! waits for all jobs to finish. 259 | inline void 260 | ThreadPool::join() 261 | { 262 | pool_->wait(); 263 | } 264 | 265 | //! sets the number of active threads in the pool. 266 | //! @param threads the desired number of threads. 267 | inline void 268 | ThreadPool::setNumThreads(size_t threads) 269 | { 270 | pool_->set_active_threads(threads); 271 | } 272 | 273 | //! gets the number of active threads in the pool. 274 | inline size_t 275 | ThreadPool::getNumThreads() const 276 | { 277 | return pool_->get_active_threads(); 278 | } 279 | 280 | } // end namespace RcppThread 281 | -------------------------------------------------------------------------------- /inst/include/RcppThread/parallelFor.hpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Thomas Nagler 2 | // 3 | // This file is part of the RcppThread and licensed under the terms of 4 | // the MIT license. For a copy, see the LICENSE.md file in the root directory of 5 | // RcppThread or https://github.com/tnagler/RcppThread/blob/master/LICENSE.md. 6 | 7 | #pragma once 8 | 9 | #include "RcppThread/ThreadPool.hpp" 10 | #include 11 | 12 | namespace RcppThread { 13 | 14 | //! computes an index-based for loop in parallel batches. 15 | //! @param begin first index of the loop. 16 | //! @param end the loop runs in the range `[begin, end)`. 17 | //! @param f a function (the 'loop body'). 18 | //! @param nThreads limits the number of threads used from the global pool. 19 | //! @param nBatches the number of batches to create; the default (0) 20 | //! uses work stealing to distribute tasks. 21 | //! @details Consider the following code: 22 | //! ``` 23 | //! std::vector x(10); 24 | //! for (size_t i = 0; i < x.size(); i++) { 25 | //! x[i] = i; 26 | //! } 27 | //! ``` 28 | //! The parallel equivalent is 29 | //! ``` 30 | //! parallelFor(0, 10, [&] (size_t i) { 31 | //! x[i] = i; 32 | //! }); 33 | //! ``` 34 | //! The function dispatches to a global thread pool, so it can safely be nested 35 | //! or called multiple times with almost no overhead. 36 | //! 37 | //! **Caution**: if the iterations are not independent from another, 38 | //! the tasks need to be synchronized manually (e.g., using mutexes). 39 | template 40 | inline void 41 | parallelFor(int begin, 42 | int end, 43 | F f, 44 | size_t nThreads = std::thread::hardware_concurrency(), 45 | size_t nBatches = 0) 46 | { 47 | auto oldThreads = ThreadPool::globalInstance().getNumThreads(); 48 | ThreadPool::globalInstance().setNumThreads(nThreads); 49 | 50 | ThreadPool::globalInstance().parallelFor(begin, end, f, nBatches); 51 | ThreadPool::globalInstance().wait(); 52 | 53 | ThreadPool::globalInstance().setNumThreads(oldThreads); 54 | } 55 | 56 | //! computes a range-based for loop in parallel batches. 57 | //! @param items an object allowing for `items.size()` and whose elements 58 | //! are accessed by the `[]` operator. 59 | //! @param f a function (the 'loop body'). 60 | //! @param nThreads limits the number of threads used from the global pool. 61 | //! @param nBatches the number of batches to create; the default (0) 62 | //! uses work stealing to distribute tasks. 63 | //! @details Consider the following code: 64 | //! ``` 65 | //! std::vector x(10, 1.0); 66 | //! for (auto& xx : x) { 67 | //! xx *= 2; 68 | //! } 69 | //! ``` 70 | //! The parallel equivalent is 71 | //! ``` 72 | //! parallelFor(x, [&] (double& xx) { 73 | //! xx *= 2; 74 | //! }); 75 | //! ``` 76 | //! The function dispatches to a global thread pool, so it can safely be nested 77 | //! or called multiple times with almost no overhead. 78 | //! 79 | //! **Caution**: if the iterations are not independent from another, 80 | //! the tasks need to be synchronized manually (e.g., using mutexes). 81 | template 82 | inline void 83 | parallelForEach(I& items, 84 | F f, 85 | size_t nThreads = std::thread::hardware_concurrency(), 86 | size_t nBatches = 0) 87 | { 88 | // loop ranges ranges indicate iterator offset 89 | auto begin = std::begin(items); 90 | auto size = std::distance(begin, std::end(items)); 91 | parallelFor( 92 | 0, size, [f, begin](int i) { f(*(begin + i)); }, nThreads, nBatches); 93 | } 94 | 95 | //! pushes jobs to the global thread pool. 96 | //! @param f a function taking an arbitrary number of arguments. 97 | //! @param args a comma-seperated list of the other arguments that shall 98 | //! be passed to `f`. 99 | //! 100 | //! The function returns void; if a job returns a result, use 101 | //! `async()`. 102 | template 103 | inline void 104 | push(F&& f, Args&&... args) 105 | { 106 | ThreadPool::globalInstance().push(std::forward(f), 107 | std::forward(args)...); 108 | } 109 | 110 | //! pushes jobs returning a value to the global thread pool. 111 | //! @param f a function taking an arbitrary number of arguments. 112 | //! @param args a comma-seperated list of the other arguments that shall 113 | //! be passed to `f`. 114 | //! @return an `std::shared_future`, where the user can get the result and 115 | //! rethrow exceptions. 116 | template 117 | inline auto 118 | pushReturn(F&& f, Args&&... args) -> std::future 119 | { 120 | return ThreadPool::globalInstance().pushReturn(std::forward(f), 121 | std::forward(args)...); 122 | } 123 | 124 | //! pushes jobs returning a value to the global thread pool. 125 | //! @param f a function taking an arbitrary number of arguments. 126 | //! @param args a comma-seperated list of the other arguments that shall 127 | //! be passed to `f`. 128 | //! @return an `std::shared_future`, where the user can get the result and 129 | //! rethrow exceptions. 130 | template 131 | inline auto 132 | async(F&& f, Args&&... args) -> std::future 133 | { 134 | return pushReturn(std::forward(f), std::forward(args)...); 135 | } 136 | 137 | //! waits for all jobs to finish and checks for interruptions, but only from the 138 | //! main thread. Does nothing when called from other threads. 139 | inline void 140 | wait() 141 | { 142 | ThreadPool::globalInstance().wait(); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /inst/include/mainpage.h: -------------------------------------------------------------------------------- 1 | /** @file mainpage.h 2 | * @brief api_docs 3 | * 4 | */ 5 | /** @mainpage RcppThread: R-friendly threading in C++ 6 | * 7 | * @authors Thomas Nagler 8 | * 9 | * This is the API documentation for the RcppThread C++ library. For a more 10 | * high-level overview see the 11 | * vignette and 12 | * README. 13 | * 14 | * @section license License 15 | * 16 | * The MIT License (MIT) 17 | * 18 | * Copyright © 2022 Thomas Nagler 19 | * 20 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 21 | * this software and associated documentation files (the “Software”), to deal in 22 | * the Software without restriction, including without limitation the rights to 23 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 24 | * the Software, and to permit persons to whom the Software is furnished to do so, 25 | * subject to the following conditions: 26 | * 27 | * The above copyright notice and this permission notice shall be included in all 28 | * copies or substantial portions of the Software. 29 | * 30 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 32 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 33 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 34 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 35 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 36 | * 37 | * 38 | */ 39 | -------------------------------------------------------------------------------- /man/LdFlags.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/LdFlags.R 3 | \name{LdFlags} 4 | \alias{LdFlags} 5 | \title{Get portable linker flags for libraries building on RcppThread} 6 | \usage{ 7 | LdFlags() 8 | } 9 | \description{ 10 | To be used in \code{Makevars} on Linux and OSX. Returns a string with 11 | linker flags for \code{pthread} and \code{libatomic}, if available. 12 | } 13 | \details{ 14 | Use as 15 | `PKG_LIBS = $(R_HOME)/bin/Rscript -e 'RcppThread::LdFlags()'. 16 | } 17 | -------------------------------------------------------------------------------- /man/RcppThread.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppThread-package.R 3 | \docType{package} 4 | \name{RcppThread} 5 | \alias{RcppThread-package} 6 | \alias{RcppThread} 7 | \title{R-friendly C++11 threads} 8 | \description{ 9 | Provides a C++11-style thread class and thread pool that can safely be 10 | interrupted from R. 11 | } 12 | \references{ 13 | Nagler, T. (2021). "R-Friendly Multi-Threading in C++." 14 | \emph{Journal of Statistical Software, Code Snippets}, \emph{97}(1), 1-18. 15 | \doi{10.18637/jss.v097.c01}. 16 | } 17 | \seealso{ 18 | Useful links: 19 | \itemize{ 20 | \item \url{https://github.com/tnagler/RcppThread} 21 | \item Report bugs at \url{https://github.com/tnagler/RcppThread/issues} 22 | } 23 | 24 | } 25 | \author{ 26 | \strong{Maintainer}: Thomas Nagler \email{mail@tnagler.com} (\href{https://orcid.org/0000-0003-1855-0046}{ORCID}) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /man/detectCores.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/detectCores.R 3 | \name{detectCores} 4 | \alias{detectCores} 5 | \title{Detect the Number of CPU Cores} 6 | \usage{ 7 | detectCores() 8 | } 9 | \description{ 10 | Detects the number of (logical) CPU cores. 11 | } 12 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /src/Makevars: -------------------------------------------------------------------------------- 1 | PKG_CPPFLAGS = -I../inst/include 2 | PKG_LIBS = `"$(R_HOME)/bin/Rscript" -e "source('../R/LdFlags.R'); LdFlags()"` 3 | -------------------------------------------------------------------------------- /src/detectCores.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef R_NO_REMAP 4 | #define R_NO_REMAP 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | #include "RcppThread.h" 11 | 12 | extern "C" 13 | { 14 | 15 | SEXP detectCoresCpp() 16 | { 17 | SEXP result; 18 | PROTECT(result = NEW_INTEGER(1)); 19 | INTEGER(result)[0] = std::thread::hardware_concurrency(); 20 | UNPROTECT(1); 21 | return result; 22 | } 23 | 24 | SEXP testGlobalCpp() 25 | { 26 | auto fun = [](int i) {}; 27 | RcppThread::parallelFor(0, 100, fun); 28 | SEXP result; 29 | PROTECT(result = NEW_INTEGER(1)); 30 | INTEGER(result)[0] = 1; 31 | UNPROTECT(1); 32 | return result; 33 | } 34 | 35 | static const R_CallMethodDef callMethods[] = { 36 | { "detectCoresCpp", (DL_FUNC)&detectCoresCpp, 0 }, 37 | { "testGlobalCpp", (DL_FUNC)&testGlobalCpp, 0 }, 38 | { NULL, NULL, 0 } 39 | }; 40 | 41 | void R_init_RcppThread(DllInfo* info) 42 | { 43 | R_registerRoutines(info, NULL, callMethods, NULL, NULL); 44 | R_useDynamicSymbols(info, TRUE); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/testGlobal.cpp: -------------------------------------------------------------------------------- 1 | // #include "RcppThread.h" 2 | // 3 | // extern "C" { 4 | // 5 | // SEXP testGlobalCpp() { 6 | // RcppThread:: 7 | // SEXP result; 8 | // PROTECT(result = NEW_INTEGER(1)); 9 | // INTEGER(result)[0] = std::thread::hardware_concurrency(); 10 | // UNPROTECT(1); 11 | // return result; 12 | // } 13 | // 14 | // 15 | // static const R_CallMethodDef callMethods[] = { 16 | // {"testGlobalCpp", (DL_FUNC) &detectCoresCpp, 0}, 17 | // {NULL, NULL, 0} 18 | // }; 19 | // 20 | // void R_init_RcppThread(DllInfo *info) 21 | // { 22 | // R_registerRoutines(info, NULL, callMethods, NULL, NULL); 23 | // R_useDynamicSymbols(info, TRUE); 24 | // } 25 | // 26 | // } 27 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(RcppThread) 3 | 4 | test_check("RcppThread") 5 | -------------------------------------------------------------------------------- /tests/testthat/tests.R: -------------------------------------------------------------------------------- 1 | RcppThread:::testGlobal() 2 | 3 | if (RcppThread:::hasAtomicSupport()) { 4 | 5 | context("Compile test functions") 6 | Rcpp::sourceCpp(file = normalizePath("../tests.cpp")) 7 | 8 | runs <- 5 9 | for (run in seq_len(runs)) { 10 | context(paste0("---------------------------- run ", run, "/", runs)) 11 | test_that("start", expect_true(TRUE)) 12 | 13 | ## ------------------------------------------------------- 14 | context("R-monitor") 15 | test_that("R-monitor works", { 16 | expect_output(testMonitor(), "RcppThread says hi!", fixed = TRUE) 17 | }) 18 | 19 | 20 | ## ------------------------------------------------------- 21 | context("Thread class") 22 | test_that("Thread class works", { 23 | expect_output(testThreadClass()) 24 | }) 25 | 26 | 27 | ## ------------------------------------------------------- 28 | context("Thread pool") 29 | 30 | test_that("push works", { 31 | testThreadPoolPush() 32 | expect_silent(testThreadPoolPush()) 33 | }) 34 | 35 | test_that("pushReturn works", { 36 | expect_silent(testThreadPoolPushReturn()) 37 | }) 38 | 39 | test_that("map works", { 40 | expect_silent(testThreadPoolMap()) 41 | }) 42 | 43 | test_that("parallelFor works", { 44 | expect_silent(testThreadPoolParallelFor()) 45 | }) 46 | 47 | test_that("nested parallelFor works", { 48 | expect_silent(testThreadPoolNestedParallelFor()) 49 | }) 50 | 51 | test_that("parallelForEach works", { 52 | expect_silent(testThreadPoolParallelForEach()) 53 | }) 54 | 55 | test_that("nested parallelForEach works", { 56 | expect_silent(testThreadPoolNestedParallelForEach()) 57 | }) 58 | 59 | test_that("works single threaded", { 60 | expect_silent(testThreadPoolSingleThreaded()) 61 | }) 62 | 63 | test_that("destructible without join", { 64 | expect_silent(testThreadPoolDestructWOJoin()) 65 | }) 66 | 67 | # test_that("rethrows exceptions", { 68 | # expect_silent(testThreadPoolExceptionHandling()) 69 | # }) 70 | 71 | ## ------------------------------------------------------- 72 | context("Parallel for functions") 73 | 74 | test_that("parallelFor works", { 75 | expect_silent(testParallelFor()) 76 | }) 77 | 78 | test_that("nested parallelFor works", { 79 | expect_silent(testNestedParallelFor()) 80 | }) 81 | 82 | test_that("parallelForEach works", { 83 | expect_silent(testParallelForEach()) 84 | }) 85 | 86 | test_that("nested parallelForEach works", { 87 | expect_silent(testNestedParallelForEach()) 88 | }) 89 | 90 | # ------------------------------------------------------ 91 | context("Progress tracking") 92 | test_that("ProgressCounter works", { 93 | expect_output(testProgressCounter(), "Computing: 100% \\(done\\)") 94 | }) 95 | 96 | test_that("ProgressCounter works", { 97 | expect_output(testProgressCounter2(), "Test message: 100% \\(done\\)") 98 | }) 99 | 100 | test_that("ProgressBar works", { 101 | expect_output(testProgressBar(), "100% \\(done\\)") 102 | }) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /vignettes/RcppThread-vignette.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnagler/RcppThread/3d47c8add5330eef4189d35f76eb166384947f13/vignettes/RcppThread-vignette.pdf -------------------------------------------------------------------------------- /vignettes/RcppThread-vignette.pdf.asis: -------------------------------------------------------------------------------- 1 | %\VignetteIndexEntry{RcppThread} 2 | %\VignetteEngine{R.rsp::asis} 3 | %\VignetteKeyword{vignette} 4 | %\VignetteKeyword{package} 5 | 6 | --------------------------------------------------------------------------------