Skip to content

Instantly share code, notes, and snippets.

@SamWhited
Last active April 25, 2016 19:54
Show Gist options
  • Save SamWhited/ebe4f5923526c0d9220f1b5b23b56eba to your computer and use it in GitHub Desktop.
Save SamWhited/ebe4f5923526c0d9220f1b5b23b56eba to your computer and use it in GitHub Desktop.
Go bcrypt benchmarks
license: gpl-3.0

It can be hard or impossible to know before compiling when context switches will happen in Go. This means that if you have many goroutines running CPU intensive work (such as the blowfish key expansion step of bcrypt) concurrently (generally we want to run CPU intensive work in parallel and not yield the event loop) it can be possible to enter a situation where work almost never finishes because we continuously context switch in new work as we add it (and if all the operations are roughly the same length Go will tend to complete them all at roughly the same time).

Inlining helps this problem, but complicated crypto functions like blowfish.ExpandKey can be more complicated than is allowed by the inliners "harriness budget". Turning this budget up helps a lot, but results in bigger binary sizes.

The benchmark in bcrypt_benchmark_test.go was run against Go 1.6, Go 1.7 HEAD (as of 2016-04-25), and the same dev version of Go 1.7 with the max inlining budget turned up to 1000 using the enclosed patch.

// +build ignore
package main
import (
"sync"
"testing"
"golang.org/x/crypto/bcrypt"
)
var pass = []byte(`superfly`)
const cost = 11
func BenchmarkBcrypt(b *testing.B) {
for n := 0; n < b.N; n++ {
crypt, _ := bcrypt.GenerateFromPassword(pass, cost)
_ = crypt
}
}
func runParallelBcrypts(num int) {
var wg sync.WaitGroup
wg.Add(num)
for i := 0; i < num; i++ {
go func() {
crypt, _ := bcrypt.GenerateFromPassword(pass, cost)
_ = crypt
wg.Done()
}()
}
wg.Wait()
}
func BenchmarkBcrypt10Parallel(b *testing.B) {
for n := 0; n < b.N; n++ {
runParallelBcrypts(10)
}
}
func BenchmarkBcrypt100Parallel(b *testing.B) {
for n := 0; n < b.N; n++ {
runParallelBcrypts(100)
}
}
// Probably going to take way too long due to context switching on the non-inlined version…
// func BenchmarkBcrypt1000Parallel(b *testing.B) {
// for n := 0; n < b.N; n++ {
// runParallelBcrypts(1000)
// }
// }
Benchmark 1.7 dev maxBudget=1000 1.7 dev maxBudget=80 1.6 maxBudget=80?
BenchmarkBcrypt-4 138354851 145777572 171263426
BenchmarkBcrypt10Parallel-4 431629897 465049071 542888102
BenchmarkBcrypt100Parallel-4 4267734139 4593104302 5346905378
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#6b486b", "#a05d56", "#d0743c"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", function(error, data) {
if (error) throw error;
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "Benchmark"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(data.map(function(d) { return d.Benchmark; }));
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.ages, function(d) { return d.value; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("(ns/op)");
var benchmark = svg.selectAll(".benchmark")
.data(data)
.enter().append("g")
.attr("class", "benchmark")
.attr("transform", function(d) { return "translate(" + x0(d.Benchmark) + ",0)"; });
benchmark.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go
index f9e4256..8390e2c 100644
--- a/src/cmd/compile/internal/gc/inl.go
+++ b/src/cmd/compile/internal/gc/inl.go
@@ -127,7 +127,7 @@ func caninl(fn *Node) {
return
}
- const maxBudget = 80
+ const maxBudget = 1000
budget := maxBudget // allowed hairyness
if ishairylist(fn.Nbody, &budget) || budget < 0 {
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment