diff --git a/Gopkg.lock b/Gopkg.lock index ec4fd9de..6eb3854d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -295,11 +295,11 @@ [[projects]] branch = "master" - digest = "1:630381558bc538e831db8468dd0dc2702d81789f79b8ddf665eeebc729e2a055" + digest = "1:e84d0aa788bd55e938ebbaa62782385ca4da00b63c1d6bf23270c031a2ae9a88" name = "github.com/tidwall/redbench" packages = ["."] pruneopts = "" - revision = "637a608ebec1acbf049c2e4a5eda6c2d72aa3af1" + revision = "17c5b5b864a4b072481036ac689913156f5bb81c" [[projects]] branch = "master" diff --git a/cmd/tile38-benchmark/main.go b/cmd/tile38-benchmark/main.go index 772568cc..86550b41 100644 --- a/cmd/tile38-benchmark/main.go +++ b/cmd/tile38-benchmark/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "math" "math/rand" "net" "os" @@ -23,7 +24,7 @@ var ( pipeline = 1 csv = false json = false - tests = "PING,SET,GET,SEARCH,EVAL" + tests = "PING,SET,GET,INTERSECTS,WITHIN,NEARBY,EVAL" redis = false ) @@ -146,15 +147,23 @@ func randPoint() (lat, lon float64) { return rand.Float64()*180 - 90, rand.Float64()*360 - 180 } -func randRect() (minlat, minlon, maxlat, maxlon float64) { +func isValidRect(minlat, minlon, maxlat, maxlon float64) bool { + return minlat > -90 && maxlat < 90 && minlon > -180 && maxlon < 180 +} + +func randRect(meters float64) (minlat, minlon, maxlat, maxlon float64) { for { - minlat, minlon = randPoint() - maxlat, maxlon = minlat+1, minlon+1 - if maxlat <= 180 && maxlon <= 180 { + lat, lon := randPoint() + maxlat, _ = destinationPoint(lat, lon, meters, 0) + _, maxlon = destinationPoint(lat, lon, meters, 90) + minlat, _ = destinationPoint(lat, lon, meters, 180) + _, minlon = destinationPoint(lat, lon, meters, 270) + if isValidRect(minlat, minlon, maxlat, maxlon) { return } } } + func prepFn(conn net.Conn) bool { if json { conn.Write([]byte("output json\r\n")) @@ -222,7 +231,7 @@ func main() { redbench.Bench("SET (rect)", addr, opts, prepFn, func(buf []byte) []byte { i := atomic.AddInt64(&i, 1) - minlat, minlon, maxlat, maxlon := randRect() + minlat, minlon, maxlat, maxlon := randRect(10000) return redbench.AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "BOUNDS", strconv.FormatFloat(minlat, 'f', 5, 64), strconv.FormatFloat(minlon, 'f', 5, 64), @@ -270,36 +279,270 @@ func main() { }, ) } - case "SEARCH": - if !redis { - redbench.Bench("SEARCH (nearby 1km)", addr, opts, prepFn, + case "INTERSECTS", + "INTERSECTS-RECT", "INTERSECTS-RECT-1000", "INTERSECTS-RECT-10000", "INTERSECTS-RECT-100000", + "INTERSECTS-CIRCLE", "INTERSECTS-CIRCLE-1000", "INTERSECTS-CIRCLE-10000", "INTERSECTS-CIRCLE-100000": + if redis { + break + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "INTERSECTS", "INTERSECTS-CIRCLE", "INTERSECTS-CIRCLE-1000": + redbench.Bench("INTERSECTS (intersects-circle 1km)", addr, opts, prepFn, func(buf []byte) []byte { lat, lon := randPoint() - return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT", + return redbench.AppendCommand(buf, + "INTERSECTS", "key:bench", "COUNT", "CIRCLE", strconv.FormatFloat(lat, 'f', 5, 64), strconv.FormatFloat(lon, 'f', 5, 64), "1000") }, ) - redbench.Bench("SEARCH (nearby 10km)", addr, opts, prepFn, + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "INTERSECTS", "INTERSECTS-CIRCLE", "INTERSECTS-CIRCLE-10000": + redbench.Bench("INTERSECTS (intersects-circle 10km)", addr, opts, prepFn, func(buf []byte) []byte { lat, lon := randPoint() - return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT", + return redbench.AppendCommand(buf, + "INTERSECTS", "key:bench", "COUNT", "CIRCLE", strconv.FormatFloat(lat, 'f', 5, 64), strconv.FormatFloat(lon, 'f', 5, 64), "10000") }, ) - redbench.Bench("SEARCH (nearby 100km)", addr, opts, prepFn, + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "INTERSECTS", "INTERSECTS-CIRCLE", "INTERSECTS-CIRCLE-100000": + redbench.Bench("INTERSECTS (intersects-circle 100km)", addr, opts, prepFn, func(buf []byte) []byte { lat, lon := randPoint() - return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT", + return redbench.AppendCommand(buf, + "INTERSECTS", "key:bench", "COUNT", "CIRCLE", strconv.FormatFloat(lat, 'f', 5, 64), strconv.FormatFloat(lon, 'f', 5, 64), "100000") }, ) } + // INTERSECTS-BOUNDS + switch strings.ToUpper(strings.TrimSpace(test)) { + case "INTERSECTS", "INTERSECTS-BOUNDS", "INTERSECTS-BOUNDS-1000": + minlat, minlon, maxlat, maxlon := randRect(1000) + redbench.Bench("INTERSECTS (intersects-bounds 1km)", addr, opts, prepFn, + func(buf []byte) []byte { + return redbench.AppendCommand(buf, + "INTERSECTS", "key:bench", "COUNT", "BOUNDS", + strconv.FormatFloat(minlat, 'f', 5, 64), + strconv.FormatFloat(minlon, 'f', 5, 64), + strconv.FormatFloat(maxlat, 'f', 5, 64), + strconv.FormatFloat(maxlon, 'f', 5, 64)) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "INTERSECTS", "INTERSECTS-BOUNDS", "INTERSECTS-BOUNDS-10000": + minlat, minlon, maxlat, maxlon := randRect(10000) + redbench.Bench("INTERSECTS (intersects-bounds 10km)", addr, opts, prepFn, + func(buf []byte) []byte { + return redbench.AppendCommand(buf, + "INTERSECTS", "key:bench", "COUNT", "BOUNDS", + strconv.FormatFloat(minlat, 'f', 5, 64), + strconv.FormatFloat(minlon, 'f', 5, 64), + strconv.FormatFloat(maxlat, 'f', 5, 64), + strconv.FormatFloat(maxlon, 'f', 5, 64)) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "INTERSECTS", "INTERSECTS-BOUNDS", "INTERSECTS-BOUNDS-100000": + minlat, minlon, maxlat, maxlon := randRect(10000) + redbench.Bench("INTERSECTS (intersects-bounds 100km)", addr, opts, prepFn, + func(buf []byte) []byte { + return redbench.AppendCommand(buf, + "INTERSECTS", "key:bench", "COUNT", "BOUNDS", + strconv.FormatFloat(minlat, 'f', 5, 64), + strconv.FormatFloat(minlon, 'f', 5, 64), + strconv.FormatFloat(maxlat, 'f', 5, 64), + strconv.FormatFloat(maxlon, 'f', 5, 64)) + }, + ) + } + + case "WITHIN", + "WITHIN-RECT", "WITHIN-RECT-1000", "WITHIN-RECT-10000", "WITHIN-RECT-100000", + "WITHIN-CIRCLE", "WITHIN-CIRCLE-1000", "WITHIN-CIRCLE-10000", "WITHIN-CIRCLE-100000": + if redis { + break + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "WITHIN", "WITHIN-CIRCLE", "WITHIN-CIRCLE-1000": + redbench.Bench("WITHIN (within-circle 1km)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "WITHIN", "key:bench", "COUNT", "CIRCLE", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + "1000") + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "WITHIN", "WITHIN-CIRCLE", "WITHIN-CIRCLE-10000": + redbench.Bench("WITHIN (within-circle 10km)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "WITHIN", "key:bench", "COUNT", "CIRCLE", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + "10000") + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "WITHIN", "WITHIN-CIRCLE", "WITHIN-CIRCLE-100000": + redbench.Bench("WITHIN (within-circle 100km)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "WITHIN", "key:bench", "COUNT", "CIRCLE", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + "100000") + }, + ) + } + // WITHIN-BOUNDS + switch strings.ToUpper(strings.TrimSpace(test)) { + case "WITHIN", "WITHIN-BOUNDS", "WITHIN-BOUNDS-1000": + minlat, minlon, maxlat, maxlon := randRect(1000) + redbench.Bench("WITHIN (within-bounds 1km)", addr, opts, prepFn, + func(buf []byte) []byte { + return redbench.AppendCommand(buf, + "WITHIN", "key:bench", "COUNT", "BOUNDS", + strconv.FormatFloat(minlat, 'f', 5, 64), + strconv.FormatFloat(minlon, 'f', 5, 64), + strconv.FormatFloat(maxlat, 'f', 5, 64), + strconv.FormatFloat(maxlon, 'f', 5, 64)) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "WITHIN", "WITHIN-BOUNDS", "WITHIN-BOUNDS-10000": + minlat, minlon, maxlat, maxlon := randRect(10000) + redbench.Bench("WITHIN (within-bounds 10km)", addr, opts, prepFn, + func(buf []byte) []byte { + return redbench.AppendCommand(buf, + "WITHIN", "key:bench", "COUNT", "BOUNDS", + strconv.FormatFloat(minlat, 'f', 5, 64), + strconv.FormatFloat(minlon, 'f', 5, 64), + strconv.FormatFloat(maxlat, 'f', 5, 64), + strconv.FormatFloat(maxlon, 'f', 5, 64)) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "WITHIN", "WITHIN-BOUNDS", "WITHIN-BOUNDS-100000": + minlat, minlon, maxlat, maxlon := randRect(10000) + redbench.Bench("WITHIN (within-bounds 100km)", addr, opts, prepFn, + func(buf []byte) []byte { + return redbench.AppendCommand(buf, + "WITHIN", "key:bench", "COUNT", "BOUNDS", + strconv.FormatFloat(minlat, 'f', 5, 64), + strconv.FormatFloat(minlon, 'f', 5, 64), + strconv.FormatFloat(maxlat, 'f', 5, 64), + strconv.FormatFloat(maxlon, 'f', 5, 64)) + }, + ) + } + case "NEARBY", + "NEARBY-KNN", "NEARBY-KNN-1", "NEARBY-KNN-10", "NEARBY-KNN-100", + "NEARBY-POINT", "NEARBY-POINT-1000", "NEARBY-POINT-10000", "NEARBY-POINT-100000": + if redis { + break + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "NEARBY", "NEARBY-KNN", "NEARBY-KNN-1": + redbench.Bench("NEARBY (limit 1)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "NEARBY", "key:bench", "LIMIT", "1", "COUNT", "POINT", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + ) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "NEARBY", "NEARBY-KNN", "NEARBY-KNN-10": + redbench.Bench("NEARBY (limit 10)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "NEARBY", "key:bench", "LIMIT", "10", "COUNT", "POINT", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + ) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "NEARBY", "NEARBY-KNN", "NEARBY-KNN-100": + redbench.Bench("NEARBY (limit 100)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "NEARBY", "key:bench", "LIMIT", "100", "COUNT", "POINT", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + ) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "NEARBY", "NEARBY-POINT", "NEARBY-POINT-1000": + redbench.Bench("NEARBY (point 1km)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "NEARBY", "key:bench", "COUNT", "POINT", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + "1000", + ) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "NEARBY", "NEARBY-POINT", "NEARBY-POINT-10000": + redbench.Bench("NEARBY (point 10km)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "NEARBY", "key:bench", "COUNT", "POINT", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + "10000", + ) + }, + ) + } + switch strings.ToUpper(strings.TrimSpace(test)) { + case "NEARBY", "NEARBY-POINT", "NEARBY-POINT-100000": + redbench.Bench("NEARBY (point 100km)", addr, opts, prepFn, + func(buf []byte) []byte { + lat, lon := randPoint() + return redbench.AppendCommand(buf, + "NEARBY", "key:bench", "COUNT", "POINT", + strconv.FormatFloat(lat, 'f', 5, 64), + strconv.FormatFloat(lon, 'f', 5, 64), + "100000", + ) + }, + ) + } case "EVAL": if !redis { var i int64 @@ -371,3 +614,21 @@ func main() { } } } + +const earthRadius = 6371e3 + +func toRadians(deg float64) float64 { return deg * math.Pi / 180 } +func toDegrees(rad float64) float64 { return rad * 180 / math.Pi } + +// destinationPoint return the destination from a point based on a distance and bearing. +func destinationPoint(lat, lon, meters, bearingDegrees float64) (destLat, destLon float64) { + // see http://williams.best.vwh.net/avform.htm#LL + δ := meters / earthRadius // angular distance in radians + θ := toRadians(bearingDegrees) + φ1 := toRadians(lat) + λ1 := toRadians(lon) + φ2 := math.Asin(math.Sin(φ1)*math.Cos(δ) + math.Cos(φ1)*math.Sin(δ)*math.Cos(θ)) + λ2 := λ1 + math.Atan2(math.Sin(θ)*math.Sin(δ)*math.Cos(φ1), math.Cos(δ)-math.Sin(φ1)*math.Sin(φ2)) + λ2 = math.Mod(λ2+3*math.Pi, 2*math.Pi) - math.Pi // normalise to -180..+180° + return toDegrees(φ2), toDegrees(λ2) +} diff --git a/vendor/github.com/tidwall/redbench/bench.go b/vendor/github.com/tidwall/redbench/bench.go index 9f7b4e9e..464f5cad 100644 --- a/vendor/github.com/tidwall/redbench/bench.go +++ b/vendor/github.com/tidwall/redbench/bench.go @@ -14,7 +14,7 @@ import ( "time" ) -func readResp(rd *bufio.Reader, n int) error { +func readResp(rd *bufio.Reader, n int, opts *Options) error { for i := 0; i < n; i++ { line, err := rd.ReadBytes('\n') if err != nil { @@ -25,6 +25,7 @@ func readResp(rd *bufio.Reader, n int) error { return errors.New("invalid server response") case '+', ':': case '-': + opts.Stderr.Write(line) case '$': n, err := strconv.ParseInt(string(line[1:len(line)-2]), 10, 64) if err != nil { @@ -40,7 +41,7 @@ func readResp(rd *bufio.Reader, n int) error { if err != nil { return err } - readResp(rd, int(n)) + readResp(rd, int(n), opts) } } return nil @@ -155,7 +156,7 @@ func Bench( if err != nil { return err } - if err := readResp(rd, n); err != nil { + if err := readResp(rd, n, opts); err != nil { return err } stop := time.Since(start)