| Title: | Shared Memory Multithreading |
|---|---|
| Description: | This project extends 'R' with a mechanism for efficient parallel data access by utilizing 'C++' shared memory. Large data objects can be accessed and manipulated directly from 'R' without redundant copying, providing both speed and memory efficiency. Memshare was published in Thrun, M.C., Maerte J.: "Memshare: Memory Sharing for Multicore Computation in R with an Application to Feature Selection by Mutual Information using PDE" (2026), R Journal, <DOI:10.32614/RJ-2025-043>. |
| Authors: | Julian Maerte [aut, ctr] (ORCID: <https://orcid.org/0000-0001-5451-1023>), Romain Francois [ctb], Michael Thrun [aut, ths, rev, cph, cre] (ORCID: <https://orcid.org/0000-0001-9542-5543>) |
| Maintainer: | Michael Thrun <[email protected]> |
| License: | GPL-3 |
| Version: | 1.1.1 |
| Built: | 2026-06-03 11:32:09 UTC |
| Source: | https://github.com/mthrun/memshare |
parApply function for a shared memory context. memApply mirrors parApply in the shared memory setting given a shared memory space namespace with a target matrix X and some shared variables VARS either as variables or as names of their registered variables.
memApply(X, MARGIN, FUN, NAMESPACE = NULL, CLUSTER=NULL, VARS=NULL, MAX.CORES=NULL)memApply(X, MARGIN, FUN, NAMESPACE = NULL, CLUSTER=NULL, VARS=NULL, MAX.CORES=NULL)
X |
A [1:n,1:d] numerical matrix of n rows and d columns which is worked upon. Can also be a string name of an already registered variable in |
MARGIN |
Whether to apply by row (1) or column (2). |
FUN |
Function that is applied on either the rows or columns of |
NAMESPACE |
Optional, string. The namespace identifier for the shared memory session. If this is |
CLUSTER |
Optional, A parallel::makeCluster cluster. Will be used for parallelization. By defining clusterExport constant R-copied objects (non-shared) can be shared among different executions of FUN. If |
VARS |
Optional, Either a named list of variables where the name will be the name under which the variable is registered in shared memory space or a character vector of names of variables already registered which should be provided to FUN. |
MAX.CORES |
Optional, In case CLUSTER is undefined a new cluster with |
memApply runs a worker pool on the exact same memory (for shared memory context, see registerVariables), and allows you to apply a function FUN row- or columnwise (depending on MARGIN) over the target matrix.
Since the memory is shared only the names of variables have to be copied to each worker thread in CLUSTER (a makeCluster multithreading cluster) resulting in sharing of arbitrarily large matrices (as long as the fit in RAM once) along a parallel cluster while only copying a couple of bytes per cluster.
The numerical matrix X and the Vars havee to be objects of base type 'double'.
It is recommended not to change the values of v inside FUN, however this will only lead to some copying of the column whenever it is worked upon; the shared memory thus will not be corrupted even if you write to column or row. Also the copying only ever happens for one column/row at a time leading to much lower memory consumption than parallel even in this case.
Thread safety
The vector v passed to FUN is typically an ALTREP view that
directly references shared memory rather than a private copy.
This means that multiple worker processes may be reading the same memory region
simultaneously.
Read-only operations are fully safe and recommended. Examples include
statistical summaries (mean(v), cor(v, y)), vectorized arithmetic,
and model-fitting that does not modify v.
If you attempt to modify elements of v directly (for example,
v[1] <- 0), you are writing into a shared buffer.
Concurrent modification by multiple workers can lead to race conditions
or data corruption. Even if no other process is writing, in-place assignment
may still trigger an internal copy of that row or column, slightly increasing
memory usage.
For safety and clarity, always copy v locally if you need to modify it:
f <- function(v, y) {
v <- as.vector(v) # make a private, normal R copy
v <- scale(v)
cor(v, y)
}
This ensures isolation between workers and prevents unintended data sharing.
Finally, remember that R's internal C API is not thread-safe.
If your function FUN uses multi-threaded C++ code (e.g., via OpenMP or TBB),
those internal threads must not make calls into R (such as creating
objects, evaluating expressions, or printing).
All R interactions must occur in the main thread of each worker process.
result |
A list of the results of func(row,...) of size n or func(col, ...) of size d, depending on |
Julian Maerte
[Thrun and Märte, 2026] Thrun, M.C., Märte, J.: Memshare: Memory Sharing for Multicore Computation in R with an Application to Feature Selection by Mutual Information using PDE, The R Journal, Vol. 17(4), pp. 306 - 322, doi 10.32614/RJ-2025-043, 2026.
library(parallel) cl = makeCluster(1) i = 1 A1 = matrix(as.double(1:10^(i+1)),10^i, 10^i) res = memApply(X = A1, MARGIN = 2, FUN = function(x) { return(sd(x)) }, CLUSTER=cl, NAMESPACE="ns_apply") SD_vector=unlist(res)library(parallel) cl = makeCluster(1) i = 1 A1 = matrix(as.double(1:10^(i+1)),10^i, 10^i) res = memApply(X = A1, MARGIN = 2, FUN = function(x) { return(sd(x)) }, CLUSTER=cl, NAMESPACE="ns_apply") SD_vector=unlist(res)
parLapply function for a shared memory context. memLapply mirrors parLapply in the shared memory setting given a shared memory space namespace with a target list X and some shared variables VARS either as list of variables or as their names in the memory space,
memLapply(X, FUN, NAMESPACE = NULL, CLUSTER = NULL, VARS=NULL, MAX.CORES = NULL)memLapply(X, FUN, NAMESPACE = NULL, CLUSTER = NULL, VARS=NULL, MAX.CORES = NULL)
X |
Either a 1:n list object or a the name of an already registered list object in |
FUN |
Function to be applied over the list. The first argument will be set to the list element, the remaining ones have to have the same name as they have in the shared memory space! |
NAMESPACE |
Optional, string. The namespace identifier for the shared memory session. If this is |
CLUSTER |
Optional, A parallel::makeCluster cluster. Will be used for parallelization. By defining clusterExport constant R-copied objects (non-shared) can be shared among different executions of FUN. If |
VARS |
Optional, Either a named list of variables where the name will be the name under which the variable is registered in shared memory space or a character vector of names of variables already registered which should be provided to FUN. |
MAX.CORES |
Optional, In case CLUSTER is undefined a new cluster with |
memLapply runs a worker pool on the exact same memory (shared memory context), and allows you to apply a function FUN elementwise over the target list.
Since the memory is shared only the names have to be copied to each worker thread in CLUSTER (a makeCluster multithreading cluster) resulting in sharing of arbitrarily large matrices (as long as the fit in RAM once) along a parallel cluster while only copying a couple of bytes per cluster.
It is recommended not to change the values of the list element el inside FUN, however this will only lead to some copying of the element whenever it is worked upon; the shared memory thus will not be corrupted even if you write to an element. Also the copying only ever happens for one element at a time leading to much lower memory consumption than parallel even in this case.
Thread safety
Each element el provided to FUN is typically an ALTREP view
of a shared-memory object rather than an ordinary R copy.
This means that workers can access the same physical memory region concurrently.
Read-only operations are fully safe and recommended. Examples include
computations such as matrix multiplication, summary statistics, or transformations
that return new results without altering el in place.
Because the data are shared, these operations require almost no additional memory
and avoid costly data duplication.
If your function modifies an element directly (e.g., el[1,1] <- 0), you may
be writing to a shared memory buffer that other workers can also access.
This can cause inconsistent results or data corruption if multiple workers write
simultaneously. Even when no overlap exists, such in-place modification can trigger
a copy of the element inside that worker, reducing memory efficiency.
For safety, make an explicit local copy before any in-place changes:
f <- function(el, y) {
el <- as.matrix(el) # force a private copy
el <- el * y # modify locally
colSums(el)
}
This ensures that only the current worker modifies its own private memory.
Finally, note that R’s internal C API is single-threaded.
If FUN calls compiled code (e.g., via Rcpp, OpenMP, or TBB) that
spawns multiple threads, those threads must not interact directly with R
(e.g., by creating R objects, printing, or evaluating expressions).
All such interactions must occur in the main R thread of each worker process.
result |
A 1:n list of the results of func(list[[i]],...), for every element of listName. |
Julian Maerte
[Thrun and Märte, 2026] Thrun, M.C., Märte, J.: Memshare: Memory Sharing for Multicore Computation in R with an Application to Feature Selection by Mutual Information using PDE, The R Journal, Vol. 17(4), pp. 306 - 322, doi 10.32614/RJ-2025-043, 2026.
list_length = 1000 matrix_dim = 100 l = lapply( 1:list_length, function(i) matrix(rnorm(matrix_dim * matrix_dim), nrow = matrix_dim, ncol = matrix_dim)) y = rnorm(matrix_dim) namespace = "ns_lapply" res = memshare::memLapply(l, function(el, y) { el }, NAMESPACE=namespace, VARS=list(y=y), MAX.CORES = 1)list_length = 1000 matrix_dim = 100 l = lapply( 1:list_length, function(i) matrix(rnorm(matrix_dim * matrix_dim), nrow = matrix_dim, ncol = matrix_dim)) y = rnorm(matrix_dim) namespace = "ns_lapply" res = memshare::memLapply(l, function(el, y) { el }, NAMESPACE=namespace, VARS=list(y=y), MAX.CORES = 1)
Return mutual information for a pair of joint variables. The variables can either be both numeric, both discrete or a mixture. The calculation is done via density estimate whenever necessary (i.e. for the continuous variables). The density is estimated via pareto density estimation with subsequent gaussian kernel smoothing.
mutualinfo(x, y, isXDiscrete = FALSE, isYDiscrete = FALSE, eps=.Machine$double.eps*1000, useMPMI=FALSE,na.rm=FALSE)mutualinfo(x, y, isXDiscrete = FALSE, isYDiscrete = FALSE, eps=.Machine$double.eps*1000, useMPMI=FALSE,na.rm=FALSE)
x |
[1:n] a numeric vector (not necessarily continuous) |
y |
[1:n] a numeric vector (not necessarily continuous) |
isXDiscrete |
Boolean defining whether or not the first numeric vector resembles a continuous or discrete measurement |
isYDiscrete |
Boolean defining whether or not the second numeric vector resembles a continuous or discrete measurement |
eps |
Scalar, The threshold for which the mutual info summand should be ignored (the limit of the summand for x -> 0 is 0 but the logarithm will be -inf...) |
useMPMI |
Boolean defining whether or not to use the package mpmi for the calculation (will be used as a baseline) |
na.rm |
Boolean defining whether or not to use complete obeservations only |
Mutual Information is >= 0 and symmetric (in x and y). You can think of mutual information as a measure of how much of x's information is contained in y's information or put more simply: How much does y predict x. Note that mutual information can be compared for pairs that share one variable e.g. (x,y) and (y,z), if MI(x,y) > MI(y,z) then x and y are more closely linked than y and z. However given pairs that do not share a variable, e.g. (x,y), (u,v) then MI(x,y) and MI(u,v) can not be reasonably compared. In particular: MI defines a partial ordering on the column pairs of a matrix instead of a total ordering (which correlation does for example). This is mainly due to MI not being upper-bound and thus is not reasonable put on a scale from 0 to 1.
mutualinfo |
The mutual information of the variables |
This function requires that either DataVisualizations and ScatterDensity of equal or higher version than 0.1.1 is installed, or mpmi package
Julian Märte, Michael Thrun
Claude E. Shannon: A Mathematical Theory of Communication, 1948
x = c(rnorm(1000),rnorm(2000)+8,rnorm(1000)*2-8) y = c(rep(1, 1000), rep(2, 2000), rep(3,1000)) if(requireNamespace("DataVisualizations", quietly = TRUE) && requireNamespace("ScatterDensity", quietly = TRUE) && packageVersion("ScatterDensity") >= "0.1.1" && packageVersion("DataVisualizations") >= "1.1.5"){ mutualinfo(x, y, isXDiscrete=FALSE, isYDiscrete=TRUE) } if(requireNamespace("mpmi", quietly = TRUE)) { mutualinfo(x, y, isXDiscrete=FALSE, isYDiscrete=TRUE,useMPMI=TRUE) }x = c(rnorm(1000),rnorm(2000)+8,rnorm(1000)*2-8) y = c(rep(1, 1000), rep(2, 2000), rep(3,1000)) if(requireNamespace("DataVisualizations", quietly = TRUE) && requireNamespace("ScatterDensity", quietly = TRUE) && packageVersion("ScatterDensity") >= "0.1.1" && packageVersion("DataVisualizations") >= "1.1.5"){ mutualinfo(x, y, isXDiscrete=FALSE, isYDiscrete=TRUE) } if(requireNamespace("mpmi", quietly = TRUE)) { mutualinfo(x, y, isXDiscrete=FALSE, isYDiscrete=TRUE,useMPMI=TRUE) }
When your current session has registered shared memory variables via registerVariables internally the variable is tracked until it is released via releaseVariables.
This function serves as a tool to check whether all variables have been free'd after usage or to see what variables are currently held by the session.
pageList()pageList()
The string of each element of the output list has the format environment, backslash, backslash <namespace name>.<variable name>. Default is lokal environment.
An [1:m] list of characters of the registered p namespaces, each of them having up to k variables, m<=p*k. Each element of the list is a combination of namespace and variable name
Use alongside viewList to ensure all views and pages are cleared before shutdown.
Julian Maerte
viewList, registerVariables, releaseVariables
pageList() ## Not run: # = list() ## End(Not run) mat = matrix(0,5,5) registerVariables("ns_pageL", list(mat=mat)) pageList() ## Not run: # = list("mat") ## End(Not run) releaseVariables("ns_pageL", c("mat")) pageList() ## Not run: # = list() ## End(Not run)pageList() ## Not run: # = list() ## End(Not run) mat = matrix(0,5,5) registerVariables("ns_pageL", list(mat=mat)) pageList() ## Not run: # = list("mat") ## End(Not run) releaseVariables("ns_pageL", c("mat")) pageList() ## Not run: # = list() ## End(Not run)
Given a namespace identifier (identifies the shared memory space to register to), this function allows you to allocate shared memory and copy data into it for other R sessions to access it.
registerVariables(namespace, variableList)registerVariables(namespace, variableList)
namespace |
string of the identifier of the shared memory context. |
variableList |
A named list of variables to register. Currently supported are matrices and vectors. |
No return value, called for allocation of memory pages.
Julian Maerte
releaseVariables, retrieveViews
library(memshare) n = 10 m = 10 TargetMat= matrix(rnorm(n * m), n, m) # target matrix x_vec = rnorm(n) # some other vector namespace = "ns_register" registerVariables(namespace, list(TargetMat=TargetMat, x_vec=x_vec)) memshare::releaseVariables(namespace, c("TargetMat", "x_vec"))library(memshare) n = 10 m = 10 TargetMat= matrix(rnorm(n * m), n, m) # target matrix x_vec = rnorm(n) # some other vector namespace = "ns_register" registerVariables(namespace, list(TargetMat=TargetMat, x_vec=x_vec)) memshare::releaseVariables(namespace, c("TargetMat", "x_vec"))
Delete variables from the shared memory space. Actual release occurs only when no active views remain.
releaseVariables(namespace, variableNames)releaseVariables(namespace, variableNames)
namespace |
Character(1) used at registration time. |
variableNames |
Character vector of names to free. |
Registered buffers remain allocated until all views are released.
If any worker still holds a view, releaseVariables cannot reclaim memory.
Thread safety
Registered buffers may be read concurrently by many processes. Concurrent writes must be synchronized externally (e.g., interprocess mutex). Do not call the R API from secondary threads.
Wrappers such as memApply / memLapply call this function on.exit.
Invisibly, TRUE on success.
This call succeeds in removing ownership, but underlying memory is only unmapped
when every process has called releaseViews for those variables.
Use viewList and pageList for diagnostics.
registerVariables, releaseViews
ns <- "example" X <- matrix(rnorm(100), 10, 10) registerVariables(ns, list(X = X)) # later ... releaseVariables(ns, "X")ns <- "example" X <- matrix(rnorm(100), 10, 10) registerVariables(ns, list(X = X)) # later ... releaseVariables(ns, "X")
Given a namespace identifier (identifies the shared memory space to register to), this function releases retrieved views from the shared memory space.
NOTE: All views have to be free'd upon releasing the variable by the master.
releaseViews(namespace, variableNames)releaseViews(namespace, variableNames)
namespace |
string of the identifier of the shared memory context. |
variableNames |
A character vector of variable names to delete. |
This is the only way to drop handles created by retrieveViews. Wrappers
such as memApply / memLapply call this function internaly. Not releasing views effectively
leaks the backing pages for the lifetime of the process.
No return value, called for deallocation of views.
Julian Maerte
retrieveViews, registerVariables
## Not run: # MASTER SESSION: # allocate data ## End(Not run) n = 1000 m = 100 mat = matrix(rnorm(n * m), n, m) # target matrix y = rnorm(n) # some other constant vector in which the function should not run namespace = "ns_relview" memshare::registerVariables(namespace, list(mat=mat, y=y)) ## Not run: # WORKER SESSION: ## End(Not run) res = retrieveViews(namespace, c("mat", "y")) ## Not run: # Perform your shared calculations here ## End(Not run) releaseViews(namespace, c("mat", "y")) ## Not run: # MASTER SESSION: # free memory ## End(Not run) memshare::releaseVariables(namespace, c("mat", "y"))## Not run: # MASTER SESSION: # allocate data ## End(Not run) n = 1000 m = 100 mat = matrix(rnorm(n * m), n, m) # target matrix y = rnorm(n) # some other constant vector in which the function should not run namespace = "ns_relview" memshare::registerVariables(namespace, list(mat=mat, y=y)) ## Not run: # WORKER SESSION: ## End(Not run) res = retrieveViews(namespace, c("mat", "y")) ## Not run: # Perform your shared calculations here ## End(Not run) releaseViews(namespace, c("mat", "y")) ## Not run: # MASTER SESSION: # free memory ## End(Not run) memshare::releaseVariables(namespace, c("mat", "y"))
Given a namespace identifier (identifies the shared memory space to register to), this function retrieves the metadata of the stored variable.
NOTE: If no view of the variable was previously retrieved this implicitly retrieves a view and thus has to free'd afterwards!
retrieveMetadata(namespace, variableName)retrieveMetadata(namespace, variableName)
namespace |
string of the identifier of the shared memory context. |
variableName |
[1:m] character vector, names of one ore more than one variable to retrieve the metadata from the shared memory space. |
In some contexts, querying metadata may create an implicit view. If so, you must call
releaseViews for that variable afterwards. See examples.
A [1:m] named list mapping the variable names to their retrieved metadata. Each list element contains a list of two elements called "type" and length "n"
Julian Maerte
releaseVariables, releaseViews, registerVariables
## Not run: # MASTER SESSION: # allocate data ## End(Not run) n = 1000 m = 100 mat = matrix(rnorm(n * m), n, m) # target matrix namespace = "ns_meta" memshare::registerVariables(namespace, list(mat=mat)) ## Not run: # WORKER SESSION: # retrieve metadata of the variable ## End(Not run) res = memshare::retrieveMetadata(namespace, "mat") ## Not run: # res$type = "matrix" # res$nrow = 1000 # res$ncol = 100 ## End(Not run) releaseViews(namespace, c("mat")) ## Not run: # MASTER SESSION: # free memory ## End(Not run) memshare::releaseVariables(namespace, c("mat"))## Not run: # MASTER SESSION: # allocate data ## End(Not run) n = 1000 m = 100 mat = matrix(rnorm(n * m), n, m) # target matrix namespace = "ns_meta" memshare::registerVariables(namespace, list(mat=mat)) ## Not run: # WORKER SESSION: # retrieve metadata of the variable ## End(Not run) res = memshare::retrieveMetadata(namespace, "mat") ## Not run: # res$type = "matrix" # res$nrow = 1000 # res$ncol = 100 ## End(Not run) releaseViews(namespace, c("mat")) ## Not run: # MASTER SESSION: # free memory ## End(Not run) memshare::releaseVariables(namespace, c("mat"))
ALTREP' representation of variables from a shared memory space. Given a namespace identifier (identifies the shared memory space to register to), this function constructs mocked matrices/vectors (depending on the variable type) pointing to 'C++' shared memory instead of 'R'-internal memory state.
The mockup is constructed as an 'ALTREP' object, which is an Rcpp wrapper around 'C++' raw memory. 'R' thinks of these objects as common matrices or vectors.
The variables content can be modified, resulting in modification of shared memory. Thus when not using wrapper functions like memApply or memLapply the user has to be cautious of the side-effects an 'R' session working on shared memory has on other 'R' sessions working on the same namespace.
retrieveViews(namespace, variableNames)retrieveViews(namespace, variableNames)
namespace |
string of the identifier of the shared memory context. |
variableNames |
[1:n] character vector, the names of the variables to retrieve from the shared memory space. |
Thread safety
Returned objects may alias shared memory. Concurrent writes must be synchronized externally (e.g., interprocess mutex). Do not call the R API from secondary threads.
Resource cleanup
Each call must be matched by releaseViews. Failing to release
views prevents releaseVariables from freeing memory.
An 1:p list of p elements, each element contains a variable that was registered by registerVariables
Having a view of a memory chunk introduces an internally tracked handle to the shared memory. Shared memory is not deleted until all handles are gone; before calling releaseVariables in the master session, you have to free all view-initialized handles via releaseViews!
Julian Maerte
releaseVariables, registerVariables, releaseViews
## Not run: # MASTER SESSION: # init some data and make shared ## End(Not run) n = 1000 m = 100 mat = matrix(rnorm(n * m), n, m) # target matrix y = rnorm(n) # some other constant vector in which the function should not run namespace = "ns_retrview" memshare::registerVariables(namespace, list(mat=mat, y=y)) ## Not run: # WORKER SESSION # retrieve the shared data and work with it ## End(Not run) res = memshare::retrieveViews(namespace, c("mat", "y")) ## Not run: # res is a list of the format: # list(mat=matrix_altrep, y=vector_altrep), # altrep-variables can be used # exactly the same way as a matrix or vector # and also behave like them when checking via # is.matrix or is.numeric. # important: Free view before resuming # to master session to release the variables! ## End(Not run) memshare::releaseViews(namespace, c("mat", "y")) ## Not run: # MASTER SESSION # After all view handles have been free'd, release the variable ## End(Not run) memshare::releaseVariables(namespace, c("mat", "y"))## Not run: # MASTER SESSION: # init some data and make shared ## End(Not run) n = 1000 m = 100 mat = matrix(rnorm(n * m), n, m) # target matrix y = rnorm(n) # some other constant vector in which the function should not run namespace = "ns_retrview" memshare::registerVariables(namespace, list(mat=mat, y=y)) ## Not run: # WORKER SESSION # retrieve the shared data and work with it ## End(Not run) res = memshare::retrieveViews(namespace, c("mat", "y")) ## Not run: # res is a list of the format: # list(mat=matrix_altrep, y=vector_altrep), # altrep-variables can be used # exactly the same way as a matrix or vector # and also behave like them when checking via # is.matrix or is.numeric. # important: Free view before resuming # to master session to release the variables! ## End(Not run) memshare::releaseViews(namespace, c("mat", "y")) ## Not run: # MASTER SESSION # After all view handles have been free'd, release the variable ## End(Not run) memshare::releaseVariables(namespace, c("mat", "y"))
When your current session has retrieved views of shared memory via retrieveViews internally the view is tracked until it is released via releaseViews.
This function serves as a tool to check whether all views have been free'd after usage or to see what views are currently available to the session.
viewList()viewList()
The string of each element of the output list has the format <namespace name>.<variable name>. Default is lokal environment.
Useful for leak diagnostics: if releaseVariables did not free memory,
check that viewList() is empty across all sessions.
An 1:p list of characters of the the p retrieved views
For windows we prepend the namespace identifier by "Local\\" because otherwise the shared memory is shared system-wide (instead of user-wide) which needs admin privileges.
Julian Maerte
## Not run: # MASTER SESSION: ## End(Not run) mat = matrix(0,5,5) registerVariables("ns_viewL", list(mat=mat)) ## Not run: # WORKER SESSION: ## End(Not run) viewList() # an empty list to begin with (no views retrieved) matref = retrieveViews("ns_viewL", c("mat")) viewList() ## Not run: # now equals c("ns_viewL.mat") releaseViews("ns_viewL", c("mat")) viewList() ## Not run: # an empty list again # MASTER SESSION: ## End(Not run) releaseVariables("ns_viewL", c("mat"))## Not run: # MASTER SESSION: ## End(Not run) mat = matrix(0,5,5) registerVariables("ns_viewL", list(mat=mat)) ## Not run: # WORKER SESSION: ## End(Not run) viewList() # an empty list to begin with (no views retrieved) matref = retrieveViews("ns_viewL", c("mat")) viewList() ## Not run: # now equals c("ns_viewL.mat") releaseViews("ns_viewL", c("mat")) viewList() ## Not run: # an empty list again # MASTER SESSION: ## End(Not run) releaseVariables("ns_viewL", c("mat"))