From 08f17b35385f13148dcebc7f0335a80df7da4a65 Mon Sep 17 00:00:00 2001 From: Vildan Safin Date: Mon, 30 Mar 2026 16:19:46 +0500 Subject: [PATCH] perf(chunk): Remove if from chunk bounds --- benchmark/core_slice_bench_test.go | 64 ++++++++++++++++++++++++++++++ slice.go | 35 ++++++++++------ 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/benchmark/core_slice_bench_test.go b/benchmark/core_slice_bench_test.go index 2df8b6958..c7ff3c354 100644 --- a/benchmark/core_slice_bench_test.go +++ b/benchmark/core_slice_bench_test.go @@ -1013,3 +1013,67 @@ func BenchmarkFromSlicePtrOr(b *testing.B) { }) } } + +func BenchmarkChunkIntSmallDataSmallChunk(b *testing.B) { + data := genSliceInt(100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 3) + } +} + +func BenchmarkChunkIntSmallDataLargeChunk(b *testing.B) { + data := genSliceInt(100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 50) + } +} + +func BenchmarkChunkIntMediumDataSmallChunk(b *testing.B) { + data := genSliceInt(10_000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 7) + } +} + +func BenchmarkChunkIntMediumDataLargeChunk(b *testing.B) { + data := genSliceInt(10_000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 1024) + } +} + +func BenchmarkChunkIntLargeDataSmallChunk(b *testing.B) { + data := genSliceInt(1_000_000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 13) + } +} + +func BenchmarkChunkIntLargeDataLargeChunk(b *testing.B) { + data := genSliceInt(1_000_000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 8192) + } +} + +func BenchmarkChunkStringMediumDataSmallChunk(b *testing.B) { + data := genSliceString(10_000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 7) + } +} + +func BenchmarkChunkStringMediumDataLargeChunk(b *testing.B) { + data := genSliceString(10_000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = lo.Chunk(data, 1024) + } +} diff --git a/slice.go b/slice.go index 08522cdb2..6d599b0d7 100644 --- a/slice.go +++ b/slice.go @@ -351,30 +351,39 @@ func GroupByMapErr[T any, K comparable, V any](collection []T, transform func(it // Chunk returns a slice of elements split into groups of length size. If the slice can't be split evenly, // the final chunk will be the remaining elements. // Play: https://go.dev/play/p/kEMkFbdu85g +// Chunk returns a slice of elements split into groups of length size. +// If the slice can't be split evenly, the final chunk will be the remaining elements. +// Copy chunk in a new slice, to prevent memory leak and free memory from initial collection. +// Play: https://go.dev/play/p/kEMkFbdu85g func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice { if size <= 0 { panic("lo.Chunk: size must be greater than 0") } - chunksNum := len(collection) / size - if len(collection)%size != 0 { - chunksNum++ + if len(collection) == 0 { + return []Slice{} } - result := make([]Slice, 0, chunksNum) + // Reason: https://github.com/golang/go/issues/77287 + clone := func(s Slice) Slice { + dst := make(Slice, len(s)) + copy(dst, s) + return dst + } - for i := 0; i < chunksNum; i++ { - last := (i + 1) * size - if last > len(collection) { - last = len(collection) - } + chunksNum := (len(collection) + size - 1) / size + result := make([]Slice, chunksNum) - // Copy chunk in a new slice, to prevent memory leak and free memory from initial collection. - newSlice := make(Slice, last-i*size) - copy(newSlice, collection[i*size:last]) - result = append(result, newSlice) + // Process all full-sized chunks + i := 0 + for ; i < chunksNum-1; i++ { + pos := i * size + result[i] = clone(collection[pos : pos+size]) } + // Handle the final chunk (may be smaller than size) + result[chunksNum-1] = clone(collection[i*size:]) + return result }