Skip to content

Commit

Permalink
runtime: improve comments for nextSample
Browse files Browse the repository at this point in the history
The previous comment of nextSample didn't mention Poisson processes,
which is the reason why it needed to create an exponential
distribution, so it was hard to follow the reasoning for people
not highly familiar with statistics.

Since we're at it, we also make it clear that we are just creating
a random number with exponential distribution by moving the
bulk of the function into a new fastexprand().

No functional changes.

Change-Id: I9c275e87edb3418ee0974257af64c73465028ad7
Reviewed-on: https://go-review.googlesource.com/65657
Reviewed-by: Austin Clements <austin@google.com>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
  • Loading branch information
rasky authored and aclements committed Sep 26, 2017
1 parent 3f04db4 commit 8e11cb3
Showing 1 changed file with 26 additions and 20 deletions.
46 changes: 26 additions & 20 deletions src/runtime/malloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,11 +865,13 @@ func profilealloc(mp *m, x unsafe.Pointer, size uintptr) {
mProf_Malloc(x, size)
}

// nextSample returns the next sampling point for heap profiling.
// It produces a random variable with a geometric distribution and
// mean MemProfileRate. This is done by generating a uniformly
// distributed random number and applying the cumulative distribution
// function for an exponential.
// nextSample returns the next sampling point for heap profiling. The goal is
// to sample allocations on average every MemProfileRate bytes, but with a
// completely random distribution over the allocation timeline; this
// corresponds to a Poisson process with parameter MemProfileRate. In Poisson
// processes, the distance between two samples follows the exponential
// distribution (exp(MemProfileRate)), so the best return value is a random
// number taken from an exponential distribution whose mean is MemProfileRate.
func nextSample() int32 {
if GOOS == "plan9" {
// Plan 9 doesn't support floating point in note handler.
Expand All @@ -878,33 +880,37 @@ func nextSample() int32 {
}
}

period := MemProfileRate
return fastexprand(MemProfileRate)
}

// make nextSample not overflow. Maximum possible step is
// -ln(1/(1<<kRandomBitCount)) * period, approximately 20 * period.
// fastexprand returns a random number from an exponential distribution with
// the specified mean.
func fastexprand(mean int) int32 {
// Avoid overflow. Maximum possible step is
// -ln(1/(1<<randomBitCount)) * mean, approximately 20 * mean.
switch {
case period > 0x7000000:
period = 0x7000000
case period == 0:
case mean > 0x7000000:
mean = 0x7000000
case mean == 0:
return 0
}

// Let m be the sample rate,
// the probability distribution function is m*exp(-mx), so the CDF is
// p = 1 - exp(-mx), so
// q = 1 - p == exp(-mx)
// log_e(q) = -mx
// -log_e(q)/m = x
// x = -log_e(q) * period
// x = log_2(q) * (-log_e(2)) * period ; Using log_2 for efficiency
// Take a random sample of the exponential distribution exp(-mean*x).
// The probability distribution function is mean*exp(-mean*x), so the CDF is
// p = 1 - exp(-mean*x), so
// q = 1 - p == exp(-mean*x)
// log_e(q) = -mean*x
// -log_e(q)/mean = x
// x = -log_e(q) * mean
// x = log_2(q) * (-log_e(2)) * mean ; Using log_2 for efficiency
const randomBitCount = 26
q := fastrand()%(1<<randomBitCount) + 1
qlog := fastlog2(float64(q)) - randomBitCount
if qlog > 0 {
qlog = 0
}
const minusLog2 = -0.6931471805599453 // -ln(2)
return int32(qlog*(minusLog2*float64(period))) + 1
return int32(qlog*(minusLog2*float64(mean))) + 1
}

// nextSampleNoFP is similar to nextSample, but uses older,
Expand Down

0 comments on commit 8e11cb3

Please sign in to comment.