Go�ǥ������������˵���Ĥ��������ɤ����ˡ
Go��Python�Τ褦��LL����٤�ȼ¹�®�٤�®���ΤǤ�����GC������®���櫓�ǤϤʤ��Τǡ�����Ū��GC���ѥե����ޥ󥹤�Ϳ����ƶ����ç¤ï¿½ï¿½ï¿½Ê¤ï¿½Þ¤ï¿½ï¿½ï¿½
�ޤ���Java ����٤�ȡ�������֥������ȤʤɤΤ�������ˤ˥ҡ��ץ�������������Ԥ���GC����߻��֤�Ĺ���ʤ꤬���Ǥ����������ǥҡ��ץ��������������򤱤��ץ�����ߥ󥰤����䤹������Ǥ⤢��ޤ���
MySQL �ɥ饤�ФΤ褦����쥤�䡼�Υ饤�֥������硢���ץꥱ�������¦����ǽ�׷�򾡼�˷��뤳�Ȥ��Ǥ��ʤ��Τǡ�����Ū���ϰϤǥ������������򸺤餹���Ϥ򤹤�٤��Ǥ���
�Ȥ������Ȥǡ�����ε��� �ǾҲ𤷤��ץ졼���ۥ���ִ����������ˤ����äƷи��������������������˵���Ȥä��ץ�����ߥ󥰤ˤĤ��ơ����塼�˥󥰤�����䥳���ɾ�Υƥ��˥å���Ҳ𤷤����Ȼפ��ޤ���
1. �ޤ���������ư����Τ���
go-sql-driver/mysql �Υ��ƥʤ˼���������뤫�ɤ���ʬ����ʤ���ǽ���ä��Τǡ��ǽ�Ϥʤ�٤���ñ�˼������ơ����ε�ǽ������फ�ɤ�����API���߷פϤ���Ǥ������Ȥ����ä���Ϥ�ޤ���
2. �٥���ޡ����ץ��������
���塼�˥󥰤�Ϥ�����ˡ����̳�ǧ�ѤΥ٥���ޡ����ץ�������ñ¤¤Þ¤ï¿½ï¿½ï¿½ �ƥ��Ȥ�Ʊ���� _test.go
�Ȥ���̾���ǽ����ե�����ˡ� BenchmarkXxxx(b *testing.B)
�Ȥ����ؿ���������ޤ���������� b.ReportAllocs()
��Ƥ�Ǥ����ȥ��������Ȳ�����¬���뤳�Ȥ��Ǥ��ޤ���
������������٥���ޡ����ץ������ϼ��Τ褦�ʤ�ΤǤ��� database/sql
���桼�������Ϥ����ͤ����������Ƥ����Τǡ��ɥ饤��¦���Ϥ�����ͤ� int64, float64, bool, time.Time, []byte, string ��6�Ĥη������ˤʤ�ޤ���������6�Ĥ������ͤ�1�Ĥ����Ѱդ��ơ��ץ졼���ۥ���ִ���¹Ԥ��Ƥ��ޤ���
func BenchmarkInterpolation(b *testing.B) { mc := &mysqlConn{ cfg: &config{ interpolateParams: true, loc: time.UTC, }, maxPacketAllowed: maxPacketSize, maxWriteSize: maxPacketSize - 1, } args := []driver.Value{ int64(42424242), float64(math.Pi), false, time.Unix(1423411542, 807015000), []byte("bytes containing special chars ' \" \a \x00"), "string containing special chars ' \" \a \x00", } q := "SELECT ?, ?, ?, ?, ?, ?" b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := mc.interpolateParams(q, args) if err != nil { b.Fatal(err) } } }
�¹Ԥ���ˤϼ��Τ褦�ʥ��ޥ�ɤ�¹Ԥ��ޤ���
$ go test -bench=BenchmarkInterpolation PASS BenchmarkInterpolation 300000 3887 ns/op 1144 B/op 15 allocs/op ok github.com/go-sql-driver/mysql 2.386s
3. ������������󤬵����äƤ������Ĵ�٤�
GODEBUG=allocfreetrace=1
�Ȥ����Ķ��ѿ������ꤹ��ȡ����������Ȥ������ä����Υ����å��ȥ졼���򸫤뤳�Ȥ��Ǥ��ޤ��� (�ɥ������)
�����ǡ��٥���ޡ����ץ������Υ롼�פ���Ȥ�����¹Ԥ��뾮���ʥץ��������Ѱդ������ΤǤ����������Ѱդ��Ƥ���ΤϾ�ʸ������Ϥޤ�̾�������������ʤΤǡ� main
�ѥå���������ľ�ܸƤӽФ����ȤϤǤ��ޤ��� mysql
�ѥå������˥��ߥåȤ˴ޤ�ʤ��������ե�������ɲä��Ƥ�������ƤӽФ����ɤ��ΤǤ���������ϥ٥���ޡ����ץ�������ľ�����Ѥ��뤳�Ȥˤ��ޤ�����
GODEBUG �����ꤷ���ޤ� go test
��¹Ԥ���ȥΥ�����¿������Τǡ���� go test -c
��Ȥä� mysql.test
�Ȥ����¹ԥե�������ꡢ�����¹Ԥ��ޤ������κݡ����ץ����� go test
���Ϥ����ץ����� test.
�Ȥ��� prefix ��Ĥ��뤳�Ȥˤʤ�ޤ���
$ go test -c $ GODEBUG=allocfreetrace=1 ./mysql.test -test.run=none -test.bench=BenchmarkInter -test.benchtime=10ms 2>trace.log PASS BenchmarkInterpolation 5000 4095 ns/op 1144 B/op 15 allocs/op
������Ǥϡ� -test.run
�ˤɤΥƥ��Ȥˤ�ޥå����ʤ�Ŭ����̾�� none
����ꤷ�ƥ٥���ޡ����ʳ��Υƥ��Ȥ��¹Ԥ���뤳�Ȥ��ɤ��� -test.bench
����Ū�ʳ��Υ٥���ޡ����ؿ��μ¹Ԥ��޻ߤ��� -test.benchtime=10ms
�ǥǥե����1�äΥ٥���ޡ������֤�û�����Ƥ��ޤ���ɸ�२�顼���Ϥ˥��������ȤΥ����å��ȥ졼�������Ϥ���Ƥ���Τǡ������ trace.log �˽��Ϥ��Ƥ��ޤ���
��Ū�δؿ��Ǥ��� interpolateParams
��õ��������������������ޤ�������ȼ��Τ褦�ʥ����å��ȥ졼���������ޤ���
tracealloc(0xc2080100e0, 0x70, string) goroutine 5 [running]: runtime.mallocgc(0x70, 0x22e4e0, 0x0, 0x2) /usr/local/Cellar/go/1.4.1/libexec/src/runtime/malloc.go:327 +0x32a fp=0xc20802ea60 sp=0xc20802e9b0 runtime.newarray(0x22e4e0, 0x7, 0x15c5e) /usr/local/Cellar/go/1.4.1/libexec/src/runtime/malloc.go:365 +0xc1 fp=0xc20802ea98 sp=0xc20802ea60 runtime.makeslice(0x2229c0, 0x7, 0x7, 0x0, 0x0, 0x0) /usr/local/Cellar/go/1.4.1/libexec/src/runtime/slice.go:32 +0x15c fp=0xc20802eae0 sp=0xc20802ea98 strings.genSplit(0x30c190, 0x17, 0x2e1f10, 0x1, 0x0, 0x7, 0x0, 0x0, 0x0) /usr/local/Cellar/go/1.4.1/libexec/src/strings/strings.go:287 +0x14d fp=0xc20802eb60 sp=0xc20802eae0 strings.Split(0x30c190, 0x17, 0x2e1f10, 0x1, 0x0, 0x0, 0x0) /usr/local/Cellar/go/1.4.1/libexec/src/strings/strings.go:325 +0x76 fp=0xc20802ebb0 sp=0xc20802eb60 github.com/go-sql-driver/mysql.(*mysqlConn).interpolateParams(0xc20802eed0, 0x30c190, 0x17, 0xc20802ee70, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0) /Users/inada-n/go1.4/src/github.com/go-sql-driver/mysql/connection.go:180 +0x86 fp=0xc20802ed38 sp=0xc20802ebb0 github.com/go-sql-driver/mysql.BenchmarkInterpolation(0xc20806a400) /Users/inada-n/go1.4/src/github.com/go-sql-driver/mysql/benchmark_test.go:240 +0x437 fp=0xc20802ef58 sp=0xc20802ed38 testing.(*B).runN(0xc20806a400, 0x1) /usr/local/Cellar/go/1.4.1/libexec/src/testing/benchmark.go:124 +0x95 fp=0xc20802ef68 sp=0xc20802ef58 testing.(*B).launch(0xc20806a400) /usr/local/Cellar/go/1.4.1/libexec/src/testing/benchmark.go:199 +0x78 fp=0xc20802efd8 sp=0xc20802ef68 runtime.goexit() /usr/local/Cellar/go/1.4.1/libexec/src/runtime/asm_amd64.s:2232 +0x1 fp=0xc20802efe0 sp=0xc20802efd8 created by testing.(*B).run /usr/local/Cellar/go/1.4.1/libexec/src/testing/benchmark.go:179 +0x3e ...(³��)
���Υ����å��ȥ졼���� interpolateParams()
���� strings.Split()
��ƤӽФ��Ƥ�����ʬ�ǥ��������Ȥ�ȯ�����Ƥ��뤳�Ȥ�ʬ����ޤ���
�����������ɤΥե�����̾�ȹԿ���񤫤�Ƥ���Τǡ���ñ�˳�����ʬ������Ǥ��ޤ���
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) { chunks := strings.Split(query, "?") if len(chunks) != len(args)+1 { return "", driver.ErrSkip }
;�̤Ǥ����������å��ȥ졼���Τ褦�ʡ֤�������αѿ����פ򸫤����ˡ�ȿ��Ū��̵�뤷�ƥ��顼��å����������򥳥ԥڤ��Ƽ��䤹��ͤ�褯�������ޤ����Τ����ɤ�Τ˵��Ϥ����뤫�⤷��ޤ��󤬡������å��ȥ졼���Ϥ��θ���䥢�ץꥱ�������򿼤��Τ���פʼ꤬����ʤΤǡ��ǥХå�������塼�˥󥰤���Ȥ��ʤɤϤ��ä���ȸ��������ޤ��礦��
4. ���塼�˥�
����ǽ�������ü�Ǥ������Ȥ�1��1�IJ������ʤ���Ǹ��̤��ǧ���Ƥ��������Ǥ����Ȥꤢ����������֤Υ٥���ޡ�����̤�Ĥ��Ƥ����ޤ��礦��
$ go test -bench=BenchmarkInterpolation | tee old.txt PASS BenchmarkInterpolation 500000 3942 ns/op 1144 B/op 15 allocs/op ok github.com/go-sql-driver/mysql 3.219s
4.1. ʸ����Ϣ�뤫�� []byte �ؤ� append ��
�ǽ�μ����ϡ�������ʸ����� ?
��ʬ�䤷�ơ� parts []string
��ʬ�䤵�줿�ƥ�����ʸ����ȡ��ѥ�᡼����ʸ���󲽤�����Τ��ߤ˳�Ǽ�� ("SELECT SLEEP(0)", 42
�ʤ� []string{"SELECT SLEEP(", "42", ")"}
)���Ǹ�� strings.Join(parts, "")
�Ƿ�礹��Ȥ�����LL�ǽ񤯤褦�ʥ����ɤǤ�����
Go ��ʸ����� immutable �ʤΤǡ�������ʸ����򤿤�����Ĥ��뤳�������Ǥϥ��������Ȥ�������Τ��򤱤��ޤ��󡣤����ǡ��ǽ�� []byte
�ΥХåե�����ݤ��Ƥ������ɵ����Ƥ����褦�ˤ��ޤ�����
�Хåե������̤���˥�����ʸ����ȳƥѥ�᡼�����鳵�����Ƴ��ݤ��뤳�Ȥǡ��ꥢ�������������򤱤ޤ���������¿��� strconv.AppendInt()
�Ȥ����� []byte
���ɵ����Ĥ�ʸ���󲽤���ؿ���ɸ��饤�֥����Ѱդ���Ƥ���ΤǤ����Ȥ��ޤ�������Ǽ��Τ褦�ʥ����ɤˤʤ�ޤ�����
buf := make([]byte, 0, estimated) argPos := 0 // Go 1.5 will optimize range([]byte(string)) to skip allocation. for _, c := range []byte(query) { if c != '?' { buf = append(buf, c) continue } arg := args[argPos] argPos++ if arg == nil { buf = append(buf, []byte("NULL")...) continue } switch v := arg.(type) { case int64: buf = strconv.AppendInt(buf, v, 10)
4.2. benchcmp
����ǰ�ö���̤��ǧ���ޤ������κݡ� benchcmp
�Ȥ����ġ����Ȥä���Ӥ�������򥳥ߥåȥ����˴ޤ��� Go �Υ��ߥå��ߤ����ǥ�����Ǥ���
$ go get -u golang.org/x/tools/cmd/benchcmp $ go test -bench=BenchmarkInterpolation > new.txt $ benchcmp old.txt new.txt benchmark old ns/op new ns/op delta BenchmarkInterpolation 3942 2573 -34.73% benchmark old allocs new allocs delta BenchmarkInterpolation 15 6 -60.00% benchmark old bytes new bytes delta BenchmarkInterpolation 1144 560 -51.05%
���������Ȳ���� 15 ���� 6 �˸��äƤ���Τ�ʬ����ޤ����¹�®�٤�������������˺︺�Ǥ��Ƥ��ޤ��͡�
4.3. Time.Format() ���򤱤�
int64 �� float64 �� strconv.AppendInt()
�� strconv.AppendFloat()
�Ǥ��줾�� []byte
���ɵ����뤳�Ȥ�����ޤ���������ǰ�ʤ��� time.Time
�ˤϤ��Τ褦�ʥ᥽�åɤ�����ޤ���
�����ǡ��٥å��٥��ʥ����ɤ�ʸ����ɽ������ޤ���
before:
case time.Time: if v.IsZero() { buf = append(buf, []byte("'0000-00-00'")...) } else { fmt := "'2006-01-02 15:04:05.999999'" if v.Nanosecond() == 0 { fmt = "'2006-01-02 15:04:05'" } s := v.In(mc.cfg.loc).Format(fmt) buf = append(buf, []byte(s)...) }
after:
case time.Time: if v.IsZero() { buf = append(buf, "'0000-00-00'"...) } else { v := v.In(mc.cfg.loc) v = v.Add(time.Nanosecond * 500) // To round under microsecond year := v.Year() year100 := year / 100 year1 := year % 100 month := v.Month() day := v.Day() hour := v.Hour() minute := v.Minute() second := v.Second() micro := v.Nanosecond() / 1000 buf = append(buf, []byte{ '\'', digits10[year100], digits01[year100], digits10[year1], digits01[year1], '-', digits10[month], digits01[month], '-', digits10[day], digits01[day], ' ', digits10[hour], digits01[hour], ':', digits10[minute], digits01[minute], ':', digits10[second], digits01[second], }...) if micro != 0 { micro10000 := micro / 10000 micro100 := micro / 100 % 100 micro1 := micro % 100 buf = append(buf, []byte{ '.', digits10[micro10000], digits01[micro10000], digits10[micro100], digits01[micro100], digits10[micro1], digits01[micro1], }...) } buf = append(buf, '\'') }
after �Ϻǽ��ǤΥ����ɤǡ����塼�˥󥰰ʳ��˥ޥ���������ʬ��0�ʤ��ά����ʤɤβ��������äƤ��ޤ���
digits10
, digits01
�Ͻ����򸺤餹����� 0~99 ��10�ΰ̤�1�ΰ̤�ʸ�����¤٤���ΤǤ�������Ϻ����Ѱդ�����ΤǤϤʤ��ƴ��� mysql �ɥ饤�Ф�¾�βս�����Ѥ���Ƥ�����Ŭ����Ŭ�Ѥ��ޤ�����
����ǥ��������Ȥ�2�ĸ��餻�ޤ�����
benchmark old allocs new allocs delta BenchmarkInterpolation 6 4 -33.33%
4.4. ʸ������Ф��� range ��Ȥ�ʤ�
Go �ǥ롼�פ�Ȥ��Ȥ����ɤ� range
��ʸ��Ȥ��ΤǤ����� string ���� s ���Ф��� for i, c := range s {
�Ȥ���ȡ��Х���ñ�̤ǤϤʤ� unicode �� code point ñ�̤ǥ롼�פ��Ƥ��ޤ��ޤ���
for i, c := range([]byte(s)) {
�Τ褦�� []byte
�˥��㥹�Ȥ��ƥ롼�פ��Ƥ����ΤǤ����������� string ���� []byte �ؤ��Ѵ��ǥ��������Ȥȥ��ԡ����¹Ԥ���Ƥ��ޤ��ޤ��� (Go 1.5 �ǤϺ�Ŭ������ޤ�)
�����ǡ������ C �������� for ʸ�˽�ľ���ޤ���
@@ -210,8 +210,8 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin buf := make([]byte, 0, estimated) argPos := 0 - // Go 1.5 will optimize range([]byte(string)) to skip allocation. - for _, c := range []byte(query) { + for i := 0; i < len(query); i++ { + c := query[i] if c != '?' {
����Ǥ⤦1�ĥ��������Ȥ򸺤餹���Ȥ��Ǥ��ޤ�����
benchmark old allocs new allocs delta BenchmarkInterpolation 4 3 -25.00%
4.5. []byte �� string ��ľ�� append ����
Go �� append �ˤϡ� []T ���Υ��饤�� ts �� T �������� t ��1���ɲä��� ts = append(ts, t)
�ȡ��̤� []T ���Υ��饤�� ts2 ���礹�� ts = append(ts, ts2...)
��2�ĤλȤ���������ޤ���
������ []byte �� string ���ɵ�������ˤĤ��Ƥϡ� string �� []byte ���Ǥ��뤫�Τ褦��ľ�� append �����Ѥ��뤳�Ȥ��Ǥ��ޤ��� buff = append(buff, s...)
���������Ȥ��ò¤±¤ë¤¿ï¿½ï¿½ï¿½ 1 �Х��Ȥ��� append ���Ƥ�����ʬ����ɤ����ꡢʸ�����ƥ��ò¥¥ã¥¹ï¿½È¤ï¿½ï¿½Ê¤ï¿½ï¿½ï¿½ append ���Ƥ�����ʬ(��Ŭ�������;�פʥ��������Ȥȥ��ԡ���ȯ�����ʤ�)��ʷ�ˤ�������Ǥ��ޤ�����
@@ -210,17 +210,19 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin argPos := 0 for i := 0; i < len(query); i++ { - c := query[i] - if c != '?' { - buf = append(buf, c) - continue + q := strings.IndexByte(query[i:], '?') + if q == -1 { + buf = append(buf, query[i:]...) + break } + buf = append(buf, query[i:i+q]...) + i += q arg := args[argPos] argPos++ if arg == nil { - buf = append(buf, []byte("NULL")...) + buf = append(buf, "NULL"...) continue }
����Ϥ���ǥ��������Ȥ��򤱤�����ʬ��̵���ä��Ǥ����� []byte �� append ���Ƥ��������ɤ�񤯤Ȥ��˳Ф��Ƥ������� Tips �Ǥ�����
4.6. []bytes �Ѥδؿ��� string �Ѥδؿ����̤��Ѱդ���
[]byte
������� string
���ΰ�����ξ��Ʊ���褦�˥������ȡ����������פ��ޤ���
����������ǰ�ʤ��� Go �� []byte �� string �δ֤��Ѵ��ǥ��������ȡ����ԡ���ȯ�����Ƥ��ޤ��ޤ���
��Ŭ���ǽ�����Τ� buf = append(buf, []byte("NULL")...)
�Τ褦�ʥ���ץ�ʥ����������ǡ� string ������˼��ؿ��� []byte
�� string(bs)
�ǥ��㥹�Ȥ����Ϥ����䤽�εդΥ������Ǥϸ��ߤ� Go �ǤϺ�Ŭ������ޤ���
�����ǡ�����Ʊ�����������׽�����Ԥ��ؿ��� []byte
������˼��С������� string
������˼��С�������2���Ѱդ��ޤ���
����ƥ롼����� switch �����Ǥⶦ�̲��Ǥ��ʤ����Ȼפ��ޤ�����������饤��Ÿ����Ԥ��������ͤȤʤ��ç¤ï¿½ï¿½ï¿½ï¿½Ä¶ï¿½ï¿½ï¿½Æ¤ï¿½ï¿½Þ¤ï¿½ï¿½è¤¦ï¿½ï¿½ï¿½ï¿½Ç½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Æ¤ï¿½ï¿½Þ¤Ã¤ï¿½ï¿½Î¤Ç¡ï¿½ï¿½Ø¤É¥ï¿½ï¿½Ô¥Ú¤ï¿½2�Ĥδؿ����Ѱդ��뤳�Ȥˤʤ�ޤ�����
����ϼ¹Ԥ���٤����¤ä��ΤǤ�����������ʸ�������٤ơ��ѥ�᡼�����Ϥ����ʸ���󡿥Х������ BLOB �� TEXT �����ç¤ï¿½Ê¥Ç¡ï¿½ï¿½ï¿½ï¿½Ç¤ï¿½ï¿½ï¿½ï¿½Ç½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Î¤Ç¡ï¿½Â®ï¿½Ù¡ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½È¶ï¿½ï¿½ï¿½ï¿½Å¶ï¿½ï¿½ï¿½ï¿½Ê¤ï¿½ï¿½ï¿½ï¿½È¤Ë¤ï¿½ï¿½Þ¤ï¿½ï¿½ï¿½ï¿½ï¿½
���ԥڥ����ɤϤ��������ƥʥ����˰��ƶ���ڤܤ��Τǡ�����ϺǸ�μ��ʤǤ�������� Go �ΥС������Dz��餫�β�����ʤ��Ѱդ���뤳�Ȥ���Ԥ��ޤ���
����ǡ����������Ȳ����2�ĤˤޤǸ���ޤ�����
benchmark old ns/op new ns/op delta BenchmarkInterpolation 2463 2118 -14.01% benchmark old allocs new allocs delta BenchmarkInterpolation 3 2 -33.33% benchmark old bytes new bytes delta BenchmarkInterpolation 496 448 -9.68%
4.7. �������Хåե��κ�����
�Ǹ�˻Ĥä�2�ĤΥ��������Ȥϡ��ǽ�˥Хåե����Ѱդ�����ʬ�ȡ��Ǹ�� MySQL �˥��ޥ�ɤ��ꤲ��ؿ����Ϥ�ʸ������뤿��˥Хåե��� string �˥��㥹�Ȥ���2�ս�Ǥ���
MySQL �˥��ޥ�ɤ���������Ȥ������Ѥ��Ƥ��������ѤΥХåե��ˡ��ѥ��åȥإå�����ʬ�������ľ�ܥ�������������뤳�ȤǺǸ��2�ĤΥ��������Ȥ�ä����Ȥ��Ǥ��ޤ���
����������¸�Υ��ޥ�ɤ�������ʬ���߷פ�����Ƥ��ޤ��Τȡ��ƥ��Ȥ����ˤ����ʤ�Τǡ�ľ�ܥѥ��åȤ��ä�����Τϼ��Ť��Ƥ����ơ������ѤΥХåե���interpolate�˻Ȥ������ˤ��ޤ�������������ѥ��åȤϥ������4�Х��ȤΥإå����դ�����ΤʤΤǡ�������Ū�ˤ�����Ѥ���ΤˤԤä���Ǥ���
benchmark old ns/op new ns/op delta BenchmarkInterpolation 1900 1403 -26.16% benchmark old allocs new allocs delta BenchmarkInterpolation 2 1 -50.00% benchmark old bytes new bytes delta BenchmarkInterpolation 448 160 -64.29%
�ޤȤ�
���������Ȳս�����ꤹ����ˡ�䡢���ʸ�����������塼�˥󥰤���ݤ˳Ф��Ƥ������������ǥ��󥰾�� Tips ��Ҳ𤷤ޤ�����
@methane