├── LICENSE ├── Makefile ├── README.md ├── README.md.j2 ├── benchmarks_test.go ├── make_benchmarks.py ├── meta.py ├── packages.yaml ├── results ├── DecodeString.stat ├── EncodeInt.stat ├── EncodeString.stat └── meta.out └── util_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michael McLoughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: benchmarks_test.go README.md 2 | 3 | benchmarks_test.go: make_benchmarks.py packages.yaml 4 | python $< packages.yaml > $@ 5 | goimports -w $@ 6 | go list -f '{{ join .TestImports "\n"}}' | xargs go get -u -v 7 | 8 | results/%.out: benchmarks_test.go 9 | mkdir -p results 10 | go test -bench $* | grep Benchmark | sort -k3,3n > $@ 11 | 12 | %.stat: %.out 13 | benchstat $< > $@ 14 | 15 | README.md: README.md.j2 packages.yaml meta.py results/EncodeString.stat results/EncodeInt.stat results/DecodeString.stat 16 | python meta.py > results/meta.out 17 | j2 --format=yaml $< packages.yaml > $@ 18 | 19 | deps: 20 | pip install pyyaml j2cli 21 | go get -u golang.org/x/perf/cmd/benchstat 22 | 23 | clean: 24 | $(RM) benchmarks_test.go README.md 25 | $(RM) -r results 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geohashbench 2 | Benchmarks to compare golang geohash implementations. 3 | 4 | ## Results 5 | 6 | ### String Encoding 7 | 8 | ``` 9 | name time/op 10 | MmcloughlinEncodeString-4 44.3ns ± 0% 11 | PierrreEncodeString-4 460ns ± 0% 12 | CodeforEncodeString-4 468ns ± 0% 13 | GansiduiEncodeString-4 522ns ± 0% 14 | TomihiltunenEncodeString-4 545ns ± 0% 15 | FanixkEncodeString-4 547ns ± 0% 16 | TidwallEncodeString-4 602ns ± 0% 17 | BroadyEncodeString-4 1.06µs ± 0% 18 | ``` 19 | 20 | ### Integer Encoding 21 | 22 | ``` 23 | name time/op 24 | MmcloughlinEncodeInt-4 3.74ns ± 0% 25 | BsmEncodeInt-4 15.0ns ± 0% 26 | EzzkoramEncodeInt-4 34.1ns ± 0% 27 | CorscEncodeInt52-4 363ns ± 0% 28 | ``` 29 | 30 | ### String Decoding 31 | 32 | ``` 33 | name time/op 34 | MmcloughlinDecodeString-4 106ns ± 0% 35 | PierrreDecodeString-4 297ns ± 0% 36 | CodeforDecodeString-4 367ns ± 0% 37 | BroadyDecodeString-4 429ns ± 0% 38 | TidwallDecodeString-4 446ns ± 0% 39 | TomihiltunenDecodeString-4 512ns ± 0% 40 | FanixkDecodeString-4 569ns ± 0% 41 | ``` 42 | 43 | ### Meta 44 | 45 | ``` 46 | $ date 47 | Sun Aug 5 21:12:51 PDT 2018 48 | $ go version 49 | go version go1.10.3 darwin/amd64 50 | $ sysctl -n machdep.cpu.brand_string 51 | Intel(R) Core(TM) i7-7567U CPU @ 3.50GHz 52 | ``` 53 | 54 | ## Packages Tested 55 | 56 | * [mmcloughlin/geohash](https://github.com/mmcloughlin/geohash) ([godoc](https://godoc.org/github.com/mmcloughlin/geohash)) 57 | * [TomiHiltunen/geohash-golang](https://github.com/TomiHiltunen/geohash-golang) ([godoc](https://godoc.org/github.com/TomiHiltunen/geohash-golang)) 58 | * [gansidui/geohash](https://github.com/gansidui/geohash) ([godoc](https://godoc.org/github.com/gansidui/geohash)) 59 | * [pierrre/geohash](https://github.com/pierrre/geohash) ([godoc](https://godoc.org/github.com/pierrre/geohash)) 60 | * [broady/gogeohash](https://github.com/broady/gogeohash) ([godoc](https://godoc.org/github.com/broady/gogeohash)) 61 | * [bsm/geohashi](https://github.com/bsm/geohashi) ([godoc](https://godoc.org/github.com/bsm/geohashi)) 62 | * [corsc/go-geohash](https://github.com/corsc/go-geohash) ([godoc](https://godoc.org/github.com/corsc/go-geohash)) 63 | * [ezzkoram/geohash](https://github.com/ezzkoram/geohash) ([godoc](https://godoc.org/github.com/ezzkoram/geohash)) 64 | * [Codefor/geohash](https://github.com/Codefor/geohash) ([godoc](https://godoc.org/github.com/Codefor/geohash)) 65 | * [fanixk/geohash](https://github.com/fanixk/geohash) ([godoc](https://godoc.org/github.com/fanixk/geohash)) 66 | * [tidwall/tile38/pkg/geojson/geohash](https://github.com/tidwall/tile38/pkg/geojson/geohash) ([godoc](https://godoc.org/github.com/tidwall/tile38/pkg/geojson/geohash)) -------------------------------------------------------------------------------- /README.md.j2: -------------------------------------------------------------------------------- 1 | # geohashbench 2 | Benchmarks to compare golang geohash implementations. 3 | 4 | ## Results 5 | 6 | ### String Encoding 7 | 8 | ``` 9 | {% include 'results/EncodeString.stat' %} 10 | ``` 11 | 12 | ### Integer Encoding 13 | 14 | ``` 15 | {% include 'results/EncodeInt.stat' %} 16 | ``` 17 | 18 | ### String Decoding 19 | 20 | ``` 21 | {% include 'results/DecodeString.stat' %} 22 | ``` 23 | 24 | ### Meta 25 | 26 | ``` 27 | {% include 'results/meta.out' %} 28 | ``` 29 | 30 | ## Packages Tested 31 | {% for package in packages -%} 32 | {% set name = '/'.join(package['import'].split('/')[1:]) %} 33 | * [{{ name }}](https://{{ package['import'] }}) ([godoc](https://godoc.org/{{ package['import'] }})) 34 | {%- endfor %} 35 | -------------------------------------------------------------------------------- /benchmarks_test.go: -------------------------------------------------------------------------------- 1 | package geohashbench 2 | 3 | import ( 4 | "testing" 5 | 6 | codefor "github.com/Codefor/geohash" 7 | tomihiltunen "github.com/TomiHiltunen/geohash-golang" 8 | broady "github.com/broady/gogeohash" 9 | bsm "github.com/bsm/geohashi" 10 | corsc "github.com/corsc/go-geohash" 11 | ezzkoram "github.com/ezzkoram/geohash" 12 | fanixk "github.com/fanixk/geohash" 13 | gansidui "github.com/gansidui/geohash" 14 | mmcloughlin "github.com/mmcloughlin/geohash" 15 | pierrre "github.com/pierrre/geohash" 16 | tidwall "github.com/tidwall/tile38/pkg/geojson/geohash" 17 | ) 18 | 19 | func BenchmarkMmcloughlinEncodeInt(b *testing.B) { 20 | points := RandomPoints(1024) 21 | b.ResetTimer() 22 | for i := 0; i < b.N; i++ { 23 | mmcloughlin.EncodeInt(points[i%1024][0], points[i%1024][1]) 24 | } 25 | } 26 | 27 | func BenchmarkMmcloughlinEncodeString(b *testing.B) { 28 | points := RandomPoints(1024) 29 | b.ResetTimer() 30 | for i := 0; i < b.N; i++ { 31 | mmcloughlin.Encode(points[i%1024][0], points[i%1024][1]) 32 | } 33 | } 34 | 35 | func BenchmarkMmcloughlinDecodeString(b *testing.B) { 36 | geohashes := RandomStringGeohashes(1024) 37 | b.ResetTimer() 38 | for i := 0; i < b.N; i++ { 39 | mmcloughlin.Decode(geohashes[i%1024]) 40 | } 41 | } 42 | 43 | func BenchmarkTomihiltunenEncodeString(b *testing.B) { 44 | points := RandomPoints(1024) 45 | b.ResetTimer() 46 | for i := 0; i < b.N; i++ { 47 | tomihiltunen.Encode(points[i%1024][0], points[i%1024][1]) 48 | } 49 | } 50 | 51 | func BenchmarkTomihiltunenDecodeString(b *testing.B) { 52 | geohashes := RandomStringGeohashes(1024) 53 | b.ResetTimer() 54 | for i := 0; i < b.N; i++ { 55 | tomihiltunen.Decode(geohashes[i%1024]) 56 | } 57 | } 58 | 59 | func BenchmarkGansiduiEncodeString(b *testing.B) { 60 | points := RandomPoints(1024) 61 | b.ResetTimer() 62 | for i := 0; i < b.N; i++ { 63 | gansidui.Encode(points[i%1024][0], points[i%1024][1], 12) 64 | } 65 | } 66 | 67 | func BenchmarkPierrreEncodeString(b *testing.B) { 68 | points := RandomPoints(1024) 69 | b.ResetTimer() 70 | for i := 0; i < b.N; i++ { 71 | pierrre.Encode(points[i%1024][0], points[i%1024][1], 12) 72 | } 73 | } 74 | 75 | func BenchmarkPierrreDecodeString(b *testing.B) { 76 | geohashes := RandomStringGeohashes(1024) 77 | b.ResetTimer() 78 | for i := 0; i < b.N; i++ { 79 | pierrre.Decode(geohashes[i%1024]) 80 | } 81 | } 82 | 83 | func BenchmarkBroadyEncodeString(b *testing.B) { 84 | points := RandomPoints(1024) 85 | b.ResetTimer() 86 | for i := 0; i < b.N; i++ { 87 | broady.Encode(points[i%1024][0], points[i%1024][1]) 88 | } 89 | } 90 | 91 | func BenchmarkBroadyDecodeString(b *testing.B) { 92 | geohashes := RandomStringGeohashes(1024) 93 | b.ResetTimer() 94 | for i := 0; i < b.N; i++ { 95 | broady.Decode(geohashes[i%1024]) 96 | } 97 | } 98 | 99 | func BenchmarkBsmEncodeInt(b *testing.B) { 100 | points := RandomPoints(1024) 101 | b.ResetTimer() 102 | for i := 0; i < b.N; i++ { 103 | bsm.Encode(points[i%1024][0], points[i%1024][1]) 104 | } 105 | } 106 | 107 | func BenchmarkCorscEncodeInt52(b *testing.B) { 108 | points := RandomPoints(1024) 109 | b.ResetTimer() 110 | for i := 0; i < b.N; i++ { 111 | corsc.EncodeInt(points[i%1024][0], points[i%1024][1], 52) 112 | } 113 | } 114 | 115 | func BenchmarkEzzkoramEncodeInt(b *testing.B) { 116 | points := RandomPoints(1024) 117 | b.ResetTimer() 118 | for i := 0; i < b.N; i++ { 119 | ezzkoram.FromCoordinates(points[i%1024][0], points[i%1024][1]).Hash() 120 | } 121 | } 122 | 123 | func BenchmarkCodeforEncodeString(b *testing.B) { 124 | points := RandomPoints(1024) 125 | b.ResetTimer() 126 | for i := 0; i < b.N; i++ { 127 | codefor.Encode(points[i%1024][0], points[i%1024][1]) 128 | } 129 | } 130 | 131 | func BenchmarkCodeforDecodeString(b *testing.B) { 132 | geohashes := RandomStringGeohashes(1024) 133 | b.ResetTimer() 134 | for i := 0; i < b.N; i++ { 135 | codefor.Decode(geohashes[i%1024]) 136 | } 137 | } 138 | 139 | func BenchmarkFanixkEncodeString(b *testing.B) { 140 | points := RandomPoints(1024) 141 | b.ResetTimer() 142 | for i := 0; i < b.N; i++ { 143 | fanixk.Encode(points[i%1024][0], points[i%1024][1]) 144 | } 145 | } 146 | 147 | func BenchmarkFanixkDecodeString(b *testing.B) { 148 | geohashes := RandomStringGeohashes(1024) 149 | b.ResetTimer() 150 | for i := 0; i < b.N; i++ { 151 | fanixk.Decode(geohashes[i%1024]) 152 | } 153 | } 154 | 155 | func BenchmarkTidwallEncodeString(b *testing.B) { 156 | points := RandomPoints(1024) 157 | b.ResetTimer() 158 | for i := 0; i < b.N; i++ { 159 | tidwall.Encode(points[i%1024][0], points[i%1024][1], 12) 160 | } 161 | } 162 | 163 | func BenchmarkTidwallDecodeString(b *testing.B) { 164 | geohashes := RandomStringGeohashes(1024) 165 | b.ResetTimer() 166 | for i := 0; i < b.N; i++ { 167 | tidwall.Decode(geohashes[i%1024]) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /make_benchmarks.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import yaml 3 | 4 | 5 | def read_packages(f): 6 | pkgs = yaml.load(f)['packages'] 7 | for pkg in pkgs: 8 | enrich_package(pkg) 9 | return pkgs 10 | 11 | 12 | def enrich_package(pkg): 13 | path = pkg['import'] 14 | username = path.split('/')[1] 15 | pkg.setdefault('alias', username.lower()) 16 | pkg.setdefault('label', username.title()) 17 | 18 | 19 | benchmark_template = ''' 20 | func Benchmark{label}{name}(b *testing.B) {{ 21 | {vectors} := {generator}({num}) 22 | b.ResetTimer() 23 | for i := 0; i < b.N; i++ {{ 24 | {alias}.{func} 25 | }} 26 | }} 27 | ''' 28 | 29 | 30 | INPUT_TYPES = { 31 | 'Encode': { 32 | 'generator': 'RandomPoints', 33 | 'vectors': 'points', 34 | 'num': 1024, 35 | 'lat': 'points[i%1024][0]', 36 | 'lng': 'points[i%1024][1]', 37 | }, 38 | 'DecodeString': { 39 | 'generator': 'RandomStringGeohashes', 40 | 'num': 1024, 41 | 'vectors': 'geohashes', 42 | 'geohash': 'geohashes[i%1024]', 43 | }, 44 | } 45 | 46 | 47 | def output_benchmarks(pkgs): 48 | print 'package geohashbench' 49 | print 50 | 51 | print 'import (' 52 | print '\t"testing"' 53 | for pkg in pkgs: 54 | print '\t{alias} "{import}"'.format(**pkg) 55 | print ')' 56 | 57 | for pkg in pkgs: 58 | for name, tmpl in pkg.get('benchmarks', {}).items(): 59 | for pattern, input_type in INPUT_TYPES.items(): 60 | if name.startswith(pattern): 61 | break 62 | params = dict(pkg) 63 | params.update(input_type) 64 | params['name'] = name 65 | params['func'] = tmpl.format(**params) 66 | print benchmark_template.format(**params) 67 | 68 | 69 | def main(args): 70 | filename = args[1] 71 | with open(filename) as f: 72 | pkgs = read_packages(f) 73 | output_benchmarks(pkgs) 74 | 75 | 76 | if __name__ == '__main__': 77 | main(sys.argv) 78 | -------------------------------------------------------------------------------- /meta.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | 4 | 5 | COMMANDS = [ 6 | 'date', 7 | 'go version', 8 | 'sysctl -n machdep.cpu.brand_string', 9 | ] 10 | 11 | 12 | for cmd in COMMANDS: 13 | print '$', cmd 14 | sys.stdout.flush() 15 | subprocess.check_call(cmd, shell=True) 16 | -------------------------------------------------------------------------------- /packages.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - import: github.com/mmcloughlin/geohash 3 | benchmarks: 4 | EncodeString: Encode({lat}, {lng}) 5 | EncodeInt: EncodeInt({lat}, {lng}) 6 | DecodeString: Decode({geohash}) 7 | - import: github.com/TomiHiltunen/geohash-golang 8 | benchmarks: 9 | EncodeString: Encode({lat}, {lng}) 10 | DecodeString: Decode({geohash}) 11 | - import: github.com/gansidui/geohash 12 | benchmarks: 13 | EncodeString: Encode({lat}, {lng}, 12) 14 | - import: github.com/pierrre/geohash 15 | benchmarks: 16 | EncodeString: Encode({lat}, {lng}, 12) 17 | DecodeString: Decode({geohash}) 18 | - import: github.com/broady/gogeohash 19 | benchmarks: 20 | EncodeString: Encode({lat}, {lng}) 21 | DecodeString: Decode({geohash}) 22 | - import: github.com/bsm/geohashi 23 | benchmarks: 24 | EncodeInt: Encode({lat}, {lng}) 25 | - import: github.com/corsc/go-geohash 26 | benchmarks: 27 | EncodeInt52: EncodeInt({lat}, {lng}, 52) 28 | - import: github.com/ezzkoram/geohash 29 | benchmarks: 30 | EncodeInt: FromCoordinates({lat}, {lng}).Hash() 31 | - import: github.com/Codefor/geohash 32 | benchmarks: 33 | EncodeString: Encode({lat}, {lng}) 34 | DecodeString: Decode({geohash}) 35 | - import: github.com/fanixk/geohash 36 | benchmarks: 37 | EncodeString: Encode({lat}, {lng}) 38 | DecodeString: Decode({geohash}) 39 | - import: github.com/tidwall/tile38/pkg/geojson/geohash 40 | benchmarks: 41 | EncodeString: Encode({lat}, {lng}, 12) 42 | DecodeString: Decode({geohash}) 43 | -------------------------------------------------------------------------------- /results/DecodeString.stat: -------------------------------------------------------------------------------- 1 | name time/op 2 | MmcloughlinDecodeString-4 106ns ± 0% 3 | PierrreDecodeString-4 297ns ± 0% 4 | CodeforDecodeString-4 367ns ± 0% 5 | BroadyDecodeString-4 429ns ± 0% 6 | TidwallDecodeString-4 446ns ± 0% 7 | TomihiltunenDecodeString-4 512ns ± 0% 8 | FanixkDecodeString-4 569ns ± 0% 9 | -------------------------------------------------------------------------------- /results/EncodeInt.stat: -------------------------------------------------------------------------------- 1 | name time/op 2 | MmcloughlinEncodeInt-4 3.74ns ± 0% 3 | BsmEncodeInt-4 15.0ns ± 0% 4 | EzzkoramEncodeInt-4 34.1ns ± 0% 5 | CorscEncodeInt52-4 363ns ± 0% 6 | -------------------------------------------------------------------------------- /results/EncodeString.stat: -------------------------------------------------------------------------------- 1 | name time/op 2 | MmcloughlinEncodeString-4 44.3ns ± 0% 3 | PierrreEncodeString-4 460ns ± 0% 4 | CodeforEncodeString-4 468ns ± 0% 5 | GansiduiEncodeString-4 522ns ± 0% 6 | TomihiltunenEncodeString-4 545ns ± 0% 7 | FanixkEncodeString-4 547ns ± 0% 8 | TidwallEncodeString-4 602ns ± 0% 9 | BroadyEncodeString-4 1.06µs ± 0% 10 | -------------------------------------------------------------------------------- /results/meta.out: -------------------------------------------------------------------------------- 1 | $ date 2 | Sun Aug 5 21:12:51 PDT 2018 3 | $ go version 4 | go version go1.10.3 darwin/amd64 5 | $ sysctl -n machdep.cpu.brand_string 6 | Intel(R) Core(TM) i7-7567U CPU @ 3.50GHz 7 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package geohashbench 2 | 3 | import "math/rand" 4 | 5 | func RandomPoint() (lat, lng float64) { 6 | lat = -90 + 180*rand.Float64() 7 | lng = -180 + 360*rand.Float64() 8 | return 9 | } 10 | 11 | func RandomPoints(n int) [][2]float64 { 12 | points := make([][2]float64, n) 13 | for i := 0; i < n; i++ { 14 | lat, lng := RandomPoint() 15 | points[i] = [2]float64{lat, lng} 16 | } 17 | return points 18 | } 19 | 20 | func RandomStringGeohash() string { 21 | const alphabet = "0123456789bcdefghjkmnpqrstuvwxyz" 22 | chars := [12]byte{} 23 | for i := 0; i < 12; i++ { 24 | chars[i] = alphabet[rand.Intn(32)] 25 | } 26 | return string(chars[:]) 27 | } 28 | 29 | func RandomStringGeohashes(n int) []string { 30 | geohashes := make([]string, n) 31 | for i := 0; i < n; i++ { 32 | geohashes[i] = RandomStringGeohash() 33 | } 34 | return geohashes 35 | } 36 | --------------------------------------------------------------------------------