@@ -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