Exponential Bucket Histogram - part 7 (#3499)

* hammer out the upper/lower bound of scale

* comment
This commit is contained in:
Reiley Yang 2022-07-27 16:21:55 -07:00 committed by GitHub
parent 6789efab53
commit 25cfa1721f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 160 additions and 67 deletions

View File

@ -35,7 +35,18 @@ internal class ExponentialBucketHistogram
private int scale;
private double scalingFactor; // 2 ^ scale / log(2)
public ExponentialBucketHistogram(int scale, int maxBuckets = 160)
/// <summary>
/// Initializes a new instance of the <see cref="ExponentialBucketHistogram"/> class.
/// </summary>
/// <param name="maxBuckets">
/// The maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. The default value is 160.
/// </param>
public ExponentialBucketHistogram(int maxBuckets = 160)
: this(maxBuckets, 20)
{
}
internal ExponentialBucketHistogram(int maxBuckets, int scale)
{
/*
The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIndex(double.MaxValue) ]:
@ -80,7 +91,12 @@ internal class ExponentialBucketHistogram
*/
Guard.ThrowIfOutOfRange(scale, min: -11, max: 20);
Guard.ThrowIfOutOfRange(maxBuckets, min: 1);
/*
Regardless of the scale, MapToIndex(1) will always be -1, so we need two buckets at minimum:
bucket[-1] = (1/base, 1]
bucket[0] = (1, base]
*/
Guard.ThrowIfOutOfRange(maxBuckets, min: 2);
this.Scale = scale;
this.PositiveBuckets = new CircularBufferBuckets(maxBuckets);

View File

@ -27,21 +27,21 @@ public class ExponentialBucketHistogramTest
[Fact]
public void IndexLookup()
{
// An exponential bucket histogram with scale = 0.
// The base is 2 ^ (2 ^ -0) = 2.
// The buckets are:
//
// ...
// bucket[-3]: (1/8, 1/4]
// bucket[-2]: (1/4, 1/2]
// bucket[-1]: (1/2, 1]
// bucket[0]: (1, 2]
// bucket[1]: (2, 4]
// bucket[2]: (4, 8]
// bucket[3]: (8, 16]
// ...
var histogram = new ExponentialBucketHistogram(0);
/*
An exponential bucket histogram with scale = 0.
The base is 2 ^ (2 ^ 0) = 2.
The buckets are:
...
bucket[-3]: (1/8, 1/4]
bucket[-2]: (1/4, 1/2]
bucket[-1]: (1/2, 1]
bucket[0]: (1, 2]
bucket[1]: (2, 4]
bucket[2]: (4, 8]
bucket[3]: (8, 16]
...
*/
var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
Assert.Equal(-1075, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-1074, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2
@ -51,10 +51,12 @@ public class ExponentialBucketHistogramTest
Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6
Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7
Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8
Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308
Assert.Equal(-1025, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023)
Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308
Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive)
Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-1022, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308
Assert.Equal(-3, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25
Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5
@ -71,21 +73,21 @@ public class ExponentialBucketHistogramTest
Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308
Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
// An exponential bucket histogram with scale = -1.
// The base is 2 ^ (2 ^ 1) = 4.
// The buckets are:
//
// ...
// bucket[-3]: (1/64, 1/16]
// bucket[-2]: (1/16, 1/4]
// bucket[-1]: (1/4, 1]
// bucket[0]: (1, 4]
// bucket[1]: (4, 16]
// bucket[2]: (16, 64]
// bucket[3]: (64, 256]
// ...
histogram = new ExponentialBucketHistogram(-1);
/*
An exponential bucket histogram with scale = -1.
The base is 2 ^ (2 ^ 1) = 4.
The buckets are:
...
bucket[-3]: (1/64, 1/16]
bucket[-2]: (1/16, 1/4]
bucket[-1]: (1/4, 1]
bucket[0]: (1, 4]
bucket[1]: (4, 16]
bucket[2]: (16, 64]
bucket[3]: (64, 256]
...
*/
histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -1);
Assert.Equal(-538, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2
@ -95,10 +97,12 @@ public class ExponentialBucketHistogramTest
Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6
Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7
Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308
Assert.Equal(-513, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023)
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive)
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-511, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308
Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5
@ -115,21 +119,21 @@ public class ExponentialBucketHistogramTest
Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308
Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
// An exponential bucket histogram with scale = -2.
// The base is 2 ^ (2 ^ 2) = 16.
// The buckets are:
//
// ...
// bucket[-3]: (1/4096, 1/256]
// bucket[-2]: (1/256, 1/16]
// bucket[-1]: (1/16, 1]
// bucket[0]: (1, 16]
// bucket[1]: (16, 256]
// bucket[2]: (256, 4096]
// bucket[3]: (4096, 65536]
// ...
histogram = new ExponentialBucketHistogram(-2);
/*
An exponential bucket histogram with scale = -2.
The base is 2 ^ (2 ^ 2) = 16.
The buckets are:
...
bucket[-3]: (1/4096, 1/256]
bucket[-2]: (1/256, 1/16]
bucket[-1]: (1/16, 1]
bucket[0]: (1, 16]
bucket[1]: (16, 256]
bucket[2]: (256, 4096]
bucket[3]: (4096, 65536]
...
*/
histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -2);
Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2
@ -139,10 +143,12 @@ public class ExponentialBucketHistogramTest
Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6
Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7
Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308
Assert.Equal(-257, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023)
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive)
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5
@ -159,21 +165,92 @@ public class ExponentialBucketHistogramTest
Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308
Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
// An exponential bucket histogram with scale = 1.
// The base is 2 ^ (2 ^ -1) = sqrt(2) = 1.41421356237.
// The buckets are:
//
// ...
// bucket[-3]: (0.35355339059, 1/2]
// bucket[-2]: (1/2, 0.70710678118]
// bucket[-1]: (0.70710678118, 1]
// bucket[0]: (1, 1.41421356237]
// bucket[1]: (1.41421356237, 2]
// bucket[2]: (2, 2.82842712474]
// bucket[3]: (2.82842712474, 4]
// ...
/*
An exponential bucket histogram with scale = -10.
The base is 2 ^ (2 ^ 10) = 2 ^ 1024 = double.MaxValue + 2 ^ -52 (slightly bigger than double.MaxValue).
The buckets are:
bucket[-2]: [double.Epsilon, 2 ^ -1024]
bucket[-1]: (2 ^ -1024, 1]
bucket[0]: (1, double.MaxValue]
*/
histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -10);
histogram = new ExponentialBucketHistogram(1);
Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.1125369292536007E-308 (2 ^ -1023)
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1
Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002
Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
/*
An exponential bucket histogram with scale = -11.
The base is 2 ^ (2 ^ 11) = 2 ^ 2048 (much bigger than double.MaxValue).
The buckets are:
bucket[-1]: [double.Epsilon, 1]
bucket[0]: (1, double.MaxValue]
*/
histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -11);
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1
Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002
Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
/*
An exponential bucket histogram with scale = 1.
The base is 2 ^ (2 ^ -1) = sqrt(2) = 1.41421356237.
The buckets are:
...
bucket[-3]: (0.35355339059, 1/2]
bucket[-2]: (1/2, 0.70710678118]
bucket[-1]: (0.70710678118, 1]
bucket[0]: (1, 1.41421356237]
bucket[1]: (1.41421356237, 2]
bucket[2]: (2, 2.82842712474]
bucket[3]: (2.82842712474, 4]
...
*/
histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 1);
}
[Fact]
public void InfinityHandling()
{
var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
histogram.Record(double.PositiveInfinity);
histogram.Record(double.NegativeInfinity);
Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size);
}
[Fact]
public void NaNHandling()
{
var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
histogram.Record(double.NaN); // NaN (language/runtime native)
histogram.Record(IEEE754Double.FromString("0 11111111111 0000000000000000000000000000000000000000000000000001").DoubleValue); // sNaN on x86/64 and ARM
histogram.Record(IEEE754Double.FromString("0 11111111111 1000000000000000000000000000000000000000000000000001").DoubleValue); // qNaN on x86/64 and ARM
histogram.Record(IEEE754Double.FromString("0 11111111111 1111111111111111111111111111111111111111111111111111").DoubleValue); // NaN (alternative encoding)
Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size);
}
[Fact]
public void ZeroHandling()
{
var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
histogram.Record(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // +0
histogram.Record(IEEE754Double.FromString("1 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // -0
Assert.Equal(2, histogram.ZeroCount);
}
}