fastwalk: Parallel Directory Traversal for Go

repository·master·Indexed 40 days ago

https://github.com/charlievieth/fastwalk

A high-performance, parallel alternative to filepath.WalkDir and godirwalk for Go. It utilizes multiple goroutines to concurrently stat the filesystem and execute callbacks across macOS, Linux, and Windows. Features include symbolic link following via Config, duplicate file and directory filtering, and custom sorting modes (SortLexical, SortFilesFirst, SortDirsFirst). Note that the provided walkFn must be thread-safe due to concurrent execution.

Tokens
5K
Snippets
20
Records
24
Agent score
49%

What's inside fastwalk

  1. Use fastwalk for parallel directory traversal

    master

    The fastwalk package provides a high-performance, parallel version of filepath.WalkDir. It uses multiple goroutines to concurrently stat the filesystem and execute the callback function, making it significantly faster than the standard library on macOS, Linux, and Windows.

    Important Requirement: Because fastwalk executes the callback function concurrently, your walkFn (the function that processes each file/directory) must be safe for concurrent use (thread-safe).

    // Example of basic usage
    conf := &fastwalk.Config{
        Follow: true, // Set to true to follow symbolic links
    }
    
    walkFn := func(path string, d fs.DirEntry, err error) error {
        // Your logic here. 
        // MUST BE THREAD-SAFE
        fmt.Println(path)
        return nil
    }
    
    err := fastwalk.Walk(conf, "/path/to/root", walkFn)
  2. Configure fastwalk via Config

    master

    You can control the behavior of the traversal using the fastwalk.Config struct. The primary configuration option is:

    • Follow (bool): If set to true, the walker will follow symbolic links during traversal.
    conf := fastwalk.Config{
        Follow: true,
    }
  3. Control directory entry order with SortMode

    master

    The SortMode determines how entries within a single directory are ordered before being visited. While the overall traversal remains non-deterministic due to parallelism, sorting provides more predictable output for tools like file searchers.

    SortMode Modes

    • SortNone: (Default) No sorting. Entries are visited in the order provided by the OS.
    • SortLexical: Entries are sorted by name (case-sensitive).
    • SortFilesFirst: Regular files and non-directories are visited before directories. Within each group, entries are sorted by name. This is ideal for tools that print output.
    • SortDirsFirst: Directories are visited first, then regular files, then everything else. This can improve performance by enqueuing sub-directories earlier, preventing worker starvation.
    // Example: Using SortFilesFirst for cleaner output
    conf := &fastwalk.Config{
    	Sort: fastwalk.SortFilesFirst,
    }
  4. Configure Walk behavior with Config

    master

    The Config struct allows you to tune the performance and behavior of the Walk function. If nil is passed to Walk, DefaultConfig is used.

    FieldTypeDescription
    FollowboolIf true, follows symbolic links and automatically detects/ignores infinite loops.
    ToSlashboolIf true, joins all paths using forward slashes / instead of the OS default. Useful for Windows users in WSL/MSYS environments.
    SortSortModeDetermines the order entries in a directory are visited (see SortMode).
    NumWorkersintNumber of parallel worker goroutines. If ≤ 0, DefaultNumWorkers() is used.
    MaxDepthintLimits traversal depth. 0 or less disables the limit.
    conf := &fastwalk.Config{
    	Follow:     true,
    	NumWorkers: 16,
    	Sort:       fastwalk.SortFilesFirst,
    	MaxDepth:   5,
    }
  5. Determine optimal worker count with DefaultNumWorkers

    master

    The DefaultNumWorkers() function calculates a sensible number of goroutines for parallel traversal based on the system's CPU count and operating system.

    • General: Clamps GOMAXPROCS to a range of 4 to 32.
    • Darwin (macOS): Uses a specialized heuristic because high concurrency can degrade IO performance on APFS. It attempts to use the number of performance cores on modern ARM Macs, or falls back to specific counts (4, 6, or 10) based on core count for Intel Macs.
    • Windows/Linux: Uses the clamped GOMAXPROCS value.

    You can override this by setting the NumWorkers field in your Config object.

  6. Use EntryFilter to prevent duplicate file processing on Windows

    master

    On Windows, EntryFilter provides a mechanism to track and filter filesystem entries using unique file identifiers (Volume Serial Number and File Index). This is useful for preventing infinite loops or redundant processing caused by symbolic links or hard links during filesystem traversal.

    To use it, instantiate a new filter with NewEntryFilter() and call its Entry(path string, _ fs.DirEntry) bool method. The Entry method returns true if the file has been seen before, and false if it is a new entry.

    import "github.com/charlievieth/fastwalk"
    
    filter := fastwalk.NewEntryFilter()
    
    // Inside a walk function:
    // If Entry returns true, the file has already been encountered.
    if filter.Entry(currentPath, entry) {
        // Skip this entry to avoid duplicates
        return
    }
  7. Use StatDirEntry to get FileInfo from a DirEntry

    master

    The StatDirEntry function provides a way to obtain an fs.FileInfo for a given directory entry.

    If the provided de is a fastwalk.DirEntry, the function uses its Stat method, which may return a cached FileInfo to improve performance. If you require a fresh os.Stat call that bypasses any potential caching, you should call os.Stat directly instead.

    If de is not a fastwalk.DirEntry, the function falls back to calling os.Stat(path).

    import (
    	"io/fs"
    	"os"
    	"github.com/charlievieth/fastwalk"
    )
    
    // Example usage within a walk function
    // path is the current file path, de is the fs.DirEntry provided by the walker
    info, err := fastwalk.StatDirEntry(path, de)
    if err != nil {
    	// handle error
    }
    // use info (fs.FileInfo)
  8. Ignore permission errors when walking

    master

    Use IgnorePermissionErrors to wrap an fs.WalkDirFunc. This wrapper intercepts fs.ErrPermission (or any error where os.IsPermission(err) is true) and returns nil instead of passing the error to the caller. This allows the directory traversal to continue even when certain files or directories are inaccessible.

    Key Behaviors:

    • Unlike the duplicate-checking wrappers, the returned fs.WalkDirFunc may be reused safely.
    // Example usage wrapping a standard walk function
    walkFn := func(path string, d fs.DirEntry, err error) error {
        fmt.Println(path)
        return nil
    }
    
    wrappedWalk := fastwalk.IgnorePermissionErrors(walkFn)
    // Use wrappedWalk in your Walk implementation
  9. Ignore duplicate directories when walking

    master

    Use IgnoreDuplicateDirs to wrap an fs.WalkDirFunc. This wrapper allows the walker to follow symbolic links while preventing infinite loops or redundant processing by skipping directories that have already been traversed.

    Key Behaviors:

    • If a symlink points to a directory that has already been visited, the directory is skipped.
    • The Follow setting in Config does not affect behavior when using this wrapper.
    • Warning: The order of traversal is undefined. In a scenario where multiple symlinks point to the same directory, only one of the paths (the original or one of the links) will be traversed.
    • Warning: The returned function should generally not be reused. Reusing it will cause any previously visited file to be skipped in subsequent walks.
    • The original walkFn is still called for skipped directories to ensure error handling remains functional.
    // Example usage wrapping a standard walk function
    walkFn := func(path string, d fs.DirEntry, err error) error {
        fmt.Println(path)
        return nil
    }
    
    wrappedWalk := fastwalk.IgnoreDuplicateDirs(walkFn)
    // Use wrappedWalk in your Walk implementation
  10. Use EntryFilter to prevent symlink loops and duplicate processing

    master

    The EntryFilter type is used to track visited directory entries. It helps detect and avoid symlink loops or prevent processing the same file multiple times during a filesystem walk. It works by tracking unique file identifiers consisting of a device number (Dev) and an inode number (Ino).

    To use it, initialize a filter with NewEntryFilter() and check entries using the Entry method during your walk process.

    import "github.com/charlievieth/fastwalk"
    
    filter := fastwalk.NewEntryFilter()
    // Inside your walk function:
    if filter.Entry(path, de) {
        // This entry has been seen before (e.g., a symlink loop or duplicate)
        return
    }