Skip to content

Commit fad97f8

Browse files
authored
Merge pull request #571 from devlights/add-zero-memory-copy-example
2 parents 1e21bcf + 5e4cac6 commit fad97f8

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

examples/advanced/examples.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/devlights/try-golang/examples/advanced/imaging"
1111
"github.com/devlights/try-golang/examples/advanced/reflection"
1212
"github.com/devlights/try-golang/examples/advanced/structtag"
13+
"github.com/devlights/try-golang/examples/advanced/zeromemorycopy"
1314
"github.com/devlights/try-golang/mapping"
1415
)
1516

@@ -34,4 +35,5 @@ func (r *register) Regist(m mapping.ExampleMapping) {
3435
imaging.NewRegister().Regist(m)
3536
reflection.NewRegister().Regist(m)
3637
structtag.NewRegister().Regist(m)
38+
zeromemorycopy.NewRegister().Regist(m)
3739
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package zeromemorycopy
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"strconv"
8+
"time"
9+
"unsafe"
10+
11+
"github.com/devlights/gomy/times"
12+
)
13+
14+
// ByteSliceToString -- バイトスライスから文字列へメモリコピー無しに変換するサンプルです。
15+
//
16+
// REFERENCES
17+
// - https://github.com/devlights/gomy/blob/master/zeromemcpy/b2s.go
18+
// - https://cs.opensource.google/go/go/+/refs/tags/go1.18.4:src/strings/builder.go;l=47
19+
func ByteSliceToString() error {
20+
//
21+
// []byte から string へメモリコピー無しに変換するには unsafe パッケージを使う必要がある。
22+
// unsafeパッケージは文字通り unsafe な操作を行うパッケージなので、通常時には利用するべきではない。
23+
// パフォーマンスが極端に求められている場合で、且つ、メモリコピーの部分がボトルネックな場合にのみ利用するべき。
24+
// (通常、このような部分よりも他の部分がボトルネックになっているはず)
25+
//
26+
// Goは、基本的にこのようなトリッキーなことをしなくても充分速い。
27+
//
28+
29+
// -------------------------------------
30+
// 大きなサイズのバッファを作る
31+
// -------------------------------------
32+
33+
buf := new(bytes.Buffer)
34+
for i := 0; i < 30_000_000; i++ {
35+
buf.WriteString(strconv.Itoa(i))
36+
}
37+
38+
b := buf.Bytes()
39+
fmt.Printf("[length] %vbytes, %vmb\n", len(b), len(b)/1024/1024)
40+
41+
// -------------------------------------
42+
// 普通に変換
43+
// -------------------------------------
44+
elapsed := times.Stopwatch(func(start time.Time) {
45+
io.WriteString(io.Discard, string(b))
46+
})
47+
fmt.Printf("[normal] %v\n", elapsed)
48+
49+
// -------------------------------------
50+
// メモリコピー無しで変換
51+
// -------------------------------------
52+
elapsed = times.Stopwatch(func(start time.Time) {
53+
io.WriteString(io.Discard, *(*string)(unsafe.Pointer(&b)))
54+
55+
/* 上を細かく区切ると以下のようになる
56+
var (
57+
ptrByte = unsafe.Pointer(&b)
58+
ptrStr = (*string)(ptrByte)
59+
str = *ptrStr
60+
)
61+
io.WriteString(io.Discard, str)
62+
*/
63+
})
64+
fmt.Printf("[zeromemcpy] %v\n", elapsed)
65+
66+
return nil
67+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package zeromemorycopy
2+
3+
import "github.com/devlights/try-golang/mapping"
4+
5+
type (
6+
register struct{}
7+
)
8+
9+
// NewRegister -- このパッケージ用のサンプルを登録する mapping.Register を生成します。
10+
func NewRegister() mapping.Register {
11+
r := new(register)
12+
return r
13+
}
14+
15+
// Regist -- 登録します.
16+
func (r *register) Regist(m mapping.ExampleMapping) {
17+
m["zeromemorycopy_string_to_byteslice"] = StringToByteSlice
18+
m["zeromemorycopy_byteslice_to_string"] = ByteSliceToString
19+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package zeromemorycopy
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"reflect"
7+
"strconv"
8+
"strings"
9+
"time"
10+
"unsafe"
11+
12+
"github.com/devlights/gomy/times"
13+
)
14+
15+
// StringToByteSlice -- 文字列からバイトスライスへメモリコピー無しに変換するサンプルです。
16+
//
17+
// REFERENCES
18+
// - https://stackoverflow.com/questions/59209493/how-to-use-unsafe-get-a-byte-slice-from-a-string-without-memory-copy
19+
// - https://github.com/devlights/gomy/blob/master/zeromemcpy/s2b.go
20+
// - https://pkg.go.dev/unsafe@go1.18.4#Slice
21+
func StringToByteSlice() error {
22+
//
23+
// string から []byte へメモリコピー無しに変換するには unsafe パッケージを使う必要がある。
24+
// unsafeパッケージは文字通り unsafe な操作を行うパッケージなので、通常時には利用するべきではない。
25+
// パフォーマンスが極端に求められている場合で、且つ、メモリコピーの部分がボトルネックな場合にのみ利用するべき。
26+
// (通常、このような部分よりも他の部分がボトルネックになっているはず)
27+
//
28+
// Goは、基本的にこのようなトリッキーなことをしなくても充分速い。
29+
//
30+
31+
// -------------------------------------
32+
// 大きなサイズの文字列を作る
33+
// -------------------------------------
34+
var sb strings.Builder
35+
for i := 0; i < 30_000_000; i++ {
36+
sb.WriteString(strconv.Itoa(i))
37+
}
38+
39+
s := sb.String()
40+
fmt.Printf("[length] %vbytes, %vmb\n", len(s), len(s)/1024/1024)
41+
42+
// -------------------------------------
43+
// 普通に変換
44+
// -------------------------------------
45+
elapsed := times.Stopwatch(func(start time.Time) {
46+
io.Discard.Write([]byte(s))
47+
})
48+
fmt.Printf("[normal] %v\n", elapsed)
49+
50+
// -------------------------------------
51+
// メモリコピー無しで変換
52+
// -------------------------------------
53+
elapsed = times.Stopwatch(func(start time.Time) {
54+
io.Discard.Write(unsafe.Slice((*byte)(unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&s)).Data)), len(s)))
55+
56+
/* 上を細かく区切ると以下のようになる
57+
var (
58+
ptrStr = unsafe.Pointer(&s)
59+
strHeader = (*reflect.StringHeader)(ptrStr)
60+
ptrStrData = unsafe.Pointer(strHeader.Data)
61+
ptrByte = (*byte)(ptrStrData)
62+
slice = unsafe.Slice(ptrByte, len(s))
63+
)
64+
io.Discard.Write(slice)
65+
*/
66+
})
67+
fmt.Printf("[zeromemcpy] %v\n", elapsed)
68+
69+
return nil
70+
}

0 commit comments

Comments
 (0)