[Rd] R 4.5.3 macOS arm64: severe matrix-product performance regression vs R 4.5.2 due to libRblas.dylib selecting libRblas.0.dylib instead of libRblas.vecLib.dylib
Jeffrey Racine
r@c|nej@ @end|ng |rom gm@||@com
Fri Mar 27 00:48:31 CET 2026
Greetings,
I am reporting a substantial performance regression on macOS arm64 when
moving from `R 4.5.2` to `R 4.5.3`.
This first appeared while benchmarking the `np` package, where a
conditional-density least-squares cross-validation route (`npcdensbw(...,
bwmethod="cv.ls")`) became roughly 5x slower under `R 4.5.3`. After
investigation, the package itself appears not to be at fault: the
regression can be reproduced directly with base matrix-product operations
and seems to be caused by a change in which BLAS library `R 4.5.3` uses on
macOS arm64.
### System
- Hardware: Apple Silicon (`aarch64`)
- OS: macOS Tahoe 26.4
- `R 4.5.2`: `R version 4.5.2 (2025-10-31)`
- `R 4.5.3`: `R version 4.5.3 (2026-03-11)`
### Summary of findings
Using the official macOS arm64 installers:
- `R-4.5.2-arm64.pkg`
- `R-4.5.3-arm64.pkg`
I unpacked both installers and compared their framework payloads.
Key visible result:
- In `R 4.5.2`, `Resources/lib/libRblas.dylib` points to
`libRblas.vecLib.dylib`
- In `R 4.5.3`, `Resources/lib/libRblas.dylib` points to `libRblas.0.dylib`
Both payloads still contain both files:
- `libRblas.0.dylib`
- `libRblas.vecLib.dylib`
### Payload evidence
`R 4.5.2` unpacked payload:
```text
lrwxr-xr-x ... libRblas.dylib -> libRblas.vecLib.dylib
-rwxr-xr-x ... libRblas.0.dylib
-rwxr-xr-x ... libRblas.vecLib.dylib
```
`R 4.5.3` unpacked payload:
```text
lrwxr-xr-x ... libRblas.dylib -> libRblas.0.dylib
-rwxr-xr-x ... libRblas.0.dylib
-rwxr-xr-x ... libRblas.vecLib.dylib
```
Additional payload comparison:
- `Makeconf`, `ldpaths`, `Renviron`, `bin/R`, and installer scripts were
byte-identical between `4.5.2` and `4.5.3`
- the executable `__text` sections of `libRblas.0.dylib` and
`libRlapack.dylib` were identical
- the executable `__text` section of `libR.dylib` differed
### How the runtimes were tested
I ran both R versions directly from the unpacked installer payloads using
`Resources/bin/exec/R`, with `R_HOME` and `DYLD_LIBRARY_PATH` pointed at
each unpacked framework. This avoids relying on the live installed system
framework.
Example invocation:
```bash
R_HOME=/tmp/R452_pkg/R-fw.pkg/Payload/R.framework/Versions/4.5-arm64/Resources
\
DYLD_LIBRARY_PATH=/tmp/R452_pkg/R-fw.pkg/Payload/R.framework/Versions/4.5-arm64/Resources/lib
\
/tmp/R452_pkg/R-fw.pkg/Payload/R.framework/Versions/4.5-arm64/Resources/bin/exec/R
--vanilla -q -f /tmp/r_blas_probe2.R
```
and similarly for `R 4.5.3`.
### Minimal reproducer
```r
set.seed(1)
n <- 1000000L
x <- rnorm(n)
y <- rnorm(n)
A <- matrix(rnorm(2000L*2000L), 2000L, 2000L)
B <- matrix(rnorm(2000L*2000L), 2000L, 2000L)
cat(R.version.string, "\n")
cat("crossprod vec\n")
print(system.time(for(i in 1:40) z <- crossprod(x, y)))
cat("matmult\n")
print(system.time(C <- A %*% B))
```
### Observed timings
`R 4.5.2`:
- `crossprod()` elapsed about `0.032s`
- `%*%` elapsed about `0.034s`
Observed output:
```text
R version 4.5.2 (2025-10-31)
crossprod vec
user system elapsed
0.031 0.002 0.032
matmult
user system elapsed
0.058 0.003 0.034
```
`R 4.5.3`:
- `crossprod()` elapsed about `0.072s`
- `%*%` elapsed about `2.443s`
Observed output:
```text
R version 4.5.3 (2026-03-11)
crossprod vec
user system elapsed
0.071 0.000 0.072
matmult
user system elapsed
2.381 0.057 2.443
```
This is a very large regression for `%*%` and a clear regression for
`crossprod()`.
### Loaded libraries during runtime
For the unpacked `R 4.5.2` process, `lsof` showed:
```text
... /tmp/R452_pkg/.../Resources/lib/libRblas.vecLib.dylib
... /tmp/R452_pkg/.../Resources/lib/libR.dylib
... /tmp/R452_pkg/.../Resources/lib/libRlapack.dylib
```
`vmmap` also showed that Apple Accelerate / vecLib libraries were mapped.
For the unpacked `R 4.5.3` process, `lsof` showed:
```text
... /tmp/R453_pkg/.../Resources/lib/libRblas.0.dylib
... /tmp/R453_pkg/.../Resources/lib/libR.dylib
... /tmp/R453_pkg/.../Resources/lib/libRlapack.dylib
```
### Critical confirmation
In a temporary copy of the unpacked `R 4.5.3` framework, I changed only
this symlink:
- from:
```text
libRblas.dylib -> libRblas.0.dylib
```
- to:
```text
libRblas.dylib -> libRblas.vecLib.dylib
```
After that single change, the `R 4.5.3` runtime regained the fast timings:
```text
R version 4.5.3 (2026-03-11)
crossprod vec
user system elapsed
0.032 0.002 0.033
matmult
user system elapsed
0.055 0.003 0.032
```
`lsof` then showed:
```text
... /tmp/R453_test/.../Resources/lib/libRblas.vecLib.dylib
... /tmp/R453_test/.../Resources/lib/libR.dylib
... /tmp/R453_test/.../Resources/lib/libRlapack.dylib
```
So the regression appears to be directly tied to the BLAS target selected
by `libRblas.dylib` in the macOS arm64 `R 4.5.3` distribution.
### Why this matters
This caused a large apparent regression in package benchmarks, but the
package code was not the source. The underlying issue is visible in base
matrix-product operations.
For performance-sensitive code relying on repeated dot products or matrix
multiplies, the effect is substantial.
One affected package-level example was `np::npcdensbw(..., bwmethod =
"cv.ls")`,
whose hot path repeatedly uses BLAS-backed dot products inside
conditional-density CVLS block computations.
### Expected behavior
I would expect `R 4.5.3` on macOS arm64 not to regress dramatically
relative to `R 4.5.2` for basic matrix products on the same hardware.
At minimum, if the BLAS target changed intentionally, I would expect that
change not to impose such a severe runtime penalty.
### Request
Could you please confirm whether the `libRblas.dylib -> libRblas.0.dylib`
change in the macOS arm64 `R 4.5.3` installer was intentional?
If intentional, is there a recommended way to restore the previous fast
vecLib-backed behavior?
If not intentional, this appears to be a macOS arm64 packaging/runtime
regression in `R 4.5.3`.
I can provide exact command transcripts and additional package-level
evidence if helpful.
## Local investigation notes
This report was motivated by a package-level timing cliff in `np`, but the
package code appears exonerated:
- the relevant `np` hot path remains unchanged in current source
- the same slowdown occurs across different `np` source snapshots when run
under `R 4.5.3`
- the same `np` source and same compiled package are fast again under `R
4.5.2`
The key practical interpretation is:
- not a package source regression
- likely an R macOS BLAS-linkage/runtime regression in `4.5.3`
## Relevant local files
- [R-4.5.2-arm64.pkg](/Users/jracine/Development/R-4.5.2-arm64.pkg)
- [R-4.5.3-arm64.pkg](/Users/jracine/Development/R-4.5.3-arm64.pkg)
[[alternative HTML version deleted]]
More information about the R-devel
mailing list