1
0
mirror of https://github.com/golang/go synced 2024-09-29 12:14:28 -06:00

reflectdata: unroll a loop in array equal function generation

As josharian mentioned, a compare function could benefit from
unrolling a loop for arrays. This commit introduces such
functionality.

name                     old time/op  new time/op  delta
EqArrayOfStrings5-12     12.5ns ± 1%   8.4ns ± 1%  -33.05%  (p=0.008 n=5+5)
EqArrayOfStrings64-12    71.7ns ± 1%  64.1ns ± 1%  -10.57%  (p=0.008 n=5+5)
EqArrayOfStrings1024-12  1.12µs ± 1%  1.01µs ± 0%   -9.77%  (p=0.008 n=5+5)
[Geo mean]                100ns         81ns       -18.56%

name                    old time/op  new time/op  delta
EqArrayOfFloats5-12     4.50ns ± 2%  3.32ns ± 1%  -26.09%  (p=0.008 n=5+5)
EqArrayOfFloats64-12    41.3ns ± 1%  35.7ns ± 0%  -13.63%  (p=0.016 n=5+4)
EqArrayOfFloats1024-12   619ns ± 1%   557ns ± 1%   -9.95%  (p=0.008 n=5+5)
[Geo mean]              48.6ns       40.4ns       -16.85%

Change-Id: If1b69c5cf3fb246bb0275a292118b0b93ad9c9a8
Reviewed-on: https://go-review.googlesource.com/c/go/+/368614
Reviewed-by: Keith Randall <khr@golang.org>
Trust: Emmanuel Odeke <emmanuel@orijtech.com>
Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
vpachkov 2021-12-02 16:18:29 +03:00 committed by Emmanuel Odeke
parent 5210a71285
commit 1a955bcdb7
2 changed files with 136 additions and 34 deletions

View File

@ -412,22 +412,25 @@ func geneq(t *types.Type) *obj.LSym {
// //
// if eq(p[0], q[0]) && eq(p[1], q[1]) && ... { // if eq(p[0], q[0]) && eq(p[1], q[1]) && ... {
// } else { // } else {
// return // goto neq
// } // }
// //
// And so on. // And so on.
// //
// Otherwise it generates: // Otherwise it generates:
// //
// for i := 0; i < nelem; i++ { // iterateTo := nelem/unroll*unroll
// if eq(p[i], q[i]) { // for i := 0; i < iterateTo; i += unroll {
// if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) {
// } else { // } else {
// goto neq // goto neq
// } // }
// } // }
// if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... {
// } else {
// goto neq
// }
// //
// TODO(josharian): consider doing some loop unrolling
// for larger nelem as well, processing a few elements at a time in a loop.
checkAll := func(unroll int64, last bool, eq func(pi, qi ir.Node) ir.Node) { checkAll := func(unroll int64, last bool, eq func(pi, qi ir.Node) ir.Node) {
// checkIdx generates a node to check for equality at index i. // checkIdx generates a node to check for equality at index i.
checkIdx := func(i ir.Node) ir.Node { checkIdx := func(i ir.Node) ir.Node {
@ -442,38 +445,62 @@ func geneq(t *types.Type) *obj.LSym {
return eq(pi, qi) return eq(pi, qi)
} }
if nelem <= unroll { iterations := nelem / unroll
if last { iterateTo := iterations * unroll
// Do last comparison in a different manner. // If a loop is iterated only once, there shouldn't be any loop at all.
nelem-- if iterations == 1 {
} iterateTo = 0
// Generate a series of checks. }
for i := int64(0); i < nelem; i++ {
// if check {} else { goto neq } if iterateTo > 0 {
nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(i)), nil, nil) // Generate an unrolled for loop.
nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) // for i := 0; i < nelem/unroll*unroll; i += unroll
fn.Body.Append(nif)
}
if last {
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, checkIdx(ir.NewInt(nelem))))
}
} else {
// Generate a for loop.
// for i := 0; i < nelem; i++
i := typecheck.Temp(types.Types[types.TINT]) i := typecheck.Temp(types.Types[types.TINT])
init := ir.NewAssignStmt(base.Pos, i, ir.NewInt(0)) init := ir.NewAssignStmt(base.Pos, i, ir.NewInt(0))
cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(nelem)) cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(iterateTo))
post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(1))) loop := ir.NewForStmt(base.Pos, nil, cond, nil, nil)
loop := ir.NewForStmt(base.Pos, nil, cond, post, nil)
loop.PtrInit().Append(init) loop.PtrInit().Append(init)
// if eq(pi, qi) {} else { goto neq }
nif := ir.NewIfStmt(base.Pos, checkIdx(i), nil, nil) // if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) {
nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) // } else {
loop.Body.Append(nif) // goto neq
fn.Body.Append(loop) // }
if last { for j := int64(0); j < unroll; j++ {
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true))) // if check {} else { goto neq }
nif := ir.NewIfStmt(base.Pos, checkIdx(i), nil, nil)
nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
loop.Body.Append(nif)
post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(1)))
loop.Body.Append(post)
} }
fn.Body.Append(loop)
if nelem == iterateTo {
if last {
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true)))
}
return
}
}
// Generate remaining checks, if nelem is not a multiple of unroll.
if last {
// Do last comparison in a different manner.
nelem--
}
// if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... {
// } else {
// goto neq
// }
for j := iterateTo; j < nelem; j++ {
// if check {} else { goto neq }
nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(j)), nil, nil)
nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
fn.Body.Append(nif)
}
if last {
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, checkIdx(ir.NewInt(nelem))))
} }
} }
@ -481,7 +508,6 @@ func geneq(t *types.Type) *obj.LSym {
case types.TSTRING: case types.TSTRING:
// Do two loops. First, check that all the lengths match (cheap). // Do two loops. First, check that all the lengths match (cheap).
// Second, check that all the contents match (expensive). // Second, check that all the contents match (expensive).
// TODO: when the array size is small, unroll the length match checks.
checkAll(3, false, func(pi, qi ir.Node) ir.Node { checkAll(3, false, func(pi, qi ir.Node) ir.Node {
// Compare lengths. // Compare lengths.
eqlen, _ := EqString(pi, qi) eqlen, _ := EqString(pi, qi)

View File

@ -0,0 +1,76 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package reflectdata_test
import "testing"
func BenchmarkEqArrayOfStrings5(b *testing.B) {
var a [5]string
var c [5]string
for i := 0; i < 5; i++ {
a[i] = "aaaa"
c[i] = "cccc"
}
for j := 0; j < b.N; j++ {
_ = a == c
}
}
func BenchmarkEqArrayOfStrings64(b *testing.B) {
var a [64]string
var c [64]string
for i := 0; i < 64; i++ {
a[i] = "aaaa"
c[i] = "cccc"
}
for j := 0; j < b.N; j++ {
_ = a == c
}
}
func BenchmarkEqArrayOfStrings1024(b *testing.B) {
var a [1024]string
var c [1024]string
for i := 0; i < 1024; i++ {
a[i] = "aaaa"
c[i] = "cccc"
}
for j := 0; j < b.N; j++ {
_ = a == c
}
}
func BenchmarkEqArrayOfFloats5(b *testing.B) {
var a [5]float32
var c [5]float32
for i := 0; i < b.N; i++ {
_ = a == c
}
}
func BenchmarkEqArrayOfFloats64(b *testing.B) {
var a [64]float32
var c [64]float32
for i := 0; i < b.N; i++ {
_ = a == c
}
}
func BenchmarkEqArrayOfFloats1024(b *testing.B) {
var a [1024]float32
var c [1024]float32
for i := 0; i < b.N; i++ {
_ = a == c
}
}