5#ifndef LINE_API_FJ_FJ_TAIL_FORKTAIL_H
6#define LINE_API_FJ_FJ_TAIL_FORKTAIL_H
68inline int digamma_shift() {
return 20; }
78T digamma(
const T& x0) {
79 const T one = num_traits<T>::from_int(1);
80 const T lim = num_traits<T>::from_int(digamma_shift());
81 T x = x0, acc = num_traits<T>::from_int(0);
86 const T inv = one / x, inv2 = inv * inv;
88 T s = log(x) - inv / num_traits<T>::from_int(2);
90 s -= p / num_traits<T>::from_int(12);
92 s += p / num_traits<T>::from_int(120);
94 s -= p / num_traits<T>::from_int(252);
96 s += p / num_traits<T>::from_int(240);
98 s -= p / num_traits<T>::from_int(132);
100 s += p * num_traits<T>::from_int(691) / num_traits<T>::from_int(32760);
106T trigamma(
const T& x0) {
107 const T one = num_traits<T>::from_int(1);
108 const T lim = num_traits<T>::from_int(digamma_shift());
109 T x = x0, acc = num_traits<T>::from_int(0);
111 acc += one / (x * x);
114 const T inv = one / x, inv2 = inv * inv;
115 T s = inv + inv2 / num_traits<T>::from_int(2);
117 s += p / num_traits<T>::from_int(6);
119 s -= p / num_traits<T>::from_int(30);
121 s += p / num_traits<T>::from_int(42);
123 s -= p / num_traits<T>::from_int(30);
125 s += p * num_traits<T>::from_int(5) / num_traits<T>::from_int(66);
138void ge_fit(
const T& ET,
const T& VT, T& alpha, T& beta) {
139 const T one = num_traits<T>::from_int(1);
140 const T scv = VT / (ET * ET);
141 const T fine = num_traits<T>::from_double(1e-12);
142 if (
num_abs(T(scv - one)) < fine) {
145 const T tri1 = trigamma(one);
146 const T di1 = digamma(one);
147 auto residual = [&](
const T& la) {
150 const T d = digamma(T(a + one)) - di1;
151 return T((tri1 - trigamma(T(a + one))) / (d * d) - scv);
153 T lo = num_traits<T>::from_int(-30), hi = num_traits<T>::from_int(30);
154 const T lomin = num_traits<T>::from_int(-700), himax = num_traits<T>::from_int(700);
155 const T step = num_traits<T>::from_int(30);
156 const T zero = num_traits<T>::from_int(0);
157 while (residual(lo) < zero && lo > lomin) lo -= step;
158 while (residual(hi) > zero && hi < himax) hi += step;
159 const RootResult<T> rr =
160 root_brent<T>(residual, lo, hi, num_traits<T>::from_double(1e-14), 500);
162 alpha = exp(rr.root);
164 beta = ET / (digamma(T(alpha + one)) - digamma(one));
190 const std::vector<T>& K = std::vector<T>(),
192 const std::vector<T>& P = std::vector<T>()) {
194 "fj_tail_forktail requires transcendental arithmetic: the generalized "
195 "exponential fit inverts a ratio of digamma and trigamma values");
204 std::vector<T> Kv = K;
205 if (Kv.empty()) Kv.assign(1, one);
207 if (p > one) p = p / hundred;
208 if (p <= zero || p >= one)
209 throw InputError(
"fj_tail_forktail: the percentile must lie strictly between 0 and 1");
210 if (VT.size() != ET.size())
211 throw InputError(
"fj_tail_forktail: ET and VT must have the same number of entries");
212 if (ET.empty())
throw InputError(
"fj_tail_forktail: ET must be nonempty");
213 for (std::size_t i = 0; i < ET.size(); ++i)
214 if (ET[i] <= zero || VT[i] <= zero)
216 "fj_tail_forktail: the task response time mean and variance must be positive");
218 const std::size_t nbranch = ET.size();
220 r.
alpha.assign(nbranch, zero);
221 r.
beta.assign(nbranch, zero);
222 for (std::size_t i = 0; i < nbranch; ++i) detail::ge_fit(ET[i], VT[i], r.
alpha[i], r.
beta[i]);
224 if (nbranch == 1 && Kv.size() > 1) {
226 if (P.size() != Kv.size())
228 "fj_tail_forktail: a vector of fanouts K needs a probability vector P of the "
231 for (std::size_t i = 0; i < P.size(); ++i) {
233 throw InputError(
"fj_tail_forktail: the fanout probabilities P must be "
234 "non-negative and sum to one");
239 "fj_tail_forktail: the fanout probabilities P must be non-negative and sum to one");
240 const T alpha = r.
alpha[0], beta = r.
beta[0];
241 T kmin = Kv[0], kmax = Kv[0];
242 for (std::size_t i = 1; i < Kv.size(); ++i) {
243 if (Kv[i] < kmin) kmin = Kv[i];
244 if (Kv[i] > kmax) kmax = Kv[i];
246 auto mixres = [&](
const T& x) {
248 for (std::size_t i = 0; i < Kv.size(); ++i)
249 s += P[i] * exp(Kv[i] * alpha * log1p(-exp(-x / beta)));
252 const T xlo = -beta * log1p(-exp(log(p) / (kmin * alpha)));
253 const T xhi = -beta * log1p(-exp(log(p) / (kmax * alpha)));
254 const T lo = xlo < xhi ? xlo : xhi;
255 const T hi = xlo < xhi ? xhi : xlo;
265 const T flo = mixres(lo);
266 const T fhi = mixres(hi);
283 r.
xp = -r.
beta[0] * log1p(-exp(log(p) / (Kv[0] * r.
alpha[0])));
288 const T logp = log(p);
289 auto residual = [&](
const T& x) {
291 for (std::size_t i = 0; i < nbranch; ++i)
292 s += r.
alpha[i] * log1p(-exp(-x / r.
beta[i]));
296 for (std::size_t i = 0; i < nbranch; ++i) {
297 const T cand = -r.
beta[i] * log1p(-exp(logp / r.
alpha[i]));
298 if (cand > xlo) xlo = cand;
301 while (residual(xhi) < zero) {
304 throw InputError(
"fj_tail_forktail: could not bracket the ForkTail percentile");
307 if (residual(xlo) > zero) {
309 while (residual(xlo) > zero && xlo > tiny) xlo = xlo / two;
321 return fj_tail_forktail(std::vector<T>(1, ET), std::vector<T>(1, VT), std::vector<T>(1, K), p);
The exception types the port throws.
Shared return types and arithmetic helpers for the templated fork-join port.
ForkTailResult< T > fj_tail_forktail(const std::vector< T > &ET, const std::vector< T > &VT, const std::vector< T > &K=std::vector< T >(), const T &p_in=num_traits< T >::from_int(99), const std::vector< T > &P=std::vector< T >())
ForkTail black-box tail-latency approximation for fork-join requests.
RootResult< T > root_brent(F f, const T &a0, const T &b0, const T &tol, unsigned maxiter=200)
Brent's method on a bracket with a sign change.
Number-type abstraction for the templated API port.
Deterministic scalar root finding.
Outcome of a scalar solve.
T root
best estimate of the root
Mirrors MATLAB's [xp, alpha, beta] return list.
std::vector< T > beta
fitted scale parameters, one per branch
T xp
predicted p-th percentile of the request response time
std::vector< T > alpha
fitted shape parameters, one per branch