Skip to content

Commit d6054b3

Browse files
authored
feat: slightly optimize CellsToMultiPolygon and LatLng#String (#86)
Before: ``` BenchmarkCellsToMultiPolygon-16 589149 2022 ns/op 344 B/op 8 allocs/op BenchmarkLatLng_String-16 6480610 184.2 ns/op 40 B/op 3 allocs/op ``` After: ``` BenchmarkCellsToMultiPolygon-16 600781 2006 ns/op 200 B/op 5 allocs/op BenchmarkLatLng_String-16 9569876 126.4 ns/op 24 B/op 1 allocs/op ```
1 parent 41962bc commit d6054b3

File tree

2 files changed

+76
-32
lines changed

2 files changed

+76
-32
lines changed

bench_test.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,53 @@ var (
1010
Lat: 37,
1111
Lng: -122,
1212
}
13-
cell, _ = LatLngToCell(geo, 15)
14-
addr = cell.String()
15-
geoBndry CellBoundary
16-
cells []Cell
17-
disks [][]Cell
13+
latlngStr string
14+
cell, _ = LatLngToCell(geo, 15)
15+
addr = cell.String()
16+
geoBndry CellBoundary
17+
cells []Cell
18+
disks [][]Cell
1819
)
1920

2021
func BenchmarkToString(b *testing.B) {
21-
for n := 0; n < b.N; n++ {
22+
for range b.N {
2223
addr = cell.String()
2324
}
2425
}
2526

2627
func BenchmarkFromString(b *testing.B) {
27-
for n := 0; n < b.N; n++ {
28+
for range b.N {
2829
//nolint:gosec // IndexFromString returns uint64 and fixing that to detect integer overflows will break package API. Let's skip it for now.
2930
cell = Cell(IndexFromString("850dab63fffffff"))
3031
}
3132
}
3233

34+
func BenchmarkLatLng_String(b *testing.B) {
35+
for range b.N {
36+
latlngStr = geo.String()
37+
}
38+
}
39+
3340
func BenchmarkCellToLatLng(b *testing.B) {
34-
for n := 0; n < b.N; n++ {
41+
for range b.N {
3542
geo, _ = CellToLatLng(cell)
3643
}
3744
}
3845

3946
func BenchmarkLatLngToCell(b *testing.B) {
40-
for n := 0; n < b.N; n++ {
47+
for range b.N {
4148
cell, _ = LatLngToCell(geo, 15)
4249
}
4350
}
4451

4552
func BenchmarkCellToBoundary(b *testing.B) {
46-
for n := 0; n < b.N; n++ {
53+
for range b.N {
4754
geoBndry, _ = CellToBoundary(cell)
4855
}
4956
}
5057

5158
func BenchmarkGridDisk(b *testing.B) {
52-
for n := 0; n < b.N; n++ {
59+
for range b.N {
5360
cells, _ = cell.GridDisk(10)
5461
}
5562
}
@@ -61,7 +68,7 @@ func BenchmarkGridRing(b *testing.B) {
6168
}
6269

6370
func BenchmarkPolyfill(b *testing.B) {
64-
for n := 0; n < b.N; n++ {
71+
for range b.N {
6572
cells, _ = PolygonToCells(validGeoPolygonHoles, 13)
6673
}
6774
}
@@ -71,7 +78,7 @@ func BenchmarkGridDisksUnsafe(b *testing.B) {
7178

7279
b.ResetTimer()
7380

74-
for n := 0; n < b.N; n++ {
81+
for range b.N {
7582
disks, _ = GridDisksUnsafe(cells, 10)
7683
}
7784
}

h3.go

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import "C"
3232

3333
import (
3434
"errors"
35-
"fmt"
3635
"math"
3736
"strconv"
3837
"strings"
@@ -72,6 +71,15 @@ const (
7271
RadsToDegs = 180.0 / math.Pi
7372
)
7473

74+
const (
75+
latLngFloatPrecision = 5
76+
// latLngStringSize is the size to pre-allocate the buffer for.
77+
// Given latLngFloatPrecision, a typical string is "(DD.DDDDD, -DDD.DDDDD)"
78+
// which is ~25-30 bytes. 32 is a safe and efficient capacity to start with
79+
// to avoid re-allocation.
80+
latLngStringSize = 32
81+
)
82+
7583
// PolygonToCells containment modes
7684
const (
7785
ContainmentCenter ContainmentMode = C.CONTAINMENT_CENTER // Cell center is contained in the shape
@@ -122,7 +130,6 @@ var (
122130
)
123131

124132
type (
125-
126133
// Cell is an Index that identifies a single hexagon cell at a resolution.
127134
Cell int64
128135

@@ -468,7 +475,7 @@ func PolygonToCells(polygon GeoPolygon, resolution int) ([]Cell, error) {
468475
// and then any newly found hexagons are used to test again until no new
469476
// hexagons are found.
470477
func PolygonToCellsExperimental(polygon GeoPolygon, resolution int, mode ContainmentMode, maxNumCellsReturn ...int64) ([]Cell, error) {
471-
var maxNumCells int64 = math.MaxInt64
478+
maxNumCells := int64(math.MaxInt64)
472479
if len(maxNumCellsReturn) > 0 {
473480
maxNumCells = maxNumCellsReturn[0]
474481
}
@@ -519,30 +526,55 @@ func CellsToMultiPolygon(cells []Cell) ([]GeoPolygon, error) {
519526
return nil, err
520527
}
521528

522-
ret := []GeoPolygon{}
529+
currPoly := cLinkedGeoPolygon
530+
var countPoly int
531+
for currPoly != nil {
532+
countPoly++
533+
currPoly = currPoly.next
534+
}
535+
536+
ret := make([]GeoPolygon, countPoly)
523537

524538
// traverse polygons for linked list of polygons
525-
currPoly := cLinkedGeoPolygon
539+
currPoly = cLinkedGeoPolygon
540+
countPoly = 0
526541
for currPoly != nil {
527-
loops := []GeoLoop{}
542+
currLoop := currPoly.first
543+
var countLoop int
544+
for currLoop != nil {
545+
countLoop++
546+
currLoop = currLoop.next
547+
}
548+
loops := make([]GeoLoop, countLoop)
528549

529550
// traverse loops for a polygon
530-
currLoop := currPoly.first
551+
currLoop = currPoly.first
552+
countLoop = 0
531553
for currLoop != nil {
532-
loop := []LatLng{}
554+
currPt := currLoop.first
555+
var countPt int
556+
for currPt != nil {
557+
countPt++
558+
currPt = currPt.next
559+
}
560+
loop := make([]LatLng, countPt)
533561

534562
// traverse points for a loop
535-
currPt := currLoop.first
563+
currPt = currLoop.first
564+
countPt = 0
536565
for currPt != nil {
537-
loop = append(loop, latLngFromC(currPt.vertex))
566+
loop[countPt] = latLngFromC(currPt.vertex)
567+
countPt++
538568
currPt = currPt.next
539569
}
540570

541-
loops = append(loops, loop)
571+
loops[countLoop] = loop
572+
countLoop++
542573
currLoop = currLoop.next
543574
}
544575

545-
ret = append(ret, GeoPolygon{GeoLoop: loops[0], Holes: loops[1:]})
576+
ret[countPoly] = GeoPolygon{GeoLoop: loops[0], Holes: loops[1:]}
577+
countPoly++
546578
currPoly = currPoly.next
547579
}
548580

@@ -669,7 +701,7 @@ func EdgeLengthM(e DirectedEdge) (float64, error) {
669701
func NumCells(resolution int) int {
670702
// NOTE: this is a mathematical operation, no need to call into H3 library.
671703
// See h3api.h for formula derivation.
672-
return 2 + 120*intPow(7, (resolution)) //nolint:mnd // math formula
704+
return 2 + 120*intPow(7, resolution) //nolint:mnd // math formula
673705
}
674706

675707
// Res0Cells returns all the cells at resolution 0.
@@ -1059,11 +1091,10 @@ func maxGridDiskSize(k int) int {
10591091
}
10601092

10611093
func latLngFromC(cg C.LatLng) LatLng {
1062-
g := LatLng{}
1063-
g.Lat = RadsToDegs * float64(cg.lat)
1064-
g.Lng = RadsToDegs * float64(cg.lng)
1065-
1066-
return g
1094+
return LatLng{
1095+
Lat: RadsToDegs * float64(cg.lat),
1096+
Lng: RadsToDegs * float64(cg.lng),
1097+
}
10671098
}
10681099

10691100
func cellBndryFromC(cb *C.CellBoundary) CellBoundary {
@@ -1229,7 +1260,13 @@ func intsFromC(chs []C.int) []int {
12291260
}
12301261

12311262
func (g LatLng) String() string {
1232-
return fmt.Sprintf("(%.5f, %.5f)", g.Lat, g.Lng)
1263+
buf := make([]byte, 0, latLngStringSize)
1264+
buf = append(buf, '(')
1265+
buf = strconv.AppendFloat(buf, g.Lat, 'f', latLngFloatPrecision, 64) //nolint:mnd // float bit size
1266+
buf = append(buf, ',', ' ')
1267+
buf = strconv.AppendFloat(buf, g.Lng, 'f', latLngFloatPrecision, 64) //nolint:mnd // float bit size
1268+
buf = append(buf, ')')
1269+
return string(buf)
12331270
}
12341271

12351272
func (g LatLng) toCPtr() *C.LatLng {

0 commit comments

Comments
 (0)