faf

package module
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 17, 2025 License: Apache-2.0 Imports: 8 Imported by: 3

README

faf Fire and Forget (Crash Later)

Coverage

faf is a Go module for Linux that springs from working on the system discovery service lxkns. Some of the hot paths in this and other services happen to comb the procfs process filesystem extensively, such as to gather process and task details, up to CPU affinity lists. As many elements in procfs tend to be of comparably small sizes, constantly allocating new buffer memory to read and process them, and then throwing away the buffers without any need or reusing the buffers puts unnecessary pressure on the GC and also results in avoidable CPU load.

[!CAUTION]

If you stumbled upon faf and just want to use it because you "want to make my programs faster", stay away. As with any optimizations as the ones provided by faf you need to have hot paths that really benefit from optimizing them under clearly understood constraints.

Go's standard library has to cover all walks of life in order to provide a high quality of (Go) living. Yet hot paths of very specific use cases benefit from running with open scissors. This is where faf comes in: reduce dynamic allocations considerably where they are basically just trashing the GC by trading in general comfort by deferring heap escapes to user code and only if the user code really needs them. faf optimizes for no error reporting, because in some use cases the hot paths never reported them, but just skipped. That's where faf draws its name "Fire and Forget (Crash Later)" from.

Interestingly, now that Go 1.23 introduced the iterator pattern, we can elegantly use optimizations such as when reading directories. Before, even skipping unnecessary sorting, the canonical form was along these lines:

// before with lots of smaller allocations;
// error handling was used to just bail out, but never for reporting.
dir, _ := os.Open("/my/directory")
defer dir.Close()
entries, _ := dir.ReadDir(-1)
for entry := range entries {
    _ = entry
}

We now turn this into a loop over a dedicated iterator that avoids having to read all entries first, thus avoiding creating heap trash:

// faf with Go 1.23 or later
for entry := range faf.ReadDir("/my/directory") {
    _ = entry
}

If the directory cannot be read, then the loop simply doesn't "loop" (that's what I think loops do for a living).

And to provide some figures, while benchmarks show a 25% increase in execution speed on reading directories (which you can easily eat up in your loop body), the real benefit is a constant single allocation for a full directory read. And while os.File.ReadDir runs with O(n) heap allocation, where n is the number of directory entries read, faf.ReadDir just needs a single small heap allocation. This is perfect for these use cases where you just process the directory entries and then forget them.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseHexUint added in v0.2.1

func ParseHexUint(b []byte) (uint64, bool)

ParseHexUint parses the given byte slice with a hexadecimal number, returning its uint64 value and ok, or a zero value and false in case of error. It is an error for the given decimal number overflows the uint64 range or if there bytes for characters other than "0" to "9", “a” to “f”, or “A” to “F” are encountered.

func ParseUint

func ParseUint(b []byte) (uint64, bool)

ParseUint parses the given byte slice with a decimal number, returning its uint64 value and ok, or a zero value and false in case of error. It is an error for the given decimal number overflows the uint64 range or if there bytes for characters other than "0" to "9" are encountered.

Compared with strconv.ParseUint, this version is around 50% faster, albeit that is moaning at high level. Both versions do not allocate on the heap in case of non-errors, and as our ParseUint does returns a bool instead of an error message, it is without heap allocation also in case of error.

Please note that Go's strconv.ParseUint has been optimized so that the compiler sees that while the input string escapes to the error value, ParseUint copies the input string to the error message so that in turn the compiler can now use optimized []byte to string conversions.

func ReadDir

func ReadDir(name string) iter.Seq[DirEntry]

ReadDir returns an iterator over entries of the specified directory. In case the specified directory does not exist, the iterator does not produce any entries.

Please note that ReadDir produces DirEntry directory entries where their Name fields are stored as []byte, referencing the underlying raw directory entry. The lifetime of the Name field is thus limited to the body of the iteration loop. This design avoids heap allocations and puts the need, if any, into the hands of the loop body.

Due to its Go iterator design, ReadDir needs only a single allocation per full iteration, as opposed to O(n) for os.File.ReadDir. A second allocation only happens for the first run or if there are concurrent directory reads going on and a new directory entries read buffer needs to be allocated. This directory entries buffer allocation behavior mimics the one exhibited by os.File.ReadDir.

ReadDir is roughly 25% faster compared to os.File.ReadDir and only needs a constant(!) 24 B/op heap allocation, as opposed to O(n) heap allocations for the stdlib's ReadDir.

func ReadFile

func ReadFile(name string, buffer []byte) ([]byte, bool)

ReadFile reads the contents of the named file into the supplied buffer, growing the buffer as necessary, returning the contents and true. Only the capacity of the passed buffer matters, the buffer's current length gets ignored. If the file cannot be read for whatever reason, ReadFile returns false. Please note that if the read problem appears in midstream, the buffer might have been already reallocated and thus the most recent buffer is returned even in case of error.

The buffer underlying the returned contents can be same buffer if the capacity was sufficient, otherwise it will be a new backing buffer.

As with os.ReadFile, reaching the end of the file is not considered to be an error but normal operation, and thus not reported.

Types

type Bytestring

type Bytestring struct {
	// contains filtered or unexported fields
}

Bytestring provides efficient parsing of text lines in form of byte slices, assuming contents to be in ASCII and treating UTF-8 as individual byte-sized characters.

We rely on the compiler emitting efficient machine code for determining len(b), so we don't need to store it explicitly, but use the length already stored as part of the byte slice anyway. Looking at the generated x86-64 machine code using a tools like the “Compiler Explorer” confirms efficient code where len(bs.b) results in a single MOVQ instruction. For arm64 “all we get” is also a single MOVD instruction.

func NewBytestring

func NewBytestring(b []byte) *Bytestring

NewBytestring returns a new Bytestring object for parsing the supplied text line as a byte slice. It is small enough to be allocated on the stack, avoiding heap allocations.

func (*Bytestring) EOL

func (b *Bytestring) EOL() (eol bool)

EOL returns true if the parsing has reached the end of the byte string, otherwise false.

func (*Bytestring) HexUint64 added in v0.2.1

func (b *Bytestring) HexUint64() (num uint64, ok bool)

HexUint64 parses the hexadecimal number starting in the buffer at the current position until a character other than 0-9, a-f, or A-F is encountered, or EOL. The number must consist of at least a single hex digit. If successful, HexUint64 returns the number and true; otherwise zero and false. Overflowing HexUint64 is also considered to be an error, returning zero and false in this case.

func (*Bytestring) Next

func (b *Bytestring) Next() (ch byte, ok bool)

Next returns the next byte from the bytestring and ok true, otherwise ok false.

func (*Bytestring) NumFields

func (b *Bytestring) NumFields() (num int)

NumFields returns the number of fields found in the line, starting from the current position. NumFields does not change the current position. Fields are made of sequences of characters excluding the space character. Fields are separated by one or more spaces.

func (*Bytestring) SkipSpace

func (b *Bytestring) SkipSpace() (eol bool)

SkipSpace skips over any space 0x20 characters until either reaching the first non-space character, or EOF. When reaching EOL, it returns true.

func (*Bytestring) SkipText

func (b *Bytestring) SkipText(s string) (ok bool)

SkipText skips the text s in the buffer at the current position if present, returning ok true. Otherwise, returns ok false and the buffer's parsing position is left unchanged.

func (*Bytestring) Uint64

func (b *Bytestring) Uint64() (num uint64, ok bool)

Uint64 parses the decimal number starting in the buffer at the current position until a character other than 0-9 is encountered, or EOL. The number must consist of at least a single digit. If successful, Uint64 returns the number and true; otherwise zero and false. Overflowing Uint64 is also considered to be an error, returning zero and false in this case.

type DirEntry

type DirEntry struct {
	Ino  uint64
	Name []byte
	Type DirEntryType
}

DirEntry represents a single directory entry with only name, type and inode number.

Please note that in order to avoid heap escaping allocations, DirEntry represents the name in the directory entry as a slice of bytes with its backing comes from the underlying raw getdents64 syscall. This allows us to defer converting the name to a string to user code, where there is a much better chance of compiler optimizations for conversions between strings and byte slices.

See also getdents(2) for background details.

func (DirEntry) IsBlockDev

func (d DirEntry) IsBlockDev() bool

IsBlockDev returns true if this directory entry is about a block device.

func (DirEntry) IsCharDev

func (d DirEntry) IsCharDev() bool

IsCharDev returns true if this directory entry is about a character device.

func (DirEntry) IsDir

func (d DirEntry) IsDir() bool

IsDir returns true if this directory entry is about a directory.

func (DirEntry) IsPipe

func (d DirEntry) IsPipe() bool

IsPipe returns true if this directory entry is about a pipe, also known as “FIFO”.

func (DirEntry) IsRegular

func (d DirEntry) IsRegular() bool

IsRegular returns true if this directory entry is about a regular file.

func (DirEntry) IsSocket

func (d DirEntry) IsSocket() bool

IsSocket returns true if this directory entry is about a socket.

func (d DirEntry) IsSymlink() bool

IsSymlink returns true if this directory entry is about a symbolic link.

func (DirEntry) String

func (d DirEntry) String() string

String returns a compact textual description of this DirEntry.

type DirEntryType

type DirEntryType uint8
const (
	DirEntryFIFO    DirEntryType = unix.DT_FIFO
	DirEntryChar    DirEntryType = unix.DT_CHR
	DirEntryBlock   DirEntryType = unix.DT_BLK
	DirEntryDir     DirEntryType = unix.DT_DIR
	DirEntryRegular DirEntryType = unix.DT_REG
	DirEntrySymlink DirEntryType = unix.DT_LNK
	DirEntrySocket  DirEntryType = unix.DT_SOCK
)

func (DirEntryType) String

func (t DirEntryType) String() string

type RawDirEntry64

type RawDirEntry64 []byte

RawDirEntry64 provides convenient access to a directory entry within a byte slice, as returned by the getdents64() syscall.

See also getdents(2) for background details.

func (RawDirEntry64) Ino

func (d RawDirEntry64) Ino() (uint64, bool)

Ino returns the inode number of this directory entry and ok; otherwise, it returns false.

func (RawDirEntry64) Len

func (d RawDirEntry64) Len() (uint64, bool)

Len returns the length of this directory entry, or false.

func (RawDirEntry64) Name

func (d RawDirEntry64) Name() ([]byte, bool)

Name returns the name of the directory entry, or false. The name returned references the underlying directory entry and thus becomes invalid as soon as the underlying directory entry gets overwritten.

func (RawDirEntry64) Type

func (d RawDirEntry64) Type() (DirEntryType, bool)

Type returns the type of this directory entry, or false.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL