Skip to content

Commit 9ce32dc

Browse files
committed
Use fadvise(FADV_WILLNEED) for page warming, add ctx cancellation and cache exclusions
1 parent 7d17723 commit 9ce32dc

3 files changed

Lines changed: 60 additions & 15 deletions

File tree

gradlecache/advise_default.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//go:build !linux
2+
3+
package gradlecache
4+
5+
import (
6+
"io"
7+
"os"
8+
)
9+
10+
// adviseWillNeed falls back to reading the entire file on non-Linux platforms
11+
// where fadvise is not available.
12+
func adviseWillNeed(path string) {
13+
f, err := os.Open(path)
14+
if err != nil {
15+
return
16+
}
17+
_, _ = io.Copy(io.Discard, f)
18+
_ = f.Close()
19+
}

gradlecache/advise_linux.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package gradlecache
2+
3+
import (
4+
"os"
5+
6+
"golang.org/x/sys/unix"
7+
)
8+
9+
// adviseWillNeed hints to the kernel that the file will be read soon,
10+
// triggering async readahead without blocking on actual I/O.
11+
func adviseWillNeed(path string) {
12+
f, err := os.Open(path)
13+
if err != nil {
14+
return
15+
}
16+
_ = unix.Fadvise(int(f.Fd()), 0, 0, unix.FADV_WILLNEED)
17+
_ = f.Close()
18+
}

gradlecache/save.go

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func Save(ctx context.Context, cfg SaveConfig) error {
200200
if !cfg.SkipWarm {
201201
log.Debug("warming page cache")
202202
warmStart := time.Now()
203-
warmPageCache(sources)
203+
warmPageCache(ctx, sources)
204204
log.Debug("page cache warm", "duration", time.Since(warmStart).Round(time.Millisecond))
205205
} else {
206206
log.Debug("skipping page cache warm (SkipWarm=true)")
@@ -521,33 +521,41 @@ func matchesAny(name string, patterns []string) bool {
521521
return false
522522
}
523523

524-
// warmPageCache reads every regular file under each TarSource in parallel,
525-
// faulting pages into the OS page cache before tar reads them sequentially.
526-
// On cold NVMe storage with many small files (e.g. 200K Gradle cache entries),
527-
// tar is limited to ~80 MB/s by per-file IOPS overhead. Warming the cache with
528-
// parallel readers saturates IOPS up front so that tar subsequently reads at
529-
// memory speed (~1300 MB/s).
530-
func warmPageCache(sources []TarSource) {
524+
// warmPageCache issues POSIX_FADV_WILLNEED on every regular file under each
525+
// TarSource in parallel, hinting the kernel to start async readahead before
526+
// tar reads them sequentially. On cold NVMe storage with many small files
527+
// (e.g. 200K Gradle cache entries), tar is limited to ~80 MB/s by per-file
528+
// IOPS overhead. Warming the cache saturates IOPS up front so that tar
529+
// subsequently reads at memory speed (~1300 MB/s). On non-Linux platforms,
530+
// falls back to reading files into the page cache directly.
531+
func warmPageCache(ctx context.Context, sources []TarSource) {
531532
concurrency := min(runtime.GOMAXPROCS(0)*2, 32)
532533
sem := make(chan struct{}, concurrency)
533534
var wg sync.WaitGroup
534535

535536
for _, src := range sources {
536537
root := filepath.Join(src.BaseDir, src.Path)
537538
_ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
538-
if err != nil || !d.Type().IsRegular() {
539+
if ctx.Err() != nil {
540+
return filepath.SkipAll
541+
}
542+
if err != nil {
543+
return nil
544+
}
545+
if d.IsDir() {
546+
if IsExcludedCache(d.Name()) {
547+
return filepath.SkipDir
548+
}
549+
return nil
550+
}
551+
if !d.Type().IsRegular() || IsExcludedCache(d.Name()) {
539552
return nil
540553
}
541554
sem <- struct{}{}
542555
wg.Add(1)
543556
go func() {
544557
defer func() { <-sem; wg.Done() }()
545-
f, err := os.Open(path)
546-
if err != nil {
547-
return
548-
}
549-
_, _ = io.Copy(io.Discard, f)
550-
_ = f.Close()
558+
adviseWillNeed(path)
551559
}()
552560
return nil
553561
})

0 commit comments

Comments
 (0)