diff --git a/pkg/local_object_storage/blobstor/fstree/bench_test.go b/pkg/local_object_storage/blobstor/fstree/bench_test.go new file mode 100644 index 0000000000..e0b1968855 --- /dev/null +++ b/pkg/local_object_storage/blobstor/fstree/bench_test.go @@ -0,0 +1,133 @@ +package fstree_test + +import ( + "io" + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree" + objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/stretchr/testify/require" +) + +func BenchmarkFSTree_Head(b *testing.B) { + for _, size := range payloadSizes { + b.Run(generateSizeLabel(size), func(b *testing.B) { + runReadBenchmark(b, "Head", size) + }) + } +} + +func BenchmarkFSTree_Get(b *testing.B) { + for _, size := range payloadSizes { + b.Run(generateSizeLabel(size), func(b *testing.B) { + runReadBenchmark(b, "Get", size) + }) + } +} + +func BenchmarkFSTree_GetStream(b *testing.B) { + for _, size := range payloadSizes { + b.Run(generateSizeLabel(size), func(b *testing.B) { + runReadBenchmark(b, "GetStream", size) + + b.Run("GetStream_with_payload_read", func(b *testing.B) { + freshFSTree := setupFSTree(b) + addr := prepareSingleObject(b, freshFSTree, size) + + b.ReportAllocs() + b.ResetTimer() + for range b.N { + header, reader, err := freshFSTree.GetStream(addr) + if err != nil { + b.Fatal(err) + } + if header == nil { + b.Fatal("header is nil") + } + if reader != nil { + // Read all payload to simulate real usage + _, err := io.ReadAll(reader) + if err != nil { + b.Fatal(err) + } + require.NoError(b, reader.Close()) + } + } + }) + }) + } +} + +func runReadBenchmark(b *testing.B, methodName string, payloadSize int) { + testRead := func(fsTree *fstree.FSTree, addr oid.Address) { + var err error + switch methodName { + case "Head": + _, err = fsTree.Head(addr) + case "Get": + _, err = fsTree.Get(addr) + case "GetStream": + var ( + header *objectSDK.Object + reader io.ReadCloser + ) + header, reader, err = fsTree.GetStream(addr) + if header == nil { + b.Fatal("header is nil") + } + if reader != nil { + require.NoError(b, reader.Close()) + } + } + if err != nil { + b.Fatal(err) + } + } + + b.Run(methodName+"_regular", func(b *testing.B) { + fsTree := setupFSTree(b) + addr := prepareSingleObject(b, fsTree, payloadSize) + + b.ReportAllocs() + b.ResetTimer() + for range b.N { + testRead(fsTree, addr) + } + }) + + b.Run(methodName+"_combined", func(b *testing.B) { + fsTree := setupFSTree(b) + addrs := prepareMultipleObjects(b, fsTree, payloadSize) + + b.ReportAllocs() + b.ResetTimer() + for k := range b.N { + testRead(fsTree, addrs[k%len(addrs)]) + } + }) + + b.Run(methodName+"_compressed", func(b *testing.B) { + fsTree := setupFSTree(b) + setupCompressor(b, fsTree) + addr := prepareSingleObject(b, fsTree, payloadSize) + + b.ReportAllocs() + b.ResetTimer() + for range b.N { + testRead(fsTree, addr) + } + }) + + b.Run(methodName+"_compressed_combined", func(b *testing.B) { + fsTree := setupFSTree(b) + setupCompressor(b, fsTree) + addrs := prepareMultipleObjects(b, fsTree, payloadSize) + + b.ReportAllocs() + b.ResetTimer() + for k := range b.N { + testRead(fsTree, addrs[k%len(addrs)]) + } + }) +} diff --git a/pkg/local_object_storage/blobstor/fstree/common_test.go b/pkg/local_object_storage/blobstor/fstree/common_test.go new file mode 100644 index 0000000000..514b82283e --- /dev/null +++ b/pkg/local_object_storage/blobstor/fstree/common_test.go @@ -0,0 +1,100 @@ +package fstree_test + +import ( + "crypto/rand" + "fmt" + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/object" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/compression" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree" + objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" + "github.com/stretchr/testify/require" +) + +var payloadSizes = []int{ + 0, // Empty payload + 100, // 100 bytes + 4 * 1024, // 4 KB + 16 * 1024, // 16 KB + 32 * 1024, // 32 KB + 100 * 1024, // 100 KB + 1024 * 1024, // 1 MB +} + +func setupFSTree(tb testing.TB) *fstree.FSTree { + fsTree := fstree.New(fstree.WithPath(tb.TempDir())) + require.NoError(tb, fsTree.Open(false)) + require.NoError(tb, fsTree.Init()) + return fsTree +} + +func setupCompressor(tb testing.TB, fsTree *fstree.FSTree) { + compressConfig := &compression.Config{ + Enabled: true, + } + require.NoError(tb, compressConfig.Init()) + fsTree.SetCompressor(compressConfig) +} + +func prepareSingleObject(tb testing.TB, fsTree *fstree.FSTree, payloadSize int) oid.Address { + obj := generateTestObject(payloadSize) + addr := object.AddressOf(obj) + require.NoError(tb, fsTree.Put(addr, obj.Marshal())) + return addr +} + +func addAttribute(obj *objectSDK.Object, key, value string) { + var attr objectSDK.Attribute + attr.SetKey(key) + attr.SetValue(value) + + attrs := obj.Attributes() + attrs = append(attrs, attr) + obj.SetAttributes(attrs...) +} + +func generateTestObject(payloadSize int) *objectSDK.Object { + obj := objecttest.Object() + if payloadSize > 0 { + payload := make([]byte, payloadSize) + _, _ = rand.Read(payload) + obj.SetPayload(payload) + } else { + obj.SetPayload(nil) + } + obj.SetPayloadSize(uint64(payloadSize)) + + return &obj +} + +func generateSizeLabel(size int) string { + switch { + case size == 0: + return "Empty" + case size < 1024: + return fmt.Sprintf("%dB", size) + case size < 1024*1024: + return fmt.Sprintf("%dKB", size/1024) + default: + return fmt.Sprintf("%dMB", size/(1024*1024)) + } +} + +func prepareMultipleObjects(tb testing.TB, fsTree *fstree.FSTree, payloadSize int) []oid.Address { + const numObjects = 10 + objMap := make(map[oid.Address][]byte, numObjects) + addrs := make([]oid.Address, numObjects) + + for i := range numObjects { + obj := generateTestObject(payloadSize) + addr := object.AddressOf(obj) + objMap[addr] = obj.Marshal() + addrs[i] = addr + } + + require.NoError(tb, fsTree.PutBatch(objMap)) + return addrs +} diff --git a/pkg/local_object_storage/blobstor/fstree/head_bench_test.go b/pkg/local_object_storage/blobstor/fstree/head_bench_test.go index d6fe25ffa8..8e7b029114 100644 --- a/pkg/local_object_storage/blobstor/fstree/head_bench_test.go +++ b/pkg/local_object_storage/blobstor/fstree/head_bench_test.go @@ -1,28 +1,9 @@ package fstree_test import ( - "crypto/rand" - "fmt" "testing" - - "github.com/nspcc-dev/neofs-node/pkg/core/object" - "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/compression" - "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree" - objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" - objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" - "github.com/stretchr/testify/require" ) -var payloadSizes = []int{ - 0, // Empty payload - 100, // 100 bytes - 4 * 1024, // 4 KB - 16 * 1024, // 16 KB - 32 * 1024, // 32 KB - 100 * 1024, // 100 KB - 1024 * 1024, // 1 MB -} - func BenchmarkFSTree_HeadVsGet(b *testing.B) { for _, size := range payloadSizes { b.Run(generateSizeLabel(size), func(b *testing.B) { @@ -39,39 +20,16 @@ func BenchmarkFSTree_HeadVsGet_Compressed(b *testing.B) { } } -func generateSizeLabel(size int) string { - switch { - case size == 0: - return "Empty" - case size < 1024: - return fmt.Sprintf("%dB", size) - case size < 1024*1024: - return fmt.Sprintf("%dKB", size/1024) - default: - return fmt.Sprintf("%dMB", size/(1024*1024)) - } -} - func runHeadVsGetBenchmark(b *testing.B, payloadSize int, compressed bool) { - fsTree := fstree.New(fstree.WithPath(b.TempDir())) + fsTree := setupFSTree(b) suffix := "" if compressed { - compressConfig := &compression.Config{ - Enabled: true, - } - require.NoError(b, compressConfig.Init()) - fsTree.SetCompressor(compressConfig) + setupCompressor(b, fsTree) suffix = "_Compressed" } - require.NoError(b, fsTree.Open(false)) - require.NoError(b, fsTree.Init()) - - obj := generateTestObject(payloadSize) - addr := object.AddressOf(obj) - - require.NoError(b, fsTree.Put(addr, obj.Marshal())) + addr := prepareSingleObject(b, fsTree, payloadSize) b.Run("Head"+suffix, func(b *testing.B) { b.ResetTimer() @@ -95,17 +53,3 @@ func runHeadVsGetBenchmark(b *testing.B, payloadSize int, compressed bool) { } }) } - -func generateTestObject(payloadSize int) *objectSDK.Object { - obj := objecttest.Object() - if payloadSize > 0 { - payload := make([]byte, payloadSize) - _, _ = rand.Read(payload) - obj.SetPayload(payload) - } else { - obj.SetPayload(nil) - } - obj.SetPayloadSize(uint64(payloadSize)) - - return &obj -} diff --git a/pkg/local_object_storage/blobstor/fstree/head_test.go b/pkg/local_object_storage/blobstor/fstree/head_test.go index f01a25a3d9..1303e8d624 100644 --- a/pkg/local_object_storage/blobstor/fstree/head_test.go +++ b/pkg/local_object_storage/blobstor/fstree/head_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/nspcc-dev/neofs-node/pkg/core/object" - "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/compression" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree" objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" @@ -13,9 +12,7 @@ import ( ) func TestHeadStorage(t *testing.T) { - fsTree := fstree.New(fstree.WithPath(t.TempDir())) - require.NoError(t, fsTree.Open(false)) - require.NoError(t, fsTree.Init()) + fsTree := setupFSTree(t) testObjects := func(t *testing.T, fsTree *fstree.FSTree, size int) { obj := generateTestObject(size) @@ -109,16 +106,8 @@ func TestHeadStorage(t *testing.T) { }) t.Run("with compression", func(t *testing.T) { - compressConfig := &compression.Config{ - Enabled: true, - } - require.NoError(t, compressConfig.Init()) - - fsComp := fstree.New(fstree.WithPath(t.TempDir())) - fsComp.SetCompressor(compressConfig) - - require.NoError(t, fsComp.Open(false)) - require.NoError(t, fsComp.Init()) + fsComp := setupFSTree(t) + setupCompressor(t, fsComp) for _, size := range payloadSizes { t.Run("compressed_"+generateSizeLabel(size), func(t *testing.T) { @@ -131,13 +120,3 @@ func TestHeadStorage(t *testing.T) { } }) } - -func addAttribute(obj *objectSDK.Object, key, value string) { - var attr objectSDK.Attribute - attr.SetKey(key) - attr.SetValue(value) - - attrs := obj.Attributes() - attrs = append(attrs, attr) - obj.SetAttributes(attrs...) -} diff --git a/pkg/local_object_storage/blobstor/fstree/range_bench_test.go b/pkg/local_object_storage/blobstor/fstree/range_bench_test.go index 2ac97ec7db..362d08a599 100644 --- a/pkg/local_object_storage/blobstor/fstree/range_bench_test.go +++ b/pkg/local_object_storage/blobstor/fstree/range_bench_test.go @@ -5,9 +5,6 @@ import ( "testing" "github.com/nspcc-dev/neofs-node/pkg/core/object" - "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/compression" - "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/stretchr/testify/require" ) @@ -28,24 +25,22 @@ func BenchmarkFSTree_GetRange(b *testing.B) { {from: 1 * KB, length: 4 * KB, objectSize: 1 * MB}, // 1MB, range in the middle {from: 0, length: 1 * MB, objectSize: 1 * MB}, // 1MB, full range {from: 0, length: 0, objectSize: 1 * MB}, // 1MB, zero range + {from: 1 * KB, length: 1 * KB, objectSize: 4 * KB}, // 4KB, range in the middle {from: 0, length: 4 * KB, objectSize: 4 * KB}, // 4KB, full range {from: 0, length: 0, objectSize: 4 * KB}, // 4KB, zero range - {from: 1 * KB, length: 1 * KB, objectSize: 4 * KB}, // 4KB, range in the middle } for _, tc := range testCases { b.Run(fmt.Sprintf("size=%s,off=%s,len=%s", generateSizeLabel(tc.objectSize), generateSizeLabel(int(tc.from)), generateSizeLabel(int(tc.length))), func(b *testing.B) { - tmpDir := b.TempDir() - - fsTree := fstree.New(fstree.WithPath(tmpDir)) - obj := generateTestObject(tc.objectSize) addr := object.AddressOf(obj) b.Run("regular", func(b *testing.B) { + fsTree := setupFSTree(b) require.NoError(b, fsTree.Put(addr, obj.Marshal())) + b.ResetTimer() for range b.N { _, err := fsTree.GetRange(addr, tc.from, tc.length) @@ -56,10 +51,8 @@ func BenchmarkFSTree_GetRange(b *testing.B) { }) b.Run("compressed", func(b *testing.B) { - compressConfig := &compression.Config{ - Enabled: true, - } - require.NoError(b, compressConfig.Init()) + fsTree := setupFSTree(b) + setupCompressor(b, fsTree) require.NoError(b, fsTree.Put(addr, obj.Marshal())) b.ResetTimer() @@ -72,20 +65,12 @@ func BenchmarkFSTree_GetRange(b *testing.B) { }) b.Run("combined", func(b *testing.B) { - const numObjects = 10 - - objMap := make(map[oid.Address][]byte, numObjects) - addrs := make([]oid.Address, numObjects) - for i := range numObjects { - o := generateTestObject(tc.objectSize) - objMap[object.AddressOf(o)] = o.Marshal() - addrs[i] = object.AddressOf(o) - } - require.NoError(b, fsTree.PutBatch(objMap)) + fsTree := setupFSTree(b) + addrs := prepareMultipleObjects(b, fsTree, tc.objectSize) b.ResetTimer() for k := range b.N { - _, err := fsTree.GetRange(addrs[k%numObjects], tc.from, tc.length) + _, err := fsTree.GetRange(addrs[k%len(addrs)], tc.from, tc.length) if err != nil { b.Fatal(err) }