This is an automated email from the ASF dual-hosted git repository.
laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new c18cb4fdb perf(io): avoid rebuilding MemFS WalkDir path prefixes
(#2205)
c18cb4fdb is described below
commit c18cb4fdb8461255ae34ee7738a1801959a5974f
Author: Minh Vu <[email protected]>
AuthorDate: Mon Oct 19 14:00:33 2026 +0200
perf(io): avoid rebuilding MemFS WalkDir path prefixes (#2205)
---
io/mem.go | 20 ++++++++++----------
io/mem_bench_test.go | 23 ++++++++++++++++++++---
io/mem_test.go | 41 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 71 insertions(+), 13 deletions(-)
diff --git a/io/mem.go b/io/mem.go
index e8687a6e9..e7244fb40 100644
--- a/io/mem.go
+++ b/io/mem.go
@@ -136,18 +136,18 @@ func (m *MemFS) WalkDir(root string, fn fs.WalkDirFunc)
error {
addEntry(walkEntry{path: root, isDir: true})
relative := strings.TrimPrefix(key, root+"/")
- parts := strings.Split(relative, "/")
- for i := range parts {
- entryPath := strings.Join(parts[:i+1], "/")
- if root != "" {
- entryPath = root + "/" + entryPath
- }
- entry := walkEntry{path: entryPath, isDir: i <
len(parts)-1}
- if !entry.isDir {
- entry.size = int64(len(data))
+ // Keep empty-root paths relative (e.g. "/a/b" is walked as
"a/b").
+ filePath, start := relative, 0
+ if root != "" {
+ filePath = key
+ start = len(key) - len(relative)
+ }
+ for i := range len(relative) {
+ if relative[i] == '/' {
+ addEntry(walkEntry{path: filePath[:start+i],
isDir: true})
}
- addEntry(entry)
}
+ addEntry(walkEntry{path: filePath, size: int64(len(data))})
}
m.mu.RUnlock()
if len(entriesByPath) == 0 {
diff --git a/io/mem_bench_test.go b/io/mem_bench_test.go
index 50ef091fc..a631094ef 100644
--- a/io/mem_bench_test.go
+++ b/io/mem_bench_test.go
@@ -20,24 +20,41 @@ package io_test
import (
"fmt"
"io/fs"
+ "strings"
"testing"
icebergio "github.com/apache/iceberg-go/io"
)
func BenchmarkMemFSWalkDir(b *testing.B) {
+ benchmarkMemFSWalkDir(b, func(i int) string {
+ return
fmt.Sprintf("mem://bucket/root/dir-%03d/file-%05d.parquet", i%128, i)
+ })
+}
+
+func BenchmarkMemFSWalkDirDeep(b *testing.B) {
+ benchmarkMemFSWalkDir(b, func(i int) string {
+ parts := make([]string, 8)
+ for j := range parts {
+ parts[j] = fmt.Sprintf("dir-%03d", (i+j*13)%128)
+ }
+
+ return fmt.Sprintf("mem://bucket/root/%s/file-%05d.parquet",
strings.Join(parts, "/"), i)
+ })
+}
+
+func benchmarkMemFSWalkDir(b *testing.B, nameForFile func(int) string) {
+ b.Helper()
for _, fileCount := range []int{100, 1_000, 5_000} {
b.Run(fmt.Sprintf("files=%d", fileCount), func(b *testing.B) {
memIO := icebergio.NewMemFS()
for i := range fileCount {
- name :=
fmt.Sprintf("mem://bucket/root/dir-%03d/file-%05d.parquet", i%128, i)
- if err := memIO.WriteFile(name, nil); err !=
nil {
+ if err := memIO.WriteFile(nameForFile(i), nil);
err != nil {
b.Fatal(err)
}
}
b.ReportAllocs()
- b.ResetTimer()
for b.Loop() {
if err := memIO.WalkDir("mem://bucket/root",
func(_ string, _ fs.DirEntry, err error) error {
return err
diff --git a/io/mem_test.go b/io/mem_test.go
index f396acbaa..cdc62dba5 100644
--- a/io/mem_test.go
+++ b/io/mem_test.go
@@ -353,3 +353,44 @@ func TestMemIO_WalkDirHonorsSkipAtRootAndSkipAll(t
*testing.T) {
assert.Equal(t, []string{"mem://bucket/root"}, walked)
}
}
+
+func TestMemIO_WalkDirNestedPrefixes(t *testing.T) {
+ memIO := icebergio.NewMemFS()
+ for name, data := range map[string][]byte{
+ "mem://bucket/root/a/b/c/deep.parquet": []byte("deep"),
+ "mem://bucket/root/a/first.parquet": []byte("first!"),
+ "mem://bucket/root/ab/second.parquet": []byte("second file"),
+ } {
+ require.NoError(t, memIO.WriteFile(name, data))
+ }
+
+ type visitedEntry struct {
+ path string
+ isDir bool
+ size int64
+ }
+ var got []visitedEntry
+ err := memIO.WalkDir("mem://bucket/root", func(path string, d
fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ info, err := d.Info()
+ if err != nil {
+ return err
+ }
+ got = append(got, visitedEntry{path: path, isDir: d.IsDir(),
size: info.Size()})
+
+ return nil
+ })
+ require.NoError(t, err)
+ assert.Equal(t, []visitedEntry{
+ {path: "mem://bucket/root", isDir: true},
+ {path: "mem://bucket/root/a", isDir: true},
+ {path: "mem://bucket/root/a/b", isDir: true},
+ {path: "mem://bucket/root/a/b/c", isDir: true},
+ {path: "mem://bucket/root/a/b/c/deep.parquet", size: 4},
+ {path: "mem://bucket/root/a/first.parquet", size: 6},
+ {path: "mem://bucket/root/ab", isDir: true},
+ {path: "mem://bucket/root/ab/second.parquet", size: 11},
+ }, got)
+}