Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go: Updated to 1.5.2 & Added fasthttp benchmark #1765

Merged
merged 20 commits into from
Dec 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c00129b
Added initial fasthttp benchmark
valyala Nov 17, 2015
8ede1fd
Optimize /json and /fortune by removing unnesessary bytes copying
valyala Nov 18, 2015
53ce2ea
fasthttp: use prepared statements
valyala Nov 18, 2015
c5ff600
fasthttp: fixed GOPATH in setup.bat
valyala Nov 18, 2015
5e1fb42
Merge branch 'master' of https://github.com/TechEmpower/FrameworkBenc…
valyala Nov 19, 2015
bbb8a80
fasthttp: use SO_REUSEPORT if -prefork flag is passed. This should im…
valyala Nov 20, 2015
b85f19f
Explicitly enumerate third-party packages used by fasthttp benchmark
valyala Nov 20, 2015
21728a5
fasthttp: properly set benchmark_config.json params
valyala Nov 20, 2015
8da9a61
fasthttp: updated README.md
valyala Nov 20, 2015
ae15114
fasthttp: code cleanup
valyala Nov 20, 2015
3d07ea6
fasthttp: spread available db connections between worker processes in…
valyala Nov 20, 2015
8d137b4
Update world rows in a transaction
valyala Nov 20, 2015
e39aa67
fasthttp: code prettifying
valyala Nov 20, 2015
2c1a83a
fasthttp: fixed 'Deadlock found when trying to get lock; try restarti…
valyala Nov 22, 2015
16106d3
Merge remote-tracking branch 'origin/master'
valyala Nov 30, 2015
27bbc81
fasthttp: updated stale error message
valyala Nov 30, 2015
798c6b6
Updated Go version from 1.4.2 to 1.5.1
valyala Dec 2, 2015
145f2e4
Updated go version to 1.5.2
valyala Dec 8, 2015
bf1a845
Merge remote-tracking branch 'origin/master'
valyala Dec 10, 2015
9553876
Merge remote-tracking branch 'origin/master'
valyala Dec 11, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ env:
- "TESTDIR=Erlang/elli"
- "TESTDIR=Go/beego"
- "TESTDIR=Go/falcore"
- "TESTDIR=Go/fasthttp"
- "TESTDIR=Go/gin"
- "TESTDIR=Go/go-raw"
- "TESTDIR=Go/goji"
Expand Down
14 changes: 14 additions & 0 deletions frameworks/Go/fasthttp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# [fasthttp](https://github.com/valyala/fasthttp) (GoLang) Benchmarking Test

This is the go portion of a [benchmarking test suite](https://www.techempower.com/benchmarks/) comparing a variety of web development platforms.

"Fasthttp is a fast http package for Go."

## Test URLs

http://localhost:8080/json
http://localhost:8080/db
http://localhost:8080/queries?queries=[1-500]
http://localhost:8080/fortunes
http://localhost:8080/updates?queries=[1-500]
http://localhost:8080/plaintext
51 changes: 51 additions & 0 deletions frameworks/Go/fasthttp/benchmark_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"framework": "fasthttp",
"tests": [{
"default": {
"setup_file": "setup",
"json_url": "/json",
"db_url": "/db",
"query_url": "/queries?queries=",
"fortune_url": "/fortune",
"update_url": "/update?queries=",
"plaintext_url": "/plaintext",
"port": 8080,
"approach": "Realistic",
"classification": "Platform",
"database": "MySQL",
"framework": "fasthttp",
"language": "Go",
"orm": "Raw",
"platform": "Go",
"webserver": "None",
"os": "Linux",
"database_os": "Linux",
"display_name": "fasthttp",
"notes": "",
"versus": "go"
},
"prefork": {
"setup_file": "setup_prefork",
"json_url": "/json",
"db_url": "/db",
"query_url": "/queries?queries=",
"fortune_url": "/fortune",
"update_url": "/update?queries=",
"plaintext_url": "/plaintext",
"port": 8080,
"approach": "Realistic",
"classification": "Platform",
"database": "MySQL",
"framework": "fasthttp",
"language": "Go",
"orm": "Raw",
"platform": "Go",
"webserver": "None",
"os": "Linux",
"database_os": "Linux",
"display_name": "fasthttp-prefork",
"notes": "",
"versus": "go"
}
}]
}
2 changes: 2 additions & 0 deletions frameworks/Go/fasthttp/setup.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
set GOPATH=C:\FrameworkBenchmarks\Go\fasthttp
go run src\hello\hello.go
10 changes: 10 additions & 0 deletions frameworks/Go/fasthttp/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/hello/hello.go

fw_depends go

go get -u github.com/go-sql-driver/mysql
go get -u github.com/valyala/fasthttp

go run src/hello/hello.go &
10 changes: 10 additions & 0 deletions frameworks/Go/fasthttp/setup_prefork.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/hello/hello.go

fw_depends go

go get -u github.com/go-sql-driver/mysql
go get -u github.com/valyala/fasthttp

go run src/hello/hello.go -prefork &
6 changes: 6 additions & 0 deletions frameworks/Go/fasthttp/source_code
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
./go/src/
./go/src/hello
./go/src/hello/hello.go
./go/templates/
./go/templates/fortune.html
./go/templates/layout.html
284 changes: 284 additions & 0 deletions frameworks/Go/fasthttp/src/hello/hello.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package main

import (
"database/sql"
"encoding/json"
"flag"
"html/template"
"log"
"math/rand"
"net"
"os"
"os/exec"
"runtime"
"sort"

_ "github.com/go-sql-driver/mysql"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/reuseport"
)

type Message struct {
Message string `json:"message"`
}

type World struct {
Id uint16 `json:"id"`
RandomNumber uint16 `json:"randomNumber"`
}

type Fortune struct {
Id uint16 `json:"id"`
Message string `json:"message"`
}

const (
connectionString = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world"
worldRowCount = 10000
maxConnectionCount = 256
)

var (
worldSelectStmt *sql.Stmt
worldUpdateStmt *sql.Stmt
fortuneSelectStmt *sql.Stmt
)

const helloWorldString = "Hello, World!"

var (
tmpl = template.Must(template.ParseFiles("templates/layout.html", "templates/fortune.html"))

db *sql.DB

helloWorldBytes = []byte(helloWorldString)
)

var (
listenAddr = flag.String("listenAddr", ":8080", "Address to listen to")
prefork = flag.Bool("prefork", false, "use prefork")
child = flag.Bool("child", false, "is child proc")
)

func main() {
flag.Parse()

var err error
if db, err = sql.Open("mysql", connectionString); err != nil {
log.Fatalf("Error opening database: %s", err)
}
if err = db.Ping(); err != nil {
log.Fatalf("Cannot connect to db: %s", err)
}

dbConnCount := maxConnectionCount
if *prefork {
dbConnCount = (dbConnCount + runtime.NumCPU() - 1) / runtime.NumCPU()
}
db.SetMaxIdleConns(dbConnCount)
db.SetMaxOpenConns(dbConnCount * 2)

worldSelectStmt = mustPrepare(db, "SELECT id, randomNumber FROM World WHERE id = ?")
worldUpdateStmt = mustPrepare(db, "UPDATE World SET randomNumber = ? WHERE id = ?")
fortuneSelectStmt = mustPrepare(db, "SELECT id, message FROM Fortune")

s := &fasthttp.Server{
Handler: mainHandler,
Name: "fasthttp",
}
ln := getListener()
if err = s.Serve(ln); err != nil {
log.Fatalf("Error when serving incoming connections: %s", err)
}
}

func mainHandler(ctx *fasthttp.RequestCtx) {
path := ctx.Path()
switch {
case fasthttp.EqualBytesStr(path, "/plaintext"):
plaintextHandler(ctx)
case fasthttp.EqualBytesStr(path, "/json"):
jsonHandler(ctx)
case fasthttp.EqualBytesStr(path, "/db"):
dbHandler(ctx)
case fasthttp.EqualBytesStr(path, "/queries"):
queriesHandler(ctx)
case fasthttp.EqualBytesStr(path, "/fortune"):
fortuneHandler(ctx)
case fasthttp.EqualBytesStr(path, "/update"):
updateHandler(ctx)
default:
ctx.Error("unexpected path", fasthttp.StatusBadRequest)
}
}

// Test 1: JSON serialization
func jsonHandler(ctx *fasthttp.RequestCtx) {
jsonMarshal(ctx, &Message{helloWorldString})
}

// Test 2: Single database query
func dbHandler(ctx *fasthttp.RequestCtx) {
var w World
fetchRandomWorld(&w)
jsonMarshal(ctx, &w)
}

// Test 3: Multiple database queries
func queriesHandler(ctx *fasthttp.RequestCtx) {
n := getQueriesCount(ctx)

worlds := make([]World, n)
for i := 0; i < n; i++ {
fetchRandomWorld(&worlds[i])
}

jsonMarshal(ctx, worlds)
}

// Test 4: Fortunes
func fortuneHandler(ctx *fasthttp.RequestCtx) {
rows, err := fortuneSelectStmt.Query()
if err != nil {
log.Fatalf("Error selecting db data: %v", err)
}

fortunes := make([]Fortune, 0, 16)
for rows.Next() {
var f Fortune
if err := rows.Scan(&f.Id, &f.Message); err != nil {
log.Fatalf("Error scanning fortune row: %s", err)
}
fortunes = append(fortunes, f)
}
rows.Close()
fortunes = append(fortunes, Fortune{Message: "Additional fortune added at request time."})

sort.Sort(FortunesByMessage(fortunes))

ctx.SetContentType("text/html")
if err := tmpl.Execute(ctx, fortunes); err != nil {
log.Fatalf("Error executing fortune: %s", err)
}
}

// Test 5: Database updates
func updateHandler(ctx *fasthttp.RequestCtx) {
n := getQueriesCount(ctx)

worlds := make([]World, n)
for i := 0; i < n; i++ {
w := &worlds[i]
fetchRandomWorld(w)
w.RandomNumber = uint16(randomWorldNum())
}

// sorting is required for insert deadlock prevention.
sort.Sort(WorldsByID(worlds))
txn, err := db.Begin()
if err != nil {
log.Fatalf("Error starting transaction: %s", err)
}
stmt := txn.Stmt(worldUpdateStmt)
for i := 0; i < n; i++ {
w := &worlds[i]
if _, err := stmt.Exec(w.RandomNumber, w.Id); err != nil {
log.Fatalf("Error updating world row %d: %s", i, err)
}
}
if err = txn.Commit(); err != nil {
log.Fatalf("Error when commiting world rows: %s", err)
}

jsonMarshal(ctx, worlds)
}

// Test 6: Plaintext
func plaintextHandler(ctx *fasthttp.RequestCtx) {
ctx.Success("text/plain", helloWorldBytes)
}

func jsonMarshal(ctx *fasthttp.RequestCtx, v interface{}) {
ctx.SetContentType("application/json")
if err := json.NewEncoder(ctx).Encode(v); err != nil {
log.Fatalf("error in json.Encoder.Encode: %s", err)
}
}

func fetchRandomWorld(w *World) {
n := randomWorldNum()
if err := worldSelectStmt.QueryRow(n).Scan(&w.Id, &w.RandomNumber); err != nil {
log.Fatalf("Error scanning world row: %s", err)
}
}

func randomWorldNum() int {
return rand.Intn(worldRowCount) + 1
}

func getQueriesCount(ctx *fasthttp.RequestCtx) int {
n := ctx.QueryArgs().GetUintOrZero("queries")
if n < 1 {
n = 1
} else if n > 500 {
n = 500
}
return n
}

type FortunesByMessage []Fortune

func (s FortunesByMessage) Len() int { return len(s) }
func (s FortunesByMessage) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s FortunesByMessage) Less(i, j int) bool { return s[i].Message < s[j].Message }

type WorldsByID []World

func (w WorldsByID) Len() int { return len(w) }
func (w WorldsByID) Swap(i, j int) { w[i], w[j] = w[j], w[i] }
func (w WorldsByID) Less(i, j int) bool { return w[i].Id < w[j].Id }

func mustPrepare(db *sql.DB, query string) *sql.Stmt {
stmt, err := db.Prepare(query)
if err != nil {
log.Fatalf("Error when preparing statement %q: %s", query, err)
}
return stmt
}

func getListener() net.Listener {
if !*prefork {
runtime.GOMAXPROCS(runtime.NumCPU())
ln, err := net.Listen("tcp4", *listenAddr)
if err != nil {
log.Fatal(err)
}
return ln
}

if !*child {
children := make([]*exec.Cmd, runtime.NumCPU())
for i := range children {
children[i] = exec.Command(os.Args[0], "-prefork", "-child")
children[i].Stdout = os.Stdout
children[i].Stderr = os.Stderr
if err := children[i].Start(); err != nil {
log.Fatal(err)
}
}
for _, ch := range children {
if err := ch.Wait(); err != nil {
log.Print(err)
}
}
os.Exit(0)
panic("unreachable")
}

runtime.GOMAXPROCS(1)
ln, err := reuseport.Listen("tcp4", *listenAddr)
if err != nil {
log.Fatal(err)
}
return ln
}
Loading