أسئلة وأجوبة

كيف تنشئ شريحة بايتات بشكل غير نظيف (دون تصفير عناصر بايتاتها)؟

قبل Go 1.21، لا توجد طريقة لتحقيق ذلك، حتى بالطرق unsafe.

منذ Go 1.21، يستدعي تنفيذ strings.Builder.Grow دالة داخلية bytealg.MakeNoZero بدلاً من الدالة المضمّنة make التي كان التنفيذ القديم يستدعيها. في معظم الحالات، ستُصفّر الدالة المضمّنة make عناصر الشريحة الناتجة، لذا غالباً ما تكون أبطأ نسبياً.

مع تنفيذ 1.21+، أصبح لدينا الآن فرصة لإنشاء شرائح بايتات دون تهيئة عناصرها إلى الصفر (مع أن تحقيق هذه الوظيفة يتطلب استخدام دوال unsafe).


import (
	"strings"
	"unsafe"
)

func MakeDirtyByteSlice(n int) []byte {
	var b strings.Builder
	b.Grow(n)
	var p = unsafe.StringData(b.String())
	return unsafe.Slice(p, n)
}

لكن، لسبب غير معروف (يُرجَّح أنه متعلق بتنفيذ المصرِّف/وقت التشغيل الرسمي)، تُظهر نتائج قياس الأداء أن الدالة ليست بالسرعة المتوقعة عندما لا يكون طول شريحة البايتات المنشأة كبيراً.


package ttt

import (
	"strings"
	"testing"
	"unsafe"
)

const Len = 20000

func init() {
	println("=============== Len =", Len)
}

func MakeDirtyByteSlice(n int) []byte {
	var b strings.Builder
	b.Grow(n)
	var p = unsafe.StringData(b.String())
	return unsafe.Slice(p, n)
}

func Benchmark_MakeDirtyByteSlice(tb *testing.B) {
	for range tb.N {
		r = MakeDirtyByteSlice(Len)
	}
}

func Benchmark_make(tb *testing.B) {
	for range tb.N {
		r = make([]byte, Len)
	}
}

func MakeByteSlice(initialValues ...[]byte) []byte {
	n := 0
	for i := range initialValues {
		n += len(initialValues[i])
	}
	n, r := 0, MakeDirtyByteSlice(n)
	for _, s := range initialValues {
		n += copy(r[n:], s)
	}
	return r
}

const N = Len / 4

var x = []byte(strings.Repeat("x", N))
var y = []byte(strings.Repeat("y", N))
var z = []byte(strings.Repeat("z", N))
var w = []byte(strings.Repeat("z", N))
var r []byte

func Benchmark_MakeByteSlice(tb *testing.B) {
	for range tb.N {
		r = MakeByteSlice(x, y, z, w)
	}
}

func Benchmark_make_append(tb *testing.B) {
	for range tb.N {
		r = make([]byte, 0, Len)
		r = append(r, x...)
		r = append(r, y...)
		r = append(r, z...)
		r = append(r, w...)
	}
}

نتائج قياس الأداء:


$ gotv . test -bench=. -benchmem
[Run]: $HOME/.cache/gotv/tag_go1.23rc1/bin/go test -bench=. -benchmem
=============== Len = 4000
goos: linux
goarch: amd64
pkg: example.com
cpu: Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz
Benchmark_MakeDirtyByteSlice-4   	 1752531	       669.5 ns/op	    4096 B/op	       1 allocs/op
Benchmark_make-4                 	 1268131	       940.3 ns/op	    4096 B/op	       1 allocs/op
Benchmark_MakeByteSlice-4        	  913239	      1119 ns/op	    4096 B/op	       1 allocs/op
Benchmark_make_append-4          	 1142342	      1046 ns/op	    4096 B/op	       1 allocs/op

$ gotv . test -bench=. -benchmem
[Run]: $HOME/.cache/gotv/tag_go1.23rc1/bin/go test -bench=. -benchmem
=============== Len = 20000
goos: linux
goarch: amd64
pkg: example.com
cpu: Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz
Benchmark_MakeDirtyByteSlice-4   	  628150	      1863 ns/op	   20480 B/op	       1 allocs/op
Benchmark_make-4                 	  320997	      3404 ns/op	   20480 B/op	       1 allocs/op
Benchmark_MakeByteSlice-4        	  262684	      4213 ns/op	   20480 B/op	       1 allocs/op
Benchmark_make_append-4          	  246081	      4525 ns/op	   20480 B/op	       1 allocs/op