Skip to content

Commit 96a8b95

Browse files
authored
speed up bounds-checked element access and cache vector lengths (#1511)
* speed up bounds-checked element access and cache vector lengths Closes #1510. * update NEWS * keep vector and matrix layouts unchanged Caching the length in proxy_cache and the column count in Matrix added data members, changing the layout of List, CharacterVector and Matrix. Packages pass these by reference across shared libraries compiled against different Rcpp versions: rust hands a List to revdbayes through an XPtr function pointer, and lite, threshr and evmissing reach the same path. In the 2026-10-06 reverse-dependency run revdbayes read a size that rust never wrote and failed name lookup with 'Index out of bounds'. ncol() is now derived from the cached length and nrow(), and proxy_cache reads the length on demand; only the cold warning helper and the cached atomic-vector length remain. * update copyright headers
1 parent d7be2c3 commit 96a8b95

5 files changed

Lines changed: 59 additions & 12 deletions

File tree

‎ChangeLog‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
2026-10-12 Kevin Ushey <kevinushey@gmail.com>
2+
3+
* inst/include/Rcpp/vector/traits.h: Update copyright
4+
* inst/include/Rcpp/vector/Vector.h: Idem
5+
* inst/include/Rcpp/vector/Matrix.h: Idem
6+
7+
2026-09-30 Kevin Ushey <kevinushey@gmail.com>
8+
9+
* inst/include/Rcpp/vector/traits.h: Move the out-of-bounds warning into
10+
a cold, out-of-line helper; add get_size() to the vector caches
11+
* inst/include/Rcpp/vector/Vector.h: Return the length cached by
12+
r_vector_cache from size() and length() rather than calling
13+
Rf_xlength() each time
14+
* inst/include/Rcpp/vector/Matrix.h: Derive ncol() from the cached
15+
length and nrow() instead of reading the dim attribute; document that
16+
no data members may be added, as the layout is shared across packages
17+
118
2026-09-22 Iñaki Ucar <iucar@fedoraproject.org>
219

320
* inst/include/Rcpp/sugar/matrix/col.h: Fix Col constructor using ncol()

‎inst/NEWS.Rd‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
warnings (Iñaki in \ghpr{1508} closing \ghit{1497})
2828
\item The \code{col} constructor now uses \code{nrow} in an
2929
initialization (Iñaki in \ghpr{1509})
30+
\item Bounds-checked element access is faster, \code{size()} of
31+
atomic vectors uses the cached length, and \code{ncol()} no longer
32+
reads the \code{dim} attribute; object layouts are unchanged, as
33+
packages such as \pkg{rust} and \pkg{revdbayes} exchange Rcpp
34+
objects across shared libraries (Kevin in \ghpr{1511} closing
35+
\ghit{1510})
3036
}
3137
\item Changes in Rcpp Documentation:
3238
\itemize{

‎inst/include/Rcpp/vector/Matrix.h‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Matrix.h: Rcpp R/C++ interface class library -- matrices
44
//
55
// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois
6-
// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar
6+
// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois, Kevin Ushey and Iñaki Ucar
77
//
88
// This file is part of Rcpp.
99
//
@@ -27,6 +27,10 @@ namespace Rcpp{
2727

2828
template <int RTYPE, template <class> class StoragePolicy = PreserveStorage >
2929
class Matrix : public Vector<RTYPE, StoragePolicy>, public MatrixBase<RTYPE, true, Matrix<RTYPE,StoragePolicy> > {
30+
// The only data member. Packages pass Rcpp objects by reference across
31+
// shared-library boundaries (e.g. rust calls into revdbayes through XPtr
32+
// function pointers), and the two sides may have been compiled against
33+
// different Rcpp versions. Adding a member changes the layout they share.
3034
int nrows ;
3135

3236
public:
@@ -92,14 +96,17 @@ class Matrix : public Vector<RTYPE, StoragePolicy>, public MatrixBase<RTYPE, tru
9296

9397
explicit Matrix( const no_init_matrix& obj) : VECTOR(Rf_allocMatrix(RTYPE, obj.nrow(), obj.ncol())), nrows(obj.nrow()) {}
9498

99+
// A matrix has exactly nrow * ncol elements, so the column count follows
100+
// from the (cached) length without reading the dim attribute. Only an
101+
// empty matrix, which may still have columns, needs the attribute.
95102
inline int ncol() const {
96-
return VECTOR::dims()[1];
103+
return nrows == 0 ? VECTOR::dims()[1] : static_cast<int>( VECTOR::size() / nrows ) ;
97104
}
98105
inline int nrow() const {
99106
return nrows ;
100107
}
101108
inline int cols() const {
102-
return VECTOR::dims()[1];
109+
return ncol() ;
103110
}
104111
inline int rows() const {
105112
return nrows ;

‎inst/include/Rcpp/vector/Vector.h‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Vector.h: Rcpp R/C++ interface class library -- vectors
22
//
33
// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois
4-
// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar
4+
// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois, Kevin Ushey and Iñaki Ucar
55
//
66
// This file is part of Rcpp.
77
//
@@ -263,17 +263,17 @@ class Vector :
263263
#endif
264264

265265
/**
266-
* the length of the vector, uses Rf_xlength
266+
* the length of the vector, as cached by update()
267267
*/
268268
inline R_xlen_t length() const {
269-
return ::Rf_xlength( Storage::get__() ) ;
269+
return cache.get_size() ;
270270
}
271271

272272
/**
273273
* alias of length
274274
*/
275275
inline R_xlen_t size() const {
276-
return ::Rf_xlength( Storage::get__() ) ;
276+
return cache.get_size() ;
277277
}
278278

279279
/**

‎inst/include/Rcpp/vector/traits.h‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11

22
// traits.h: Rcpp R/C++ interface class library -- support traits for vector
33
//
4-
// Copyright (C) 2010 - 2026 Dirk Eddelbuettel and Romain Francois
4+
// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois
5+
// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Kevin Ushey
56
//
67
// This file is part of Rcpp.
78
//
@@ -24,6 +25,16 @@
2425
namespace Rcpp{
2526
namespace traits{
2627

28+
// Kept out of line and marked cold so that the (almost never taken)
29+
// warning path doesn't bloat element access and block optimization of
30+
// hot loops, e.g. in sugar expressions.
31+
#if defined(__GNUC__)
32+
__attribute__((noinline, cold))
33+
#endif
34+
inline void warn_index_out_of_bounds(R_xlen_t i, R_xlen_t size) {
35+
warning("subscript out of bounds (index %s >= vector size %s)", i, size); // #nocov
36+
}
37+
2738
template <int RTYPE, template <class> class StoragePolicy = PreserveStorage >
2839
class r_vector_cache{
2940
public:
@@ -38,11 +49,12 @@ namespace traits{
3849

3950
inline void update( const VECTOR& v ) {
4051
start = ::Rcpp::internal::r_vector_start<RTYPE>(v) ;
41-
size = v.size();
52+
size = ::Rf_xlength(v.get__());
4253
}
4354

4455
inline iterator get() const { return start; }
4556
inline const_iterator get_const() const { return start; }
57+
inline R_xlen_t get_size() const { return size; }
4658

4759
inline proxy ref() { check_index(0); return start[0] ;}
4860
inline proxy ref(R_xlen_t i) { check_index(i); return start[i] ; }
@@ -55,7 +67,7 @@ namespace traits{
5567
void check_index(R_xlen_t i) const {
5668
#ifndef RCPP_NO_BOUNDS_CHECK
5769
if (i >= size) {
58-
warning("subscript out of bounds (index %s >= vector size %s)", i, size); // #nocov
70+
warn_index_out_of_bounds(i, size); // #nocov
5971
}
6072
#endif
6173
}
@@ -80,6 +92,10 @@ namespace traits{
8092
}
8193
inline iterator get() const { return iterator( proxy(*p, 0 ) ) ;}
8294
inline const_iterator get_const() const { return const_iterator( const_proxy(*p, 0) ) ; }
95+
// Not cached: a size member here would change the layout of List and
96+
// CharacterVector, which packages pass by reference across
97+
// shared-library boundaries (see the note in Matrix.h).
98+
inline R_xlen_t get_size() const { return ::Rf_xlength(p->get__()); }
8399

84100
inline proxy ref() { check_index(0); return proxy(*p,0) ; }
85101
inline proxy ref(R_xlen_t i) { check_index(i); return proxy(*p,i);}
@@ -92,8 +108,9 @@ namespace traits{
92108

93109
void check_index(R_xlen_t i) const {
94110
#ifndef RCPP_NO_BOUNDS_CHECK
95-
if (i >= p->size()) {
96-
warning("subscript out of bounds (index %s >= vector size %s)", i, p->size()); // #nocov
111+
R_xlen_t size = get_size() ;
112+
if (i >= size) {
113+
warn_index_out_of_bounds(i, size); // #nocov
97114
}
98115
#endif
99116
}

0 commit comments

Comments
 (0)