Package fio provides streaming I/O utilities with session management, automatic resource cleanup, and flexible storage backends (memory/file).
- Write Once, Dynamic Storage - Same code works with memory or file storage; automatically switches based on data size
- Type-safe Sources - Strongly typed input sources (files, URLs, bytes, readers, multipart)
- Session Management - Automatic temp file cleanup via IoManager/IoSession
- Dual Storage Backends - Memory or file-based storage with automatic spill-to-disk
- Reusable Inputs - Reset and reuse input streams multiple times
- ReaderAt Conversion - Convert any io.Reader to io.ReaderAt with memory/temp-file buffering
- Memory-mapped I/O - Optional mmap support on Unix systems for fast file reading
Write once, run with any storage backend. Your code doesn't change whether data is stored in memory or files:
// Same code - storage is determined by configuration, not code changes
output, _ := fio.Copy(ctx, fio.PathSource("input.txt"), fio.Out(".txt"))
// Read result the same way regardless of storage type
data, _ := output.Bytes() // works for both memory and file
reader, _ := output.OpenReader() // works for both memory and fileAutomatic storage selection based on data size:
// Manager with auto-threshold: small data → memory, large data → file
mgr, _ := fio.NewIoManager("./temp", fio.Memory,
fio.WithThreshold(10*1024*1024), // Switch to file at 10MB
)
// Your code stays the same - fio decides storage automatically
output, _ := fio.Copy(ctx, source, fio.Out(".json"))
// 1KB file → stored in memory
// 50MB file → stored in temp fileBenefits:
- No
if/elsefor memory vs file handling - Automatic cleanup of temp files via session
- Consistent API regardless of storage backend
- Optimal performance for each scenario
go get github.com/dreamph/fioimport "github.com/dreamph/fio"// Create an IoManager with memory storage
mgr, _ := fio.NewIoManager("./temp", fio.Memory)
defer mgr.Cleanup()
// Create a session
ses, _ := mgr.NewSession()
defer ses.Cleanup()
// Use session in context
ctx := fio.WithSession(context.Background(), ses)
// Copy from file to memory output
output, _ := fio.Copy(ctx, fio.PathSource("input.txt"), fio.Out(".txt"))
// Read the result
data, _ := output.Bytes()// Read and process a file
result, err := fio.Read(ctx, fio.PathSource("data.json"), func(r io.Reader) (*MyData, error) {
var data MyData
if err := json.NewDecoder(r).Decode(&data); err != nil {
return nil, err
}
return &data, nil
})// Transform input to output
output, err := fio.Process(ctx, fio.PathSource("input.txt"), fio.Out(".txt"),
func(r io.Reader, w io.Writer) error {
// Transform data from r to w
_, err := io.Copy(w, r)
return err
})Create type-safe input sources:
// From file path
src := fio.PathSource("/path/to/file.txt")
// From URL (auto-downloads)
src := fio.URLSource("https://example.com/file.txt")
// From bytes
src := fio.BytesSource([]byte("hello world"))
// From io.Reader
src := fio.ReaderSource(reader)
// From io.ReadCloser
src := fio.ReadCloserSource(readCloser)
// From *os.File
src := fio.FileSource(file)
// From multipart file header
src := fio.MultipartSource(fileHeader)
// From existing Output
src := fio.OutputSource(output)
// From existing Input
src := fio.InputSource(input)Manages temp directories and creates sessions:
// Create manager with file storage backend
mgr, err := fio.NewIoManager("./temp", fio.File,
fio.WithThreshold(1024*1024), // Auto-switch to file at 1MB
fio.WithSpillThreshold(64<<20), // Spill memory to file at 64MB
fio.WithMaxPreallocate(1<<20), // Cap pre-allocation at 1MB
fio.WithMmap(true), // Enable mmap on Unix
)
defer mgr.Cleanup()Represents a single operation scope with automatic cleanup:
ses, _ := mgr.NewSession()
defer ses.Cleanup() // Cleans up all temp files
// Create output within session
output, _ := ses.NewOut(fio.Out(".json"), 1024)ctx := fio.WithSession(context.Background(), ses)
// Retrieve session from context
ses := fio.Session(ctx)Configure output behavior:
// Basic output with extension
out := fio.Out(".json")
// Force memory storage
out := fio.Out(".json", fio.Memory)
// Force file storage
out := fio.Out(".json", fio.File)
// With spill threshold
out := fio.Out(".json", fio.WithSpillThreshold(32<<20))
// With output reuse (for repeated operations)
var cached *fio.Output
out := fio.Out(".json", fio.OutReuse(&cached))Open a source once and read multiple times:
// Open as reusable
input, _ := fio.OpenIn(ctx, fio.PathSource("data.txt"), fio.Reusable())
defer input.Close()
// First read
io.Copy(w1, input.Reader)
// Reset and read again
input.Reset()
io.Copy(w2, input.Reader)Convert streaming readers to random-access:
// Auto-buffers in memory or spills to temp file
result, _ := fio.ToReaderAt(ctx, reader,
fio.WithMaxMemoryBytes(8<<20), // Buffer up to 8MB in memory
fio.WithTempDir("./temp"), // Temp dir for spill files
)
defer result.Cleanup()
ra := result.ReaderAt()
size := result.Size()result, err := fio.Do(ctx, func(s *fio.Scope) (MyResult, error) {
r, err := s.Use(fio.PathSource("input.txt")) // Auto-cleanup on scope exit
if err != nil {
return MyResult{}, err
}
// Process r...
return result, nil
})output, err := fio.DoOut(ctx, fio.Out(".txt"),
func(ctx context.Context, s *fio.OutScope, w io.Writer) error {
r, err := s.Use(fio.PathSource("input.txt"))
if err != nil {
return err
}
_, err := io.Copy(w, r)
return err
})output, metadata, err := fio.DoOutResult(ctx, fio.Out(".txt"),
func(ctx context.Context, s *fio.OutScope, w io.Writer) (*Metadata, error) {
r, size, err := s.UseSized(fio.PathSource("input.txt"))
if err != nil {
return nil, err
}
_, err := io.Copy(w, r)
if err != nil {
return nil, err
}
m := Metadata{Size: size}
return &m, nil
})// Get size from source (may open the source)
size, _ := fio.Size(ctx, src)
// Get size without opening (when possible)
size := fio.SizeFromStream(src)
// Get size from any type
size := fio.SizeAny(reader)err := fio.ReadLines(ctx, fio.PathSource("file.txt"), func(line string) error {
fmt.Println(line)
return nil
})
// Shorthand for file path
err := fio.ReadFileLines(ctx, "file.txt", func(line string) error {
return nil
})// Write reader to file
n, err := fio.WriteFile(reader, "/path/to/output.txt")
// Write source to file
n, err := fio.WriteStreamToFile(src, "/path/to/output.txt")// Get data as bytes
data, _ := output.Bytes()
// Get raw byte slice (memory storage only)
data := output.Data()
// Open reader
r, _ := output.OpenReader()
defer r.Close()
// Open writer
w, _ := output.OpenWriter(sizeHint)
defer w.Close()
// Write to io.Writer
n, _ := output.WriteTo(writer)
// Save to file
err := output.SaveAs("/path/to/file.txt")
// Keep file after session cleanup
output.Keep()
// Get file path (file storage only)
path := output.Path()
// Get storage type
st := output.StorageType()
// Get size
size := output.Size()fio.Json // ".json"
fio.Csv // ".csv"
fio.Txt // ".txt"
fio.Xml // ".xml"
fio.Pdf // ".pdf"
fio.Docx // ".docx"
fio.Xlsx // ".xlsx"
fio.Pptx // ".pptx"
fio.Jpg // ".jpg"
fio.Jpeg // ".jpeg"
fio.Png // ".png"
fio.Zip // ".zip"// Convert MB to bytes
bytes := fio.MB(10) // 10485760
// Convert format to extension
ext := fio.ToExt("json") // ".json"
// Join cleanup functions
cleanup := fio.JoinCleanup(fn1, fn2, fn3)
defer cleanup()
// Safe close (ignores nil)
fio.SafeClose(closer)// Configure custom HTTP client (for URL sources)
fio.Configure(fio.NewConfig(&http.Client{
Timeout: 60 * time.Second,
}))fio.ErrNilSource // nil source provided
fio.ErrIoManagerClosed // manager is closed
fio.ErrIoSessionClosed // session is closed
fio.ErrDownloadFailed // URL download failed
fio.ErrNoSession // session is nil
fio.ErrFileStorageUnavailable // file storage requires directory
fio.ErrInvalidSessionType // invalid session type
fio.ErrNilFunc // function is nil
fio.ErrEmptyPath // empty path string
fio.ErrEmptyURL // empty URL string
fio.ErrOutputCleaned // output already cleaned up
fio.ErrNilOutHandle // nil OutHandle
fio.ErrNilOutScope // nil out-scope
fio.ErrNewOutMultiple // NewOut called more than once
fio.ErrOutReuseRequiresPtr // OutReuse requires output pointer
fio.ErrCannotGetReaderAt // reader does not support ReaderAt
fio.ErrInputNotReusable // input is not reusable
fio.ErrCannotResetInput // input reset failed
fio.ErrToReaderAtNilReader // ToReaderAt called with nil readerUse errors.Is to check wrapped errors:
if errors.Is(err, fio.ErrDownloadFailed) {
// handle URL download failures
}- Memory-mapped I/O: Available on Darwin, Linux, FreeBSD, NetBSD, OpenBSD
- Other platforms: Falls back to standard file I/O
Benchmark comparing fio and normal (standard library io.Copy) on Apple M2 Max.
| Symbol | Meaning |
|---|---|
| ⚡ | Fastest speed |
| 💾 | Lowest memory |
| 🏆 | Best overall |
| Size | Method | Speed | Throughput | Memory | Allocs | Notes |
|---|---|---|---|---|---|---|
| 1KB | normal | 201 ns | 5,084 MB/s | 1,152 B | 5 | copies data |
| fio | 130 ns | 7,865 MB/s | 249 B | 3 | 🏆⚡💾 zero-copy | |
| 1MB | normal | 115 µs | 9,139 MB/s | 1.0 MB | 5 | copies data |
| fio | 137 ns | 7,655,321 MB/s | 241 B | 3 | 🏆⚡💾 zero-copy | |
| 10MB | normal | 604 µs | 17,362 MB/s | 10 MB | 5 | copies data |
| fio | 144 ns | 72,978,112 MB/s | 241 B | 3 | 🏆⚡💾 zero-copy | |
| 100MB | normal | 3.61 ms | 29,010 MB/s | 100 MB | 5 | copies data |
| fio | 133 ns | 788,153,403 MB/s | 240 B | 3 | 🏆⚡💾 zero-copy |
| Size | Method | Speed | Throughput | Memory | Allocs | Notes |
|---|---|---|---|---|---|---|
| 1KB | normal | 117 µs | 8.7 MB/s | 743 B | 9 | ⚡ |
| fio | 139 µs | 7.4 MB/s | 727 B | 11 | 💾 | |
| 1MB | normal | 379 µs | 2,765 MB/s | 744 B | 9 | |
| fio | 268 µs | 3,914 MB/s | 720 B | 11 | 🏆⚡💾 | |
| 10MB | normal | 2.80 ms | 3,751 MB/s | 746 B | 9 | |
| fio | 2.18 ms | 4,818 MB/s | 709 B | 11 | 🏆⚡💾 | |
| 100MB | normal | 23.1 ms | 4,540 MB/s | 781 B | 9 | |
| fio | 22.8 ms | 4,591 MB/s | 718 B | 11 | 🏆⚡💾 |
| Size | Method | Speed | Throughput | Memory | Allocs | Notes |
|---|---|---|---|---|---|---|
| 1KB | normal | 20.7 µs | 49.4 MB/s | 34,096 B | 8 | |
| fio | 17.9 µs | 57.2 MB/s | 1,965 B | 13 | 🏆⚡💾 | |
| 1MB | normal | 797 µs | 1,315 MB/s | 2.0 MB | 13 | |
| fio | 108 µs | 9,685 MB/s | 1.0 MB | 13 | 🏆⚡💾 | |
| 10MB | normal | 2.66 ms | 3,939 MB/s | 32 MB | 17 | |
| fio | 1.33 ms | 7,893 MB/s | 10 MB | 13 | 🏆⚡💾 | |
| 100MB | normal | 16.7 ms | 6,290 MB/s | 256 MB | 20 | |
| fio | 16.3 ms | 6,432 MB/s | 100 MB | 13 | 🏆⚡💾 |
| Size | Method | Speed | Throughput | Memory | Allocs | Notes |
|---|---|---|---|---|---|---|
| 1KB | normal | 144 µs | 7.1 MB/s | 33,696 B | 13 | ⚡💾 |
| fio | 166 µs | 6.2 MB/s | 33,730 B | 17 | ||
| 1MB | normal | 513 µs | 2,043 MB/s | 33,712 B | 13 | ⚡💾 |
| fio | 573 µs | 1,830 MB/s | 33,730 B | 17 | ||
| 10MB | normal | 4.32 ms | 2,425 MB/s | 33,712 B | 13 | ⚡💾 |
| fio | 4.51 ms | 2,326 MB/s | 33,721 B | 17 | ||
| 100MB | normal | 44.0 ms | 2,385 MB/s | 33,717 B | 13 | |
| fio | 41.1 ms | 2,553 MB/s | 33,723 B | 17 | 🏆⚡ |
| Size | Method | Speed | Throughput | Memory | Allocs | Notes |
|---|---|---|---|---|---|---|
| 1KB | normal | 39.6 ns | 25,862 MB/s | 64 B | 2 | ⚡💾 |
| fio | 65.0 ns | 15,755 MB/s | 136 B | 3 | ||
| 1MB | normal | 43.5 ns | 24.1 TB/s | 64 B | 2 | ⚡💾 zero-copy discard |
| fio | 72.3 ns | 14.5 TB/s | 136 B | 3 | ||
| 10MB | normal | 41.0 ns | 256 TB/s | 64 B | 2 | ⚡💾 zero-copy discard |
| fio | 68.1 ns | 154 TB/s | 136 B | 3 | ||
| 100MB | normal | 39.9 ns | 2.6 PB/s | 64 B | 2 | ⚡💾 zero-copy discard |
| fio | 66.2 ns | 1.6 PB/s | 136 B | 3 |
| Size | Method | Speed | Throughput | Memory | Allocs | Notes |
|---|---|---|---|---|---|---|
| 1KB | normal | 15.7 µs | 65.1 MB/s | 240 B | 4 | ⚡💾 |
| fio | 16.5 µs | 62.1 MB/s | 553 B | 9 | ||
| 1MB | normal | 98.6 µs | 10,630 MB/s | 240 B | 4 | ⚡💾 |
| fio | 99.7 µs | 10,516 MB/s | 553 B | 9 | ||
| 10MB | fio | 839 µs | 12,488 MB/s | 554 B | 9 | ⚡ |
| normal | 844 µs | 12,424 MB/s | 244 B | 4 | 💾 | |
| 100MB | normal | 11.1 ms | 9,407 MB/s | 245 B | 4 | 💾 |
| fio | 11.5 ms | 9,100 MB/s | 583 B | 9 |
| Scenario | Winner | Why |
|---|---|---|
| bytes → memory | 🏆 fio | Zero-copy, fastest in all sizes, minimal memory |
| bytes → file | 🏆 fio | Faster at 1MB/10MB/100MB (28-41% faster); slightly slower at 1KB |
| file → memory | 🏆 fio | Faster for all sizes with 50% less memory |
| file → file | mixed | normal faster at small sizes (4-12%); fio faster at 100MB (7%) |
| bytes → read-only | normal | Both near-instant; fio has minimal overhead (~1.6x) |
| file → read-only | ~equal | Comparable performance; fio slightly more allocations |
- fio bytes→memory is zero-copy - constant-time regardless of data size
- fio bytes→file is optimized - 28-41% faster than normal for 1MB+ files
- fio file→memory is efficient - 7x faster at 1MB with 50% less memory
- file→file is competitive - fio slightly slower at small files (~10%), faster at large files
- read-only is near-instant for bytes - both normal and fio use zero-copy to io.Discard
- file read-only is I/O bound - ~10-12 GB/s throughput, both methods comparable
# Basic benchmark
go test -bench=BenchmarkCompareFio -benchmem -benchtime=3s
# With mmap enabled (Unix only)
FIO_BENCH_USE_MMAP=true go test -bench=BenchmarkCompareFio -benchmem -benchtime=3s