Approach: I'll implement the P² (P-square) algorithm which keeps a fixed small set of markers to approximate a single quantile (here 95th) in streaming fashion with O(1) memory. I’ll also summarize t-digest trade-offs (more accurate, slightly more memory).python
# python
class P2Quantile:
"""
P^2 algorithm for a single quantile (q in (0,1)).
Usage:
p2 = P2Quantile(0.95)
for x in latency_stream: p2.add(x)
estimate = p2.get()
"""
def __init__(self, q=0.95):
assert 0 < q < 1
self.q = q
self.n = 0
self.markers = [] # heights h[0..4]
self.pos = [0,0,0,0,0] # positions n[0..4]
self.des = [0, q/2, q, (1+q)/2, 1] # relative desired positions
def add(self, x):
if self.n < 5:
# bootstrap: store initial samples
self.markers.append(x)
self.n += 1
if self.n == 5:
self.markers.sort()
for i in range(5):
self.pos[i] = i+1
self.des = [1, 1+2*self.q, 1+4*self.q, 3+2*self.q, 5]
return
# find k: marker interval containing x
k = 0
if x < self.markers[0]:
self.markers[0] = x
k = 0
elif x >= self.markers[-1]:
self.markers[-1] = x
k = 3
else:
for i in range(4):
if self.markers[i] <= x < self.markers[i+1]:
k = i
break
# increment positions
for i in range(5):
if i <= k:
self.pos[i] += 1
else:
self.pos[i] += 0
self.n += 1
# update desired positions
for i in range(5):
self.des[i] += [0, self.q/2, self.q, (1+self.q)/2, 1][i]
# adjust heights
for i in range(1,4):
d = self.des[i] - self.pos[i]
if (d >= 1 and self.pos[i+1]-self.pos[i]>1) or (d <= -1 and self.pos[i]-self.pos[i-1]>1):
d_sign = int(d/abs(d))
# parabolic prediction
p = self.pos; m = self.markers
delta = d_sign
num = delta*(p[i]-p[i-1]+delta)*(m[i+1]-m[i])/(p[i+1]-p[i]) + \
delta*(p[i+1]-p[i]-delta)*(m[i]-m[i-1])/(p[i]-p[i-1])
den = (p[i+1]-p[i-1])
newh = m[i] + num/den
# if newh not monotonic, use linear
if m[i-1] < newh < m[i+1]:
m[i] = newh
else:
m[i] = m[i] + delta*(m[i + delta] - m[i])/(p[i + delta] - p[i])
self.pos[i] += delta
def get(self):
if self.n == 0:
return None
if self.n < 5:
return sorted(self.markers)[int((self.n-1)*self.q)]
return self.markers[2] # marker 2 estimates the quantile
Key concepts:- P² maintains 5 markers (heights & positions) and updates in O(1) per sample.- Memory: constant (5 floats + small bookkeeping). Very cheap.- Accuracy: Good for moderate distributions and single quantile, but can be biased with extreme tails or highly skewed/ multimodal data.Trade-offs vs t-digest:- t-digest stores centroids, adaptive compression (configurable), better tail accuracy for high percentiles (95th/99th). Memory grows with desired accuracy but remains bounded by compression parameter.- P²: fixed tiny memory, faster, simpler; less accurate for complex/volatile distributions.- Choose P² when memory is extremely tight and one quantile is needed. Choose t-digest when tail accuracy matters and slightly more memory/CPU is acceptable.