Golang Barcelona - Introductory talk about pprof, the official golang profiling tool
*.svg | |
*.out | |
*.test | |
pbench | |
server |
WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 | |
(http://www.wtfpl.net/about/) | |
Copyright (C) 2015 Ivan Fraixedes <ivan@fraixed.es> (https://ivan.fraixed.es) | |
Everyone is permitted to copy and distribute verbatim or modified | |
copies of this license document, and changing it is allowed as long | |
as the name is changed. | |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
0. You just DO WHAT THE FUCK YOU WANT TO. |
.PHONY: test bench bench-profile profile-cpu-graph profile-mem-graph profile-server-cpu profile-server-mem build-server run-server run-vegeta-fib run-vegeta-fibb deps | |
test: | |
@go test *.go | |
bench: | |
@go test -bench . -benchmem *.go | |
bench-profile: | |
@go test -o pbench -bench . -benchmem -benchtime 3s -outputdir pprof-out -memprofile mem.out -cpuprofile cpu.out | |
profile-cpu-graph: | |
@go tool pprof -svg -output pprof-out/cpu.svg pbench pprof-out/cpu.out | |
profile-mem-graph: | |
@go tool pprof -svg -output pprof-out/mem.svg pbench pprof-out/mem.out | |
profile-server-cpu: | |
@go tool pprof http://localhost:8000/debug/pprof/profile | |
profile-server-mem: | |
@go tool pprof http://localhost:8000/debug/pprof/heap | |
build-server: | |
@go build -o server *.go | |
run-server: build-server | |
./server | |
run-vegeta-fib: | |
@vegeta attack -connections 200 -output vegeta-fib.out -targets vfib-target.txt | |
run-vegeta-fibb: | |
@vegeta attack -connections 200 -output vegeta-fibb.out -targets vfibb-target.txt | |
deps: | |
go get github.com/tsenart/vegeta | |
pprof-out: | |
mkdir pprof-out |
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
_ "net/http/pprof" | |
"strconv" | |
"strings" | |
) | |
func main() { | |
http.HandleFunc("/fib", fib) | |
http.HandleFunc("/fibb", fibbuffer) | |
fmt.Println("Server listening on port 8000") | |
if err := http.ListenAndServe(":8000", nil); err != nil { | |
log.Fatalf("Server stop listening with error %v", err) | |
} | |
} | |
func fib(w http.ResponseWriter, r *http.Request) { | |
nums := strings.Split(r.FormValue("nums"), ",") | |
if len(nums) == 0 { | |
log.Printf("invalid `nums` query parameter: %v", nums) | |
respondJSON( | |
w, | |
http.StatusBadRequest, | |
[]byte(`{"status":"error","message":"Request doesn't constain nums query param or it's empty"}`)) | |
return | |
} | |
rb := fibResp{ | |
Status: "ok", | |
Results: make([]uint64, len(nums)), | |
} | |
// Create fibonacci function here because it isn't concurrent safe | |
// besides that we have more memory allocation to see in the profiler | |
fibFn := FibonacciMapCache() | |
for i, sn := range nums { | |
n, err := strconv.Atoi(sn) | |
if err != nil { | |
log.Printf("Atoi error %v (nums query param: %v)", err, nums) | |
respondJSON( | |
w, | |
http.StatusBadRequest, | |
[]byte(`{"status":"error","message":"Nums query param contains a values which cannot be parsed as a number"}`)) | |
return | |
} | |
rb.Results[i] = fibFn(uint(n)) | |
} | |
bj, err := json.Marshal(rb) | |
if err != nil { | |
log.Printf("Error marshalling response struct to JSON: %v", err) | |
w.WriteHeader(http.StatusInternalServerError) | |
return | |
} | |
respondJSON(w, http.StatusOK, bj) | |
} | |
func fibbuffer(w http.ResponseWriter, r *http.Request) { | |
nums := strings.Split(r.FormValue("nums"), ",") | |
if len(nums) == 0 { | |
log.Printf("invalid `nums` query parameter: %v", nums) | |
respondJSON( | |
w, | |
http.StatusBadRequest, | |
[]byte(`{"status":"error","message":"Request doesn't constain nums query param or it's empty"}`)) | |
return | |
} | |
rb := make([]uint64, len(nums)) | |
for i, sn := range nums { | |
n, err := strconv.Atoi(sn) | |
if err != nil { | |
log.Printf("Atoi error %v (nums query param: %v)", err, nums) | |
respondJSON( | |
w, | |
http.StatusBadRequest, | |
[]byte(`{"status":"error","message":"Nums query param contains a values which cannot be parsed as a number"}`)) | |
return | |
} | |
rb[i] = FibonacciLoop(uint(n)) | |
} | |
bj := fmt.Sprintf(`{"status":"ok","results":%s}`, strings.Replace(fmt.Sprintf("%v", rb), " ", ",", -1)) | |
respondJSON(w, http.StatusOK, []byte(bj)) | |
} | |
func respondJSON(w http.ResponseWriter, statusCode int, body []byte) { | |
h := w.Header() | |
h.Set("Content-Type", "application/json") | |
w.WriteHeader(statusCode) | |
w.Write(body) | |
} | |
type fibResp struct { | |
Status string `json:"status"` | |
Results []uint64 `json:"results"` | |
} |
package main | |
import "math" | |
func PowOf2(exponent uint) uint64 { | |
return uint64(math.Pow(float64(2), float64(exponent))) | |
} | |
func PowOf2Loop(exponent uint) uint64 { | |
var result uint64 = 1 | |
for i := uint(0); i < exponent; i++ { | |
result *= 2 | |
} | |
return result | |
} | |
func PowOf2Shift(exponent uint) uint64 { | |
if exponent == 0 { | |
return 1 | |
} | |
return 2 << (exponent - 1) | |
} | |
func FibonacciRecursive(n uint) uint64 { | |
if n <= 1 { | |
return uint64(n) | |
} | |
return FibonacciRecursive(n-1) + FibonacciRecursive(n-2) | |
} | |
func FibonacciMapCache() func(uint) uint64 { | |
cache := make(map[uint]uint64) | |
var fib func(n uint) uint64 | |
fib = func(n uint) uint64 { | |
if n <= 1 { | |
return uint64(n) | |
} | |
if r, ok := cache[n]; ok { | |
return r | |
} | |
r := fib(n-1) + fib(n-2) | |
cache[n] = r | |
return r | |
} | |
return fib | |
} | |
func FibonacciSliceCache() func(uint) uint64 { | |
cache := make([]uint64, 0) | |
var fib func(n uint) uint64 | |
fib = func(n uint) uint64 { | |
if n <= 1 { | |
return uint64(n) | |
} | |
if uint(len(cache)) < (n - 1) { | |
nc := make([]uint64, (n - 1)) | |
copy(nc, cache) | |
cache = nc | |
} | |
if n != 0 && cache[n-2] != 0 { | |
return cache[n-2] | |
} | |
r := fib(n-1) + fib(n-2) | |
cache[n-2] = r | |
return r | |
} | |
return fib | |
} | |
func FibonacciLoop(n uint) uint64 { | |
if n <= 1 { | |
return uint64(n) | |
} | |
n2 := uint64(0) | |
n1 := uint64(1) | |
for i := uint(2); i < n; i++ { | |
n1, n2 = n1+n2, n1 | |
} | |
return n1 + n2 | |
} |
package main | |
import ( | |
"math/rand" | |
"testing" | |
) | |
func TestPowOf2(t *testing.T) { | |
assertEq(t, 2, PowOf2(1)) | |
assertEq(t, 4, PowOf2(2)) | |
assertEq(t, 256, PowOf2(8)) | |
assertEq(t, 4096, PowOf2(12)) | |
} | |
func TestPowOf2Loop(t *testing.T) { | |
assertEq(t, 2, PowOf2Loop(1)) | |
assertEq(t, 4, PowOf2Loop(2)) | |
assertEq(t, 256, PowOf2Loop(8)) | |
assertEq(t, 4096, PowOf2Loop(12)) | |
} | |
func TestPowOf2Shift(t *testing.T) { | |
assertEq(t, 2, PowOf2Shift(1)) | |
assertEq(t, 4, PowOf2Shift(2)) | |
assertEq(t, 256, PowOf2Shift(8)) | |
assertEq(t, 4096, PowOf2Shift(12)) | |
} | |
func TestFibonacciRecursive(t *testing.T) { | |
assertEq(t, 0, FibonacciRecursive(0)) | |
assertEq(t, 1, FibonacciRecursive(1)) | |
assertEq(t, 1, FibonacciRecursive(2)) | |
assertEq(t, 144, FibonacciRecursive(12)) | |
} | |
func TestFibonacciMapCache(t *testing.T) { | |
assertEq(t, 0, FibonacciMapCache()(0)) | |
assertEq(t, 1, FibonacciMapCache()(1)) | |
assertEq(t, 1, FibonacciMapCache()(2)) | |
assertEq(t, 144, FibonacciMapCache()(12)) | |
} | |
func TestFibonacciSliceCache(t *testing.T) { | |
assertEq(t, 0, FibonacciSliceCache()(0)) | |
assertEq(t, 1, FibonacciSliceCache()(1)) | |
assertEq(t, 1, FibonacciSliceCache()(2)) | |
assertEq(t, 144, FibonacciSliceCache()(12)) | |
} | |
func TestFibonacciLoop(t *testing.T) { | |
assertEq(t, 0, FibonacciLoop(0)) | |
assertEq(t, 1, FibonacciLoop(1)) | |
assertEq(t, 1, FibonacciLoop(2)) | |
assertEq(t, 144, FibonacciLoop(12)) | |
} | |
func BenchmarkPowOf2(b *testing.B) { | |
ri := 0 | |
for i := 0; i < b.N; i++ { | |
if ri >= len(rnums) { | |
ri = 0 | |
} | |
n := rnums[ri] | |
ri++ | |
PowOf2(n) | |
} | |
} | |
func BenchmarkPowOf2Loop(b *testing.B) { | |
ri := 0 | |
for i := 0; i < b.N; i++ { | |
if ri >= len(rnums) { | |
ri = 0 | |
} | |
n := rnums[ri] | |
ri++ | |
PowOf2Loop(n) | |
} | |
} | |
func BenchmarkPowOf2Shift(b *testing.B) { | |
ri := 0 | |
for i := 0; i < b.N; i++ { | |
if ri >= len(rnums) { | |
ri = 0 | |
} | |
n := rnums[ri] | |
ri++ | |
PowOf2Shift(n) | |
} | |
} | |
func BenchmarkFibonacciRecursive(b *testing.B) { | |
fi := 0 | |
for i := 0; i < b.N; i++ { | |
if fi >= len(fnums) { | |
fi = 0 | |
} | |
n := fnums[fi] | |
fi++ | |
FibonacciRecursive(n) | |
} | |
} | |
func BenchmarkFibonacciMapCache(b *testing.B) { | |
fi := 0 | |
fib := FibonacciMapCache() | |
for i := 0; i < b.N; i++ { | |
if fi >= len(fnums) { | |
fi = 0 | |
} | |
n := fnums[fi] | |
fi++ | |
fib(n) | |
} | |
} | |
func BenchmarkFibonacciSliceCache(b *testing.B) { | |
fi := 0 | |
fib := FibonacciSliceCache() | |
for i := 0; i < b.N; i++ { | |
if fi >= len(fnums) { | |
fi = 0 | |
} | |
n := fnums[fi] | |
fi++ | |
fib(n) | |
} | |
} | |
func BenchmarkFibonacciLoop(b *testing.B) { | |
fi := 0 | |
for i := 0; i < b.N; i++ { | |
if fi >= len(fnums) { | |
fi = 0 | |
} | |
n := fnums[fi] | |
fi++ | |
FibonacciLoop(n) | |
} | |
} | |
const nlen = 100 | |
var rnums []uint | |
var fnums []uint | |
func init() { | |
rnums = generateRandomNums(nlen, 0) | |
fnums = generateRandomNums(nlen, 25) | |
} | |
func assertEq(t *testing.T, expected uint64, value uint64) { | |
if expected != value { | |
t.Fatalf("Expectation failed, got %d, expected %d", value, expected) | |
} | |
} | |
func generateRandomNums(amount uint, top uint) []uint { | |
s := make([]uint, amount) | |
for i := uint(0); i < amount; i++ { | |
if top > 0 { | |
s[i] = uint(rand.Int31n(int32(top))) | |
} else { | |
s[i] = uint(rand.Int31()) | |
} | |
} | |
return s | |
} |
GET http://localhost:8000/fib?nums=568,7885,954,5461 |
GET http://localhost:8000/fibb?nums=568,7885,954,5461 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment