Skip to content

Instantly share code, notes, and snippets.

@rcarmo
Last active December 16, 2015 06:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rcarmo/5394708 to your computer and use it in GitHub Desktop.
Save rcarmo/5394708 to your computer and use it in GitHub Desktop.
Go benchmark
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Main application script
Created by: Rui Carmo
License: MIT (see LICENSE for details)
"""
import os, sys
sys.path.append('lib')
import bottle, routes
application = bottle.app()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Model shims
Created by: Rui Carmo
"""
import os, sys, random, time, datetime
FIRST_NAMES = ["Emily", "Jacob", "Michael", "Emma", "Joshua", "Madison", "Matthew", "Hannah", "Andrew", "Olivia", "Cletus", "Warner", "Sarah", "Billy", "Brittany", "Daniel", "David", "Cristman", "Colin", "Royalle"]
LAST_NAMES = ["Aaron", "Bolingbroke", "Crounse", "Duff", "Drake", "Downs", "Driver", "Jasper", "Jetter", "O'Leary", "O'Malley", "Neville", "Towers", "Tripp", "Trull", "Wakefield", "Waller", "Badger", "Bagley", "Baker"]
STATES = ["Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"]
db = {}
_date_range = [
time.mktime(datetime.datetime(1971,1,1).timetuple()),
time.mktime(datetime.datetime(2000,1,1).timetuple())
]
def random_date():
start = _date_range[0]
end = _date_range[1]
return random.randrange(start, end, 38400)
class Customers:
customers = []
def __init__(self):
for i in range(0,200):
self.customers.append({
'first_name': random.choice(FIRST_NAMES),
'last_name': random.choice(LAST_NAMES),
'state': random.choice(STATES),
'birth_date': random_date(),
'id': i
})
def find_all(self):
return self.customers
def find_by_id(self, i):
return self.customers[i]
def save(self, customer):
self.customers.append(customer)
def delete(self, id):
del self.customers[id]
<!DOCTYPE html>
<html xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Customer List</title>
<style>
body {
color: #6F6F6F;
font-family: 'Lucida Sans', 'Helvetica',
'Sans-serif', 'sans';
font-size: 9pt;
line-height: 1.8em;
padding: 10px;
margin: 10px;
}
h1 {
color: #E9601A;
font-size: 14pt;
}
table.decorated {
width: 100%;
border-collapse: collapse;
}
table.decorated td {
padding-right: 5px;
padding-left: 5px;
}
table.decorated thead td {
font-weight: bold;
color: #FFF;
background-color: #CCC;
}
table.decorated tbody td {
border: 1px solid #CCC;
}
</style>
</head>
<body>
<h1>Customer List</h1>
<table class="decorated">
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>State</td>
<td>Birth Date</td>
<td>Options</td>
</tr>
</thead>
<tbody>
{{ range . }}
<tr>
<td>{{ .FirstName }}</td>
<td>{{ .LastName }}</td>
<td>{{ .State }}</td>
<td>{{ .BirthDate }}</td>
<td><a href="/delete/{{ .Id }}">Delete</a></td>
</tr>
{{ end }}
</tbody>
</table>
</body>
</html>
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Static routes
Created by: Rui Carmo
License: MIT (see LICENSE for details)
"""
import os, sys, logging
from bottle import route, view, response, redirect
from models import Customers
log = logging.getLogger()
c = Customers()
@route('/customerList')
@view('index')
def index():
global c
return {'customers': c.find_all()}
@route('/delete/<id:int>')
def delete(id):
"""Static file handler"""
global c
c.delete(id)
redirect('/')
package main
import (
"time"
"math/rand"
"net/http"
"html/template"
"runtime"
)
var FIRST_NAMES = []string{"Emily", "Jacob", "Michael", "Emma", "Joshua", "Madison", "Matthew", "Hannah", "Andrew", "Olivia", "Cletus", "Warner", "Sarah", "Billy", "Brittany", "Daniel", "David", "Cristman", "Colin", "Royalle"}
var LAST_NAMES = []string{"Aaron", "Bolingbroke", "Crounse", "Duff", "Drake", "Downs", "Driver", "Jasper", "Jetter", "O'Leary", "O'Malley", "Neville", "Towers", "Tripp", "Trull", "Wakefield", "Waller", "Badger", "Bagley", "Baker"}
var STATES = []string{"Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"}
var page_html = template.Must(template.ParseFiles("page.html"))
type Customer struct {
Id int
FirstName string
LastName string
State string
BirthDate time.Time
}
var count = 1
var customers = make([]Customer,200)
func random_first_name() string {
return FIRST_NAMES[rand.Intn(len(FIRST_NAMES))]
}
func random_last_name() string {
return LAST_NAMES[rand.Intn(len(LAST_NAMES))]
}
func random_state() string {
return STATES[rand.Intn(len(STATES))]
}
func random_int(min int, max int) int {
return min + rand.Intn(max-min)
}
func random_date() time.Time {
var now int64 = time.Now().Unix()
return time.Unix(int64(rand.Intn(int(now))),0)
}
func new_customer(n int) Customer {
return Customer {
Id : n,
FirstName : random_first_name(),
LastName : random_last_name(),
State : random_state(),
BirthDate : random_date(),
}
}
func handler(w http.ResponseWriter, r *http.Request) {
if err := page_html.Execute(w, customers); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
runtime.GOMAXPROCS(8)
rand.Seed(time.Now().UTC().UnixNano())
for i := 0; i < len(customers); i++ {
customers[i] = new_customer(i)
}
http.HandleFunc("/customerList", handler)
http.ListenAndServe(":8082", nil)
}
[uwsgi]
chdir = .
pythonpath = /Library/Python/2.7/site-packages/greenlet-0.4.0-py2.7-macosx-10.8-intel.egg:/Library/Python/2.7/site-packages
wsgi = benchmark:application
procname-prefix = bottle-uwsgi-
main_plugin = python,gevent
http-socket = 0.0.0.0:8082
loop = gevent
async = 50
enable-threads = true
listen = 1024
harakiri = 10
optimize = 2
master = True
processes = 8
disable-logging = True
#logto = /dev/null
no-default-app = False
auto-procname = True
limit-as = 90
buffer-size = 2048
socket-timeout = 30
post-buffering = 1024
limit-post = 1024
thread-stacksize = 64
%import time
<!DOCTYPE html>
<html xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Customer List</title>
<style>
body {
color: #6F6F6F;
font-family: 'Lucida Sans', 'Helvetica',
'Sans-serif', 'sans';
font-size: 9pt;
line-height: 1.8em;
padding: 10px;
margin: 10px;
}
h1 {
color: #E9601A;
font-size: 14pt;
}
table.decorated {
width: 100%;
border-collapse: collapse;
}
table.decorated td {
padding-right: 5px;
padding-left: 5px;
}
table.decorated thead td {
font-weight: bold;
color: #FFF;
background-color: #CCC;
}
table.decorated tbody td {
border: 1px solid #CCC;
}
</style>
</head>
<body>
<h1>Customer List</h1>
<table class="decorated">
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>State</td>
<td>Birth Date</td>
<td>Options</td>
</tr>
</thead>
<tbody>
%for c in customers:
<tr>
%for k in ['first_name','last_name','state']:
<td>{{c[k]}}</td>
%end
<td>{{time.strftime('%d-%m-%Y',time.gmtime(c['birth_date']))}}</td>
<td><a href="/delete/{{c['id']}}">Delete</a></td>
</tr>
%end
</tbody>
</table>
</body>
</html>
@rcarmo
Copy link
Author

rcarmo commented May 14, 2013

Running this with GOMAXPROCS=8 in a dual-core box using wrk doesn't go faster than, say, 120 requests/s:

$ wrk -t 10 -r 1000 http://localhost:8082/customerList
Making 1000 requests to http://localhost:8082/customerList
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    89.60ms   71.94ms 440.15ms   77.90%
    Req/Sec     0.00      0.00     0.00    100.00%
  1000 requests in 8.54s, 55.10MB read
Requests/sec:    117.15
Transfer/sec:      6.45MB

An equivalent bottle/gevent server performs like this on the same hardware:

$ wrk -t 10 -r 1000 http://localhost:8082/customerList
Making 1000 requests to http://localhost:8082/customerList
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    24.30ms   69.21ms 356.11ms   94.44%
    Req/Sec     0.00      0.00     0.00    100.00%
  1000 requests in 1.23s, 50.34MB read
Requests/sec:    815.78
Transfer/sec:     41.07MB

...and actually does a fair bit better with more requests:

$ wrk -t 10 -r 10000 http://localhost:8082/customerList
Making 10000 requests to http://localhost:8082/customerList
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    14.31ms   45.01ms 323.91ms   92.84%
    Req/Sec     0.00      0.00     0.00    100.00%
  10000 requests in 6.27s, 503.48MB read
Requests/sec:   1594.36
Transfer/sec:     80.27MB

...and yet go (even 1.1) doesn't go above 120 R/s

@tmc
Copy link

tmc commented May 15, 2013

Can you publish the bottle code?

@AeroNotix
Copy link

Python code, please.

@rcarmo
Copy link
Author

rcarmo commented May 15, 2013

The full Python code is fairly extensive. I can publish the model, route, etc., but it's a full MVC framework tree...

Done. At least the interesting bits, the ordering is a bit muddled, though. This is an MVC Bottle app that uses uwsgi and gevent (I've included the uwsgi.ini file).

Anyway, I'd love to understand why Go fares so poorly. It's the simplest thing possible, nearly a straight port of the Python code.

Some background:

We've been doing exactly the same pattern (HTML template filled in with precomputed random data) in a number of languages (Java, using jetty, netty, etc.), ASP.NET, Python (bottle gevent, tornado), and soon Ruby, LuaJIT, etc., and Go seemed like the perfect candidate to best them all.

I tossed in Python and Go into the crucible and was a bit amazed that on an 8-core box Go had such poor performance - we started with 1.0.3, and I re-did the benchmark on my laptop with Go 1.1 (those are the figures posted above). I've also been looking into PyPy and Erlang and would like to eventually publish the whole thing somewhere, but not with such a disparity.

@tmc
Copy link

tmc commented May 17, 2013

On my MBP Retina (8 core i7) I get these numbers:
running python with :

bottle.run(port=8082, server='gevent', quiet=True, log=None)

python:

wrk -t 10 -r 1000 http://localhost:8082/customerList
Making 1000 requests to http://localhost:8082/customerList
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    63.33ms   22.68ms 117.57ms   78.55%
    Req/Sec     0.00      0.00     0.00    100.00%
  1000 requests in 6.00s, 50.30MB read
Requests/sec:    166.59
Transfer/sec:      8.38MB

go1.0.3:

wrk -t 10 -r 1000 http://localhost:8082/customerList
Making 1000 requests to http://localhost:8082/customerList
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    36.20ms    1.96ms  45.69ms   82.63%
    Req/Sec     0.00      0.00     0.00    100.00%
  1000 requests in 3.63s, 65.90MB read
Requests/sec:    275.17
Transfer/sec:     18.13MB

go1.1:

wrk -t 10 -r 1000 http://localhost:8082/customerList
Making 1000 requests to http://localhost:8082/customerList
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    17.11ms    1.86ms  26.91ms   82.20%
    Req/Sec     0.00      0.00     0.00    100.00%
  1000 requests in 1.73s, 54.22MB read
Requests/sec:    579.20
Transfer/sec:     31.41MB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment