468 const std::size_t nidx =
lsn.nidx;
469 if (nidx == 0)
throw InputError(
"SolverLDES (native LN engine): empty layered model");
474 for (std::size_t k = 1; k <= nidx && k <
lsn.sched.size(); ++k) {
475 const bool is_host = k >
lsn.hshift && k <=
lsn.hshift +
lsn.nhosts;
476 const bool is_task = k >
lsn.tshift && k <=
lsn.tshift +
lsn.ntasks;
477 if (!is_host && !is_task)
continue;
478 const SchedStrategy sc =
lsn.sched[k];
479 const bool ok = sc == SchedStrategy::FCFS || sc == SchedStrategy::INF ||
480 sc == SchedStrategy::LCFS || sc == SchedStrategy::SIRO ||
481 sc == SchedStrategy::HOL || sc == SchedStrategy::REF ||
482 sc == SchedStrategy::LCFSPRIO ||
483 (is_host && (sc == SchedStrategy::PS || sc == SchedStrategy::PSPRIO ||
484 sc == SchedStrategy::FCFSPRPRIO ||
485 sc == SchedStrategy::FCFSPIPRIO ||
486 sc == SchedStrategy::LCFSPRPRIO ||
487 sc == SchedStrategy::LCFSPIPRIO));
490 for (std::size_t c = 0; c < scname.size(); ++c)
491 scname[c] =
static_cast<char>(std::toupper(
static_cast<unsigned char>(scname[c])));
493 "SolverLDES (native LN engine) does not simulate " + scname +
" scheduling at " +
494 (is_host ?
"processor" :
"task") +
" '" +
lsn.names[k] +
495 "'. The layered engine implements " +
496 (is_host ?
"FCFS, LCFS, SIRO, HOL, FCFSPRIO, LCFSPRIO, FCFSPRPRIO, FCFSPIPRIO, "
497 "LCFSPRPRIO, LCFSPIPRIO, PS, PSPRIO and INF"
498 :
"FCFS, LCFS, SIRO, HOL, FCFSPRIO, LCFSPRIO and INF (a task holds threads, "
499 "it does not divide or preempt them)") +
500 "; a weighted discipline would additionally need the per-task shares the layered "
501 "struct does not carry.");
503 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
506 if (t <
lsn.hassetup.size() &&
lsn.hassetup[t]) {
507 const double m = (t <
lsn.mult.size()) ?
lsn.mult[t] : 1.0;
510 "' declares a setup time on an infinite-server task, which "
511 "holds no thread to power down; give it a finite "
516 if (t <
lsn.pools.size() &&
lsn.pools[t].npools() > 0)
518 "' declares heterogeneous server pools; LDES holds server "
519 "pools on processors only");
522 const std::uint64_t max_events =
524 const std::uint64_t base =
525 (o.
seed >= 0) ?
static_cast<std::uint64_t
>(o.
seed) : std::random_device{}();
534 const long long ln_seed =
static_cast<long long>(base);
535 std::vector<Rng> g_host, g_think;
536 g_host.reserve(nidx + 1);
537 g_think.reserve(nidx + 1);
538 for (std::size_t k = 0; k <= nidx; ++k) {
539 g_host.push_back(
engine::ln_rng(ln_seed,
static_cast<long long>(k) * 10 + 1000));
540 g_think.push_back(
engine::ln_rng(ln_seed,
static_cast<long long>(k) * 10));
545 std::vector<Sampler> hostdem(nidx + 1), think(nidx + 1), actthink(nidx + 1);
546 std::vector<bool> has_hostdem(nidx + 1,
false), has_think(nidx + 1,
false),
547 has_actthink(nidx + 1,
false);
550 std::vector<Rng> g_actthink;
551 g_actthink.reserve(nidx + 1);
552 for (std::size_t k = 0; k <= nidx; ++k)
553 g_actthink.push_back(
engine::ln_rng(ln_seed,
static_cast<long long>(k) * 10 + 6000));
554 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts && a <
lsn.actthink.size(); ++a) {
556 actthink[a] = Sampler(
lsn.actthink[a],
"the think time of activity '" +
lsn.names[a] +
"'");
557 has_actthink[a] =
true;
560 for (std::size_t k = 1; k <= nidx; ++k) {
561 if (k <
lsn.hostdem.size() && !
lsn.hostdem[k].disabled &&
563 hostdem[k] = Sampler(
lsn.hostdem[k],
"the host demand of '" +
lsn.names[k] +
"'");
564 has_hostdem[k] =
true;
566 if (k <
lsn.think.size() && !
lsn.think[k].disabled &&
568 think[k] = Sampler(
lsn.think[k],
"the think time of '" +
lsn.names[k] +
"'");
574 std::vector<std::size_t> host_servers(nidx + 1, 1), task_threads(nidx + 1, 1);
575 std::vector<SchedStrategy> host_sched(nidx + 1, SchedStrategy::FCFS);
576 std::vector<SchedStrategy> task_sched(nidx + 1, SchedStrategy::FCFS);
579 for (std::size_t h =
lsn.hshift + 1; h <=
lsn.hshift +
lsn.nhosts; ++h) {
580 const double m = (h <
lsn.mult.size()) ?
lsn.mult[h] : 1.0;
581 if (h <
lsn.sched.size()) host_sched[h] =
lsn.sched[h];
582 host_servers[h] = (std::isfinite(m) && host_sched[h] != SchedStrategy::INF)
583 ? std::max<std::size_t>(1,
static_cast<std::size_t
>(m + 0.5))
584 : std::numeric_limits<std::size_t>::max();
586 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
587 const double m = (t <
lsn.mult.size()) ?
lsn.mult[t] : 1.0;
588 if (t <
lsn.sched.size()) task_sched[t] =
lsn.sched[t];
589 task_threads[t] = (std::isfinite(m) && task_sched[t] != SchedStrategy::INF)
590 ?
static_cast<std::size_t
>(m + 0.5)
591 : std::numeric_limits<std::size_t>::max();
604 std::vector<std::size_t> repl(nidx + 1, 1);
605 for (std::size_t k = 1; k <
lsn.repl.size() && k <= nidx; ++k) {
606 const double r =
lsn.repl[k];
607 repl[k] = (r > 1.0) ?
static_cast<std::size_t
>(r + 0.5) : 1;
609 std::vector<std::size_t> host_slot0(nidx + 1, 0);
610 std::size_t nhost_slots = 0;
611 for (std::size_t h =
lsn.hshift + 1; h <=
lsn.hshift +
lsn.nhosts; ++h) {
612 host_slot0[h] = nhost_slots;
613 nhost_slots += repl[h];
615 if (nhost_slots == 0) nhost_slots = 1;
618 auto host_slot = [&](std::size_t host, std::size_t trep) {
619 return host_slot0[host] + (trep % repl[host]);
624 std::vector<std::size_t> task_slot0(nidx + 1, 0);
625 std::size_t ntask_slots = 0;
626 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
627 task_slot0[t] = ntask_slots;
628 ntask_slots += repl[t];
630 if (ntask_slots == 0) ntask_slots = 1;
631 auto task_slot = [&](std::size_t task, std::size_t trep) {
632 return task_slot0[task] + (trep % repl[task]);
644 auto callee_replica = [&](std::size_t caller_task, std::size_t crep,
645 std::size_t callee_task) -> std::size_t {
646 const std::size_t rb = repl[callee_task];
647 if (rb <= 1)
return 0;
648 std::size_t f =
static_cast<std::size_t
>(
lsn.fanout_at(caller_task, callee_task) + 0.5);
650 const std::size_t ra = repl[caller_task];
651 f = (rb > ra) ? std::max<std::size_t>(1, rb / ra) : 1;
653 f = std::min(std::max<std::size_t>(1, f), rb);
654 const std::size_t k =
655 (f > 1) ?
static_cast<std::size_t
>(uniform01(g_call) *
static_cast<double>(f)) : 0;
656 return ((crep * f) + std::min(k, f - 1)) % rb;
660 const std::size_t a_lo =
lsn.ashift + 1, a_hi =
lsn.ashift +
lsn.nacts;
661 auto is_act = [&](std::size_t k) {
return k >= a_lo && k <= a_hi; };
662 auto wgt = [&](std::size_t i, std::size_t j) {
665 auto pretype = [&](std::size_t a) {
668 auto posttype = [&](std::size_t a) {
671 auto phase_of = [&](std::size_t a) ->
int {
672 const std::size_t k = a -
lsn.ashift;
673 return (k <
lsn.actphase.size() &&
lsn.actphase[k] > 0) ?
lsn.actphase[k] : 1;
677 std::vector<std::vector<std::size_t> > succ_act(nidx + 1);
678 for (std::size_t a = a_lo; a <= a_hi; ++a)
679 for (std::size_t s :
lsn.graph.succ(a))
680 if (is_act(s)) succ_act[a].push_back(s);
688 std::vector<std::vector<double> > or_prob(nidx + 1);
689 for (std::size_t a = a_lo; a <= a_hi; ++a) {
690 const std::vector<std::size_t>& sc = succ_act[a];
691 if (sc.size() < 2)
continue;
694 for (std::size_t s : sc) {
695 const double w = wgt(a, s);
696 if (w != 1.0 && w > 0.0) prob =
true;
700 for (std::size_t s : sc) tot += wgt(a, s);
701 for (std::size_t s : sc) {
702 const double w = wgt(a, s);
703 or_prob[a].push_back((tot > 0.0 && tot != 1.0) ? w / tot : w);
708 std::vector<std::size_t> join_required(nidx + 1, 0);
709 for (std::size_t a = a_lo; a <= a_hi; ++a)
711 ++join_required[succ_act[a][0]];
719 std::vector<std::size_t> join_quorum(nidx + 1, 0);
720 std::vector<std::vector<std::size_t> > join_inputs(nidx + 1);
721 for (std::size_t a = a_lo; a <= a_hi; ++a)
723 join_inputs[succ_act[a][0]].push_back(a);
724 for (std::size_t j = a_lo; j <= a_hi; ++j) {
725 if (join_required[j] == 0)
continue;
726 const std::size_t q = j <
lsn.actquorum.size() ?
lsn.actquorum[j] : 0;
727 join_quorum[j] = (q >= 1 && q < join_required[j]) ? q : join_required[j];
736 std::vector<char> seen(nidx + 1, 0), root_input(nidx + 1, 0);
737 std::deque<std::size_t> todo;
738 for (std::size_t j = a_lo; j <= a_hi; ++j)
739 if (join_required[j] > 0) todo.push_back(j);
740 while (!todo.empty()) {
741 const std::size_t a = todo.front();
743 if (seen[a])
continue;
749 const std::vector<std::size_t>& sc = succ_act[a];
750 const bool choose = sc.size() == 1 || !or_prob[a].empty() ||
753 for (std::size_t s : sc) todo.push_back(s);
755 for (std::size_t j = a_lo; j <= a_hi; ++j) {
756 if (join_quorum[j] >= join_required[j])
continue;
757 for (std::size_t a : join_inputs[j])
760 "SolverLDES (native LN engine): the AND join into '" +
lsn.names[j] +
761 "' declares a quorum of " + std::to_string(join_quorum[j]) +
" of " +
762 std::to_string(join_required[j]) +
" inputs, but its input '" +
763 lsn.names[a] +
"' follows an earlier join without an AND fork in between, "
764 "so the main line of the invocation could arrive late; a quorum join must "
765 "be fed by forked branches only");
773 std::vector<std::size_t> bound(nidx + 1, 0);
774 for (std::size_t e =
lsn.eshift + 1; e <=
lsn.eshift +
lsn.nentries; ++e) {
775 const std::size_t t =
lsn.parent[e];
776 const std::vector<std::size_t>& acts =
777 (t <
lsn.actsof.size()) ?
lsn.actsof[t] : std::vector<std::size_t>();
778 for (std::size_t a : acts)
779 if (wgt(e, a) > 0.0) {
783 if (bound[e] != 0 || acts.empty())
continue;
784 for (std::size_t a : acts) {
787 bool haspred =
false;
788 for (std::size_t o2 : acts)
789 if (o2 != a && wgt(o2, a) > 0.0) {
798 if (bound[e] == 0) bound[e] = acts[0];
802 std::vector<LnCacheState> caches;
803 std::vector<std::size_t> cache_of_slot(ntask_slots, LN_NONE);
804 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
805 if (t >=
lsn.iscache.size() || !
lsn.iscache[t])
continue;
806 const std::size_t n = t <
lsn.nitems.size() ?
lsn.nitems[t] : 0;
807 if (n == 0)
continue;
808 std::vector<int> cap = (t <
lsn.itemcap.size()) ?
lsn.itemcap[t] : std::vector<int>();
809 if (cap.empty()) cap.assign(1, 1);
810 for (std::size_t m = 0; m < repl[t]; ++m) {
814 cs.rs = (t <
lsn.replacestrat.size()) ?
lsn.replacestrat[t]
816 cs.levels.assign(cap.size(), std::list<std::size_t>());
817 cs.retrieval = t <
lsn.hasretrieval.size() &&
lsn.hasretrieval[t];
819 cs.inflight.assign(n, 0);
820 cs.held.assign(n, {});
822 cache_of_slot[task_slot(t, m)] = caches.size();
823 caches.push_back(cs);
826 std::vector<LnCacheAccess> accesses;
827 std::vector<std::size_t> access_of_driver(nidx + 1, LN_NONE);
828 std::vector<std::size_t> branch_reply(nidx + 1, 0);
829 for (std::size_t e =
lsn.eshift + 1; e <=
lsn.eshift +
lsn.nentries; ++e) {
830 const std::size_t t =
lsn.parent[e];
831 if (t <= lsn.tshift || t >
lsn.tshift +
lsn.ntasks)
continue;
832 if (cache_of_slot[task_slot(t, 0)] == LN_NONE)
continue;
833 const std::size_t drv = bound[e];
834 const bool is_item = e <
lsn.itemproc.size() && !
lsn.itemproc[e].empty();
837 if (!is_item)
continue;
838 if (drv == 0 || succ_act[drv].size() < 2)
840 "' has no cache access with a hit and a miss branch");
842 for (std::size_t m = 0; m < repl[t]; ++m) ca.caches.push_back(cache_of_slot[task_slot(t, m)]);
843 ca.hit = succ_act[drv][0];
844 ca.miss = succ_act[drv][1];
846 std::size_t card = e <
lsn.nitems.size() ?
lsn.nitems[e] : 0;
847 if (card == 0) card = caches[ca.caches[0]].nitems;
849 for (std::size_t i = 0; i < card; ++i) {
850 const double v = (i <
lsn.itemproc[e].size())
852 if (std::isnan(v) || v < 0.0)
853 throw InputError(
"SolverLDES (native LN engine): ItemEntry '" +
lsn.names[e] +
854 "' has a popularity that is not a pmf");
856 ca.cdf.push_back(
sum);
859 throw InputError(
"SolverLDES (native LN engine): ItemEntry '" +
lsn.names[e] +
860 "' has a popularity of zero total mass");
861 for (
double& c : ca.cdf) c /=
sum;
862 access_of_driver[drv] = accesses.size();
863 branch_reply[ca.hit] = e;
864 branch_reply[ca.miss] = e;
865 accesses.push_back(ca);
869 for (std::size_t a = a_lo; a <= a_hi; ++a) {
870 if (access_of_driver[a] != LN_NONE)
continue;
871 for (std::size_t s : succ_act[a])
874 "' reads a cache, but no ItemEntry of a cache task with "
875 "a popularity is bound to it");
885 std::vector<std::vector<double> > A;
886 std::vector<double> b;
887 std::map<std::size_t, int> col;
889 std::vector<Lincon> lincon(nidx + 1);
890 std::vector<bool> has_lincon(nidx + 1,
false);
891 for (std::size_t k = 1; k <
lsn.lincon_A.size() && k <= nidx; ++k) {
893 if (A.
rows() == 0)
continue;
894 const bool host = k >
lsn.hshift && k <=
lsn.hshift +
lsn.nhosts;
895 const std::vector<std::size_t>* ops =
nullptr;
896 if (host && k <
lsn.tasksof.size()) ops = &
lsn.tasksof[k];
897 if (!host && k <
lsn.entriesof.size()) ops = &
lsn.entriesof[k];
898 const std::size_t ncols = ops ? ops->size() : 0;
899 if (A.
cols() != ncols)
900 throw InputError(
"SolverLDES (native LN engine): the admission constraint on '" +
901 lsn.names[k] +
"' has " + std::to_string(A.
cols()) +
902 " columns but the server has " + std::to_string(ncols) +
" operands");
903 if (ncols == 0)
continue;
905 lc.A.assign(A.
rows(), std::vector<double>(ncols, 0.0));
906 for (std::size_t r = 0; r < A.
rows(); ++r)
908 for (std::size_t r = 0; r < A.
rows(); ++r)
909 lc.b.push_back((k <
lsn.lincon_b.size() && r <
lsn.lincon_b[k].size())
911 : std::numeric_limits<double>::infinity());
912 for (std::size_t c = 0; c < ncols; ++c) lc.col[(*ops)[c]] =
static_cast<int>(c);
914 has_lincon[k] =
true;
916 auto lincon_col = [&](std::size_t elem, std::size_t operand) ->
int {
917 if (!has_lincon[elem])
return -1;
918 const std::map<std::size_t, int>::const_iterator it = lincon[elem].col.find(operand);
919 return it == lincon[elem].col.end() ? -1 : it->second;
922 auto admits = [&](std::size_t elem,
const std::vector<int>& occ,
int col) {
923 const Lincon& lc = lincon[elem];
924 for (std::size_t r = 0; r < lc.A.size(); ++r) {
925 double lhs = lc.A[r][
static_cast<std::size_t
>(col)];
926 for (std::size_t k = 0; k < occ.size(); ++k) lhs += lc.A[r][k] * occ[k];
927 if (lhs > lc.b[r] + 1e-9)
return false;
938 std::vector<bool> pooled(nidx + 1,
false);
939 std::vector<std::vector<double> > pool_capacity(nidx + 1);
940 std::vector<std::vector<std::vector<bool> > > pool_serves(nidx + 1);
941 std::vector<std::map<std::size_t, int> > pool_col(nidx + 1);
942 for (std::size_t h =
lsn.hshift + 1; h <=
lsn.hshift +
lsn.nhosts; ++h) {
943 if (h >=
lsn.pools.size() ||
lsn.pools[h].npools() == 0)
continue;
944 if (host_sched[h] == SchedStrategy::INF)
continue;
947 std::size_t declared = 0;
948 for (std::size_t p = 0; p < np; ++p) {
949 const double cnt = (p < P.
counts.size()) ? P.
counts[p] : 0.0;
950 const std::size_t c = cnt > 0.0 ?
static_cast<std::size_t
>(cnt) : 0;
952 pool_capacity[h].push_back(
static_cast<double>(c) * (r > 0.0 ? r : 1.0));
954 std::vector<bool> sv(
nc,
false);
955 for (std::size_t j = 0; j <
nc; ++j)
957 pool_serves[h].push_back(sv);
959 if (declared != host_servers[h])
960 throw InputError(
"SolverLDES (native LN engine): processor '" +
lsn.names[h] +
961 "' declares server pools holding " + std::to_string(declared) +
962 " servers but has multiplicity " +
963 (host_servers[h] == std::numeric_limits<std::size_t>::max()
964 ? std::string(
"Inf") : std::to_string(host_servers[h])) +
965 "; the pools must partition the servers");
966 const std::vector<std::size_t>& ops =
967 (h <
lsn.tasksof.size()) ?
lsn.tasksof[h] : std::vector<std::size_t>();
968 for (std::size_t j = 0; j < ops.size() && j <
nc; ++j) pool_col[h][ops[j]] =
static_cast<int>(j);
974 std::priority_queue<LnEvent, std::vector<LnEvent>, LnEventLater> evq;
975 std::uint64_t seq = 0;
978 std::deque<LnLine> lines;
979 std::deque<LnInv> invs;
980 std::vector<std::size_t> free_lines, free_invs;
981 std::vector<LnCustomer> custs;
984 std::vector<std::size_t> host_busy(nhost_slots, 0);
985 std::vector<std::deque<std::size_t>> host_queue(nhost_slots);
987 std::vector<std::vector<std::size_t> > pool_jobs(nhost_slots);
988 std::vector<double> pool_last(nhost_slots, 0.0), pool_rate(nhost_slots, 0.0);
989 std::vector<std::uint64_t> pool_gen(nhost_slots, 0);
990 std::vector<std::size_t> host_of_slot(nhost_slots, 0);
991 for (std::size_t h =
lsn.hshift + 1; h <=
lsn.hshift +
lsn.nhosts; ++h)
992 for (std::size_t m = 0; m < repl[h]; ++m) host_of_slot[host_slot0[h] + m] = h;
994 std::vector<std::vector<int> > hocc(nhost_slots), tocc(ntask_slots);
995 std::vector<std::deque<std::size_t> > hadm(nhost_slots), tadm(ntask_slots);
996 for (std::size_t h =
lsn.hshift + 1; h <=
lsn.hshift +
lsn.nhosts; ++h)
998 for (std::size_t m = 0; m < repl[h]; ++m)
999 hocc[host_slot0[h] + m].assign(lincon[h].A[0].size(), 0);
1003 std::vector<std::vector<bool>> thr_held(ntask_slots);
1004 std::vector<std::vector<ThreadState>> thr_state(ntask_slots);
1005 std::vector<std::vector<double>> thr_setup_t0(ntask_slots);
1006 std::vector<std::vector<std::uint64_t>> thr_gen(ntask_slots);
1007 std::vector<std::deque<std::size_t>> thr_queue(ntask_slots);
1008 std::vector<std::size_t> thr_owner(ntask_slots, 0);
1009 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
1010 const std::size_t n = (task_threads[t] == std::numeric_limits<std::size_t>::max())
1011 ? 0 : task_threads[t];
1012 for (std::size_t m = 0; m < repl[t]; ++m) {
1013 const std::size_t ts = task_slot(t, m);
1015 thr_held[ts].assign(n,
false);
1016 thr_state[ts].assign(n, ThreadState::ACTIVE);
1017 thr_setup_t0[ts].assign(n, 0.0);
1018 thr_gen[ts].assign(n, 0);
1019 if (has_lincon[t]) tocc[ts].assign(lincon[t].A[0].size(), 0);
1026 std::vector<Sampler> setupd(nidx + 1), delayoffd(nidx + 1);
1027 std::vector<bool> has_setup(nidx + 1,
false);
1028 std::vector<Rng> g_setup, g_doff;
1029 g_setup.reserve(nidx + 1);
1030 g_doff.reserve(nidx + 1);
1031 for (std::size_t k = 0; k <= nidx; ++k) {
1032 g_setup.push_back(
engine::ln_rng(ln_seed,
static_cast<long long>(k) * 10 + 2000));
1033 g_doff.push_back(
engine::ln_rng(ln_seed,
static_cast<long long>(k) * 10 + 3000));
1035 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
1036 if (t >=
lsn.hassetup.size() || !
lsn.hassetup[t])
continue;
1037 const bool su = t <
lsn.setuptime.size() && !
lsn.setuptime[t].disabled &&
1039 const bool df = t <
lsn.delayofftime.size() && !
lsn.delayofftime[t].disabled &&
1041 if (!su || !df)
continue;
1042 setupd[t] = Sampler(
lsn.setuptime[t],
"the setup time of '" +
lsn.names[t] +
"'");
1043 delayoffd[t] = Sampler(
lsn.delayofftime[t],
"the delay-off time of '" +
lsn.names[t] +
"'");
1044 has_setup[t] =
true;
1054 std::vector<Rng> g_route, g_item, g_rr;
1055 g_route.reserve(nidx + 1);
1056 for (std::size_t k = 0; k <= nidx; ++k)
1057 g_route.push_back(
engine::ln_rng(ln_seed, 940000 +
static_cast<long long>(k) * 10));
1058 for (std::size_t c = 0; c < caches.size(); ++c) {
1059 g_item.push_back(
engine::ln_rng(ln_seed, 950000 +
static_cast<long long>(c) * 10));
1060 g_rr.push_back(
engine::ln_rng(ln_seed, 960000 +
static_cast<long long>(c) * 10));
1070 std::vector<std::vector<std::size_t> > fwd_dst(nidx + 1);
1071 std::vector<std::vector<double> > fwd_prob(nidx + 1);
1072 for (std::size_t c = 1; c <=
lsn.ncalls && c <
lsn.calltype.size(); ++c) {
1074 const std::size_t src =
lsn.callpair_src[c], dst =
lsn.callpair_dst[c];
1075 if (src == 0 || dst == 0 || src > nidx || dst > nidx)
continue;
1076 fwd_dst[src].push_back(dst);
1077 fwd_prob[src].push_back(c <
lsn.callproc_mean.size()
1081 std::vector<Rng> g_fwd;
1082 g_fwd.reserve(nidx + 1);
1083 for (std::size_t k = 0; k <= nidx; ++k)
1084 g_fwd.push_back(
engine::ln_rng(ln_seed, 970000 +
static_cast<long long>(k) * 10));
1088 std::deque<std::size_t> runnable;
1098 auto task_prio = [&](std::size_t task) ->
int {
1099 return (task <
lsn.prio.size()) ?
lsn.prio[task] : 0;
1101 auto host_req_prio = [&](std::size_t lid) ->
int {
return task_prio(
lsn.parent[lines[lid].act]); };
1102 auto task_req_prio = [&](std::size_t vi) ->
int {
1103 const std::size_t c = invs[vi].caller;
1104 return c == LN_NONE ? 0 : task_prio(invs[lines[c].inv].task);
1110 auto fcfs_prio = [](SchedStrategy sc) {
1112 return sc == SchedStrategy::HOL || sc == SchedStrategy::FCFSPRPRIO ||
1113 sc == SchedStrategy::FCFSPIPRIO;
1115 auto lcfs_prio = [](SchedStrategy sc) {
1116 return sc == SchedStrategy::LCFSPRIO || sc == SchedStrategy::LCFSPRPRIO ||
1117 sc == SchedStrategy::LCFSPIPRIO;
1119 auto preemptive = [](SchedStrategy sc) {
1120 return sc == SchedStrategy::FCFSPRPRIO || sc == SchedStrategy::FCFSPIPRIO ||
1121 sc == SchedStrategy::LCFSPRPRIO || sc == SchedStrategy::LCFSPIPRIO;
1123 auto is_ps = [](SchedStrategy sc) {
1124 return sc == SchedStrategy::PS || sc == SchedStrategy::PSPRIO;
1131 auto prio_enqueue = [](std::deque<std::size_t>& q, std::size_t id,
1132 const std::function<int(std::size_t)>& prio_of,
bool lifo,
1133 const std::function<double(std::size_t)>* key_of) {
1134 const int prio = prio_of(
id);
1135 const double key = key_of ? (*key_of)(id) : 0.0;
1136 std::deque<std::size_t>::iterator it = q.end();
1137 while (it != q.begin()) {
1138 const std::size_t o = *(it - 1);
1139 const int po = prio_of(o);
1140 if (po > prio)
break;
1142 if (!lifo && (!key_of || (*key_of)(o) <= key))
break;
1143 if (lifo && key_of && (*key_of)(o) > key)
break;
1154 auto enqueue = [&](std::deque<std::size_t>& q, std::size_t id, SchedStrategy sc,
1155 const std::function<int(std::size_t)>& prio_of,
double& key, Rng& g_siro,
1156 const std::function<double(std::size_t)>* key_of) {
1157 if (fcfs_prio(sc) || lcfs_prio(sc)) {
1158 prio_enqueue(q,
id, prio_of, lcfs_prio(sc), key_of);
1159 }
else if (sc == SchedStrategy::LCFS) {
1162 if (sc == SchedStrategy::SIRO) key = uniform01(g_siro);
1167 auto dequeue = [&](std::deque<std::size_t>& q, SchedStrategy sc,
1168 const std::function<double(std::size_t)>& key_of) -> std::size_t {
1169 std::deque<std::size_t>::iterator best = q.begin();
1170 if (sc == SchedStrategy::SIRO)
1171 for (std::deque<std::size_t>::iterator it = q.begin(); it != q.end(); ++it)
1172 if (key_of(*it) < key_of(*best)) best = it;
1173 const std::size_t
id = *best;
1177 const std::function<double(std::size_t)> line_key = [&](std::size_t lid) {
1178 return lines[lid].siro_key;
1180 const std::function<double(std::size_t)> inv_key = [&](std::size_t vi) {
1181 return invs[vi].siro_key;
1183 const std::function<double(std::size_t)> host_key = [&](std::size_t lid) {
1184 return lines[lid].host_t0;
1193 std::vector<std::vector<std::size_t> > ps_jobs(nhost_slots);
1194 std::vector<double> ps_last(nhost_slots, 0.0);
1195 std::vector<std::uint64_t> ps_gen(nhost_slots, 0);
1201 auto ps_rates = [&](std::size_t hs) -> std::vector<double> {
1202 const std::vector<std::size_t>& pj = ps_jobs[hs];
1203 const std::size_t n = pj.size();
1204 const std::size_t host = host_of_slot[hs];
1205 const double c =
static_cast<double>(host_servers[host]);
1206 if (host_sched[host] != SchedStrategy::PSPRIO ||
static_cast<double>(n) <= c)
1207 return std::vector<double>(n, n == 0 ? 0.0 : std::min(1.0, c /
static_cast<double>(n)));
1208 std::vector<double> rate(n, 0.0);
1209 std::vector<int> pr(n);
1210 for (std::size_t i = 0; i < n; ++i) pr[i] = task_prio(
lsn.parent[lines[pj[i]].act]);
1211 std::vector<int> levels(pr);
1212 std::sort(levels.begin(), levels.end(), std::greater<int>());
1213 levels.erase(std::unique(levels.begin(), levels.end()), levels.end());
1215 for (std::size_t l = 0; l < levels.size() && left > 0.0; ++l) {
1216 std::size_t cnt = 0;
1217 for (std::size_t i = 0; i < n; ++i) cnt += (pr[i] == levels[l]) ? 1 : 0;
1218 const double alloc = std::min(left,
static_cast<double>(cnt));
1219 for (std::size_t i = 0; i < n; ++i)
1220 if (pr[i] == levels[l]) rate[i] = alloc /
static_cast<double>(cnt);
1226 auto ps_advance = [&](std::size_t hs) {
1227 const double dt = now - ps_last[hs];
1229 const std::vector<double> rate = ps_rates(hs);
1230 for (std::size_t i = 0; i < ps_jobs[hs].size(); ++i) {
1231 LnLine& L = lines[ps_jobs[hs][i]];
1232 L.remaining = std::max(0.0, L.remaining - dt * rate[i]);
1238 auto ps_busy = [&](std::size_t hs, std::size_t n) ->
double {
1239 return static_cast<double>(std::min(n, host_servers[host_of_slot[hs]]));
1243 std::vector<double> tot_q(nidx + 1, 0.0), tot_u(nidx + 1, 0.0);
1244 std::vector<double> cur_q(nidx + 1, 0.0), cur_u(nidx + 1, 0.0);
1245 std::vector<double> last_upd(nidx + 1, 0.0);
1246 std::vector<double> completions(nidx + 1, 0.0), resp_sum(nidx + 1, 0.0),
1247 resp_cnt(nidx + 1, 0.0);
1251 std::vector<std::vector<double> > entry_resp_samples(
lsn.nentries);
1259 std::vector<double> act_host_resid(nidx + 1, 0.0);
1264 std::vector<Rng> g_callmult;
1265 g_callmult.reserve(
lsn.ncalls + 1);
1266 for (std::size_t c = 0; c <=
lsn.ncalls; ++c)
1267 g_callmult.push_back(
engine::ln_rng(ln_seed,
static_cast<long long>(c) * 10 + 4000));
1277 auto sample_call_count = [&](std::size_t cidx) -> std::size_t {
1278 const double m = (cidx <
lsn.callproc_mean.size())
1281 if (!(m > 0.0))
return 0;
1282 std::size_t n =
static_cast<std::size_t
>(std::floor(m));
1283 const double frac = m -
static_cast<double>(n);
1284 if (frac > 0.0 && cidx < g_callmult.size() && uniform01(g_callmult[cidx]) < frac) ++n;
1288 auto touch = [&](std::size_t k) {
1289 const double dt = now - last_upd[k];
1291 tot_q[k] += cur_q[k] * dt;
1292 tot_u[k] += cur_u[k] * dt;
1296 auto push_ev = [&](LnEvent e) {
1304 std::uint64_t done = 0;
1305 const std::uint64_t warm_events =
1306 static_cast<std::uint64_t
>(
static_cast<double>(max_events) * 0.2);
1308 double warm_t = 0.0;
1309 long long cycles = 0;
1316 auto reset_stats = [&]() {
1319 for (std::size_t k = 0; k <= nidx; ++k) {
1323 completions[k] = 0.0;
1326 act_host_resid[k] = 0.0;
1328 for (std::size_t e = 0; e < entry_resp_samples.size(); ++e) entry_resp_samples[e].clear();
1329 for (std::size_t ci = 0; ci < caches.size(); ++ci) {
1330 caches[ci].reads = 0;
1331 caches[ci].hits = 0;
1332 caches[ci].misses = 0;
1333 caches[ci].delayed = 0;
1339 auto new_line = [&](std::size_t inv, std::size_t act,
bool branch) -> std::size_t {
1341 if (!free_lines.empty()) {
1342 id = free_lines.back();
1343 free_lines.pop_back();
1344 lines[id] = LnLine();
1347 lines.push_back(LnLine());
1349 lines[id].inv = inv;
1350 lines[id].act = act;
1351 lines[id].calls_left = NOTDRAWN;
1352 lines[id].branch = branch;
1355 auto free_line = [&](std::size_t id) { free_lines.push_back(
id); };
1357 auto set_act = [&](std::size_t lid, std::size_t a) {
1358 LnLine& L = lines[lid];
1362 L.calls_left = NOTDRAWN;
1365 auto new_inv =[&](std::size_t entry, std::size_t trep, std::size_t caller,
1366 std::size_t cust) -> std::size_t {
1368 const bool pre = !warm || (caller != LN_NONE && invs[lines[caller].inv].pre);
1370 if (!free_invs.empty()) {
1371 id = free_invs.back();
1372 free_invs.pop_back();
1376 invs.push_back(LnInv());
1378 LnInv& v = invs[id];
1380 v.task =
lsn.parent[entry];
1382 v.ts = task_slot(v.task, trep);
1387 const std::size_t root = new_line(
id, bound[entry],
false);
1388 invs[id].root = root;
1396 auto enter_task = [&](std::size_t vi) {
1397 LnInv& v = invs[vi];
1400 cur_q[v.task] += 1.0;
1407 auto arrive_at_task = [&](std::size_t vi) ->
bool {
1408 LnInv& v = invs[vi];
1409 const int col = lincon_col(v.task, v.entry);
1412 if (!admits(v.task, tocc[v.ts], col)) {
1413 tadm[v.ts].push_back(vi);
1416 ++tocc[v.ts][
static_cast<std::size_t
>(col)];
1423 std::function<void(std::size_t)> advance;
1425 auto drain = [&]() {
1426 while (!runnable.empty()) {
1427 const std::size_t
id = runnable.front();
1428 runnable.pop_front();
1434 std::function<void(std::size_t)> advance_pool;
1435 auto pool_rates = [&](std::size_t hs) {
1436 const std::size_t h = host_of_slot[hs];
1437 const std::vector<std::size_t>& jobs = pool_jobs[hs];
1438 std::vector<double> rate(jobs.size(), 0.0);
1439 for (std::size_t p = 0; p < pool_capacity[h].size(); ++p)
1440 for (std::size_t i = 0; i < jobs.size(); ++i) {
1441 const int j = lines[jobs[i]].pcol;
1442 if (j >= 0 &&
static_cast<std::size_t
>(j) < pool_serves[h][p].size() &&
1443 pool_serves[h][p][
static_cast<std::size_t
>(j)]) {
1444 rate[i] += pool_capacity[h][p];
1451 advance_pool = [&](std::size_t hs) {
1452 const std::vector<double> r = pool_rates(hs);
1453 const double dt = now - pool_last[hs];
1454 for (std::size_t i = 0; i < pool_jobs[hs].size(); ++i) {
1455 LnLine& L = lines[pool_jobs[hs][i]];
1456 L.remaining = std::max(0.0, L.remaining - dt * r[i]);
1458 pool_last[hs] = now;
1465 auto rearm_pool = [&](std::size_t hs) {
1466 const std::size_t h = host_of_slot[hs];
1467 const std::vector<double> r = pool_rates(hs);
1468 double tot = 0.0, tmin = std::numeric_limits<double>::infinity();
1469 for (std::size_t i = 0; i < r.size(); ++i) {
1471 if (r[i] > 0.0) tmin = std::min(tmin, lines[pool_jobs[hs][i]].remaining / r[i]);
1474 pool_rate[hs] = tot;
1476 for (std::size_t m = 0; m < repl[h]; ++m) u += pool_rate[host_slot0[h] + m];
1478 const std::uint64_t g = ++pool_gen[hs];
1479 if (std::isfinite(tmin)) {
1489 auto ps_schedule = [&](std::size_t hs) {
1491 if (ps_jobs[hs].empty())
return;
1492 const std::vector<double> rate = ps_rates(hs);
1493 double tmin = std::numeric_limits<double>::infinity();
1494 for (std::size_t i = 0; i < ps_jobs[hs].size(); ++i)
1495 if (rate[i] > 0.0) tmin = std::min(tmin, lines[ps_jobs[hs][i]].remaining / rate[i]);
1505 std::vector<std::vector<std::size_t> > in_service(nhost_slots);
1506 std::uint64_t host_gen_ctr = 0;
1508 std::uint64_t fork_ctr = 0;
1513 auto begin_host = [&](std::size_t lid, std::size_t hs) {
1514 LnLine& L = lines[lid];
1515 if (L.host_rem < 0.0) L.host_rem = hostdem[L.act].next(g_host[L.act]);
1517 L.host_end = now + L.host_rem;
1518 if (preemptive(host_sched[host_of_slot[hs]])) in_service[hs].push_back(lid);
1519 L.host_gen = ++host_gen_ctr;
1534 auto preempt_for = [&](std::size_t lid, std::size_t hs) ->
bool {
1535 const SchedStrategy sc = host_sched[host_of_slot[hs]];
1536 const bool lifo = lcfs_prio(sc);
1537 const int pa = host_req_prio(lid);
1538 std::vector<std::size_t>& ins = in_service[hs];
1539 std::size_t vi = ins.size();
1541 for (std::size_t i = 0; i < ins.size(); ++i) {
1542 const int pr = host_req_prio(ins[i]);
1543 if (pr >= pa)
continue;
1544 bool better = vi == ins.size() || pr < pv;
1545 if (!better && pr == pv) {
1546 const LnLine& r = lines[ins[i]];
1547 const LnLine& v = lines[ins[vi]];
1548 better = lifo ? r.host_t0 < v.host_t0 : r.host_start > v.host_start;
1555 if (vi == ins.size() && lifo)
1556 for (std::size_t i = 0; i < ins.size(); ++i)
1557 if (host_req_prio(ins[i]) == pa &&
1558 (vi == ins.size() || lines[ins[i]].host_start < lines[ins[vi]].host_start))
1560 if (vi == ins.size())
return false;
1561 const std::size_t victim = ins[vi];
1562 ins.erase(ins.begin() +
static_cast<std::ptrdiff_t
>(vi));
1563 LnLine& v = lines[victim];
1564 v.host_gen = ++host_gen_ctr;
1565 const bool resume = sc == SchedStrategy::FCFSPRPRIO || sc == SchedStrategy::LCFSPRPRIO;
1566 v.host_rem = resume ? std::max(0.0, v.host_end - now) : -1.0;
1567 enqueue(host_queue[hs], victim, sc, host_req_prio, v.siro_key, g_siro_host, &host_key);
1568 begin_host(lid, hs);
1572 auto start_host_service = [&](std::size_t lid) {
1573 LnLine& L = lines[lid];
1574 const std::size_t act = L.act;
1575 const std::size_t host =
lsn.host_of(act);
1576 const std::size_t hs = host_slot(host, invs[L.inv].trep);
1581 const std::size_t t =
lsn.parent[act];
1582 const std::map<std::size_t, int>::const_iterator it = pool_col[host].find(t);
1583 if (it == pool_col[host].end())
1584 throw InputError(
"SolverLDES (native LN engine): activity '" +
lsn.names[act] +
1585 "' runs on a host whose pool declaration does not name its "
1586 "task, so no server can be assigned");
1587 lines[lid].pcol = it->second;
1588 lines[lid].remaining = hostdem[act].next(g_host[act]);
1589 pool_jobs[hs].push_back(lid);
1593 lines[lid].host_rem = -1.0;
1594 if (is_ps(host_sched[host])) {
1597 const std::size_t n0 = ps_jobs[hs].size();
1598 lines[lid].remaining = hostdem[act].next(g_host[act]);
1599 ps_jobs[hs].push_back(lid);
1601 cur_u[host] += ps_busy(hs, n0 + 1) - ps_busy(hs, n0);
1605 if (host_busy[hs] < host_servers[host]) {
1608 begin_host(lid, hs);
1609 }
else if (!(preemptive(host_sched[host]) && preempt_for(lid, hs))) {
1610 enqueue(host_queue[hs], lid, host_sched[host], host_req_prio, lines[lid].siro_key,
1611 g_siro_host, &host_key);
1620 auto request_host = [&](std::size_t lid) {
1621 LnLine& L = lines[lid];
1622 const std::size_t act = L.act;
1623 const std::size_t host =
lsn.host_of(act);
1624 const std::size_t hs = host_slot(host, invs[L.inv].trep);
1626 const int col = lincon_col(host,
lsn.parent[act]);
1629 if (!admits(host, hocc[hs], col)) {
1630 hadm[hs].push_back(lid);
1633 ++hocc[hs][
static_cast<std::size_t
>(col)];
1635 start_host_service(lid);
1638 auto release_host_admission = [&](std::size_t lid) {
1639 LnLine& L = lines[lid];
1640 if (L.hcol < 0)
return;
1641 const std::size_t host =
lsn.host_of(L.act);
1642 const std::size_t hs = host_slot(host, invs[L.inv].trep);
1643 --hocc[hs][
static_cast<std::size_t
>(L.hcol)];
1645 while (!hadm[hs].empty()) {
1646 const std::size_t head = hadm[hs].front();
1647 const int c = lines[head].hcol;
1648 if (!admits(host, hocc[hs], c))
return;
1649 hadm[hs].pop_front();
1650 ++hocc[hs][
static_cast<std::size_t
>(c)];
1651 start_host_service(head);
1656 auto start_setup = [&](std::size_t ts, std::size_t th) {
1657 const std::size_t task = thr_owner[ts];
1658 thr_state[ts][th] = ThreadState::SETUP;
1659 thr_setup_t0[ts][th] = now;
1661 e.t = now + setupd[task].next(g_setup[task]);
1665 e.gen = ++thr_gen[ts][th];
1670 auto start_delayoff = [&](std::size_t ts, std::size_t th) {
1671 const std::size_t task = thr_owner[ts];
1672 thr_state[ts][th] = ThreadState::DELAYOFF;
1674 e.t = now + delayoffd[task].next(g_doff[task]);
1678 e.gen = ++thr_gen[ts][th];
1691 auto acquire_thread = [&](std::size_t vi) ->
bool {
1692 LnInv& v = invs[vi];
1693 const std::size_t task = v.task;
1694 const std::size_t ts = v.ts;
1696 if (task_threads[task] == std::numeric_limits<std::size_t>::max()) {
1698 v.has_thread =
true;
1702 std::vector<bool>& held = thr_held[ts];
1703 std::vector<ThreadState>& st = thr_state[ts];
1707 for (std::size_t th = 0; th < held.size(); ++th) {
1708 if (!held[th] && st[th] == ThreadState::ACTIVE) {
1711 v.has_thread =
true;
1716 for (std::size_t th = 0; th < held.size(); ++th) {
1717 if (!held[th] && st[th] == ThreadState::DELAYOFF) {
1719 st[th] = ThreadState::ACTIVE;
1722 v.has_thread =
true;
1727 enqueue(thr_queue[ts], vi, task_sched[task], task_req_prio, invs[vi].siro_key, g_siro_task,
1729 for (std::size_t th = 0; th < held.size(); ++th) {
1730 if (!held[th] && st[th] == ThreadState::OFF) {
1731 start_setup(ts, th);
1739 auto release_thread = [&](std::size_t vi) {
1740 LnInv& v = invs[vi];
1741 const std::size_t ts = v.ts;
1742 const std::size_t th = v.th;
1743 const std::size_t task = thr_owner[ts];
1746 v.has_thread =
false;
1748 if (th == NOTHR)
return;
1749 thr_held[ts][th] =
false;
1750 if (!thr_queue[ts].empty()) {
1751 const std::size_t nxt = dequeue(thr_queue[ts], task_sched[task], inv_key);
1752 thr_held[ts][th] =
true;
1754 invs[nxt].has_thread =
true;
1756 runnable.push_back(invs[nxt].root);
1757 }
else if (has_setup[task]) {
1758 start_delayoff(ts, th);
1773 auto finish_act = [&](LnLine& L) {
1774 const std::size_t act = L.act;
1775 const bool seen = !invs[L.inv].pre;
1776 if (seen) completions[act] += 1.0;
1777 if (L.act_t0 >= 0.0) {
1779 resp_sum[act] += now - L.act_t0;
1780 resp_cnt[act] += 1.0;
1799 auto send_reply = [&](std::size_t vi,
bool direct) {
1800 LnInv& v = invs[vi];
1801 if (v.replied)
return;
1803 v.early_reply = !direct;
1805 completions[v.entry] += 1.0;
1806 const double r = now - v.entry_t0;
1807 resp_sum[v.entry] += r;
1808 resp_cnt[v.entry] += 1.0;
1812 if (v.entry >
lsn.eshift && v.entry <=
lsn.eshift +
lsn.nentries)
1813 entry_resp_samples[v.entry -
lsn.eshift - 1].push_back(r);
1819 if (v.caller != LN_NONE && !fwd_dst[v.entry].empty()) {
1820 const double u = uniform01(g_fwd[v.entry]);
1822 for (std::size_t k = 0; k < fwd_dst[v.entry].size(); ++k) {
1823 cum += fwd_prob[v.entry][k];
1825 const std::size_t tgt = fwd_dst[v.entry][k];
1826 const std::size_t caller = v.caller;
1828 const std::size_t rep = callee_replica(v.task, v.trep,
lsn.parent[tgt]);
1830 const std::size_t child = new_inv(tgt, rep, caller, LN_NONE);
1831 if (arrive_at_task(child)) runnable.push_back(invs[child].root);
1836 if (!direct && v.caller != LN_NONE) runnable.push_back(v.caller);
1841 auto cache_insert = [&](std::size_t ci, std::size_t item) {
1842 LnCacheState& cs = caches[ci];
1843 std::list<std::size_t>& lst = cs.levels[0];
1844 const std::size_t cap =
static_cast<std::size_t
>(std::max(0, cs.cap[0]));
1846 if (lst.size() >= cap && !lst.empty()) {
1847 std::size_t r =
static_cast<std::size_t
>(uniform01(g_rr[ci]) *
1848 static_cast<double>(lst.size()));
1849 r = std::min(r, lst.size() - 1);
1850 std::list<std::size_t>::iterator it = lst.begin();
1851 std::advance(it,
static_cast<long>(r));
1854 lst.push_front(item);
1857 lst.push_front(item);
1858 if (lst.size() > cap) lst.pop_back();
1862 auto cache_promote = [&](std::size_t ci, std::size_t item, std::size_t level) {
1863 LnCacheState& cs = caches[ci];
1865 const std::size_t inew = std::min(level + 1, cs.levels.size() - 1);
1869 if (inew <= level) {
1871 cs.levels[level].remove(item);
1872 cs.levels[level].push_front(item);
1876 std::list<std::size_t>& lo = cs.levels[level];
1877 std::list<std::size_t>& hi = cs.levels[inew];
1878 std::size_t kpos = 0;
1879 for (std::list<std::size_t>::iterator it = lo.begin(); it != lo.end(); ++it, ++kpos)
1880 if (*it == item)
break;
1882 if (hi.size() <
static_cast<std::size_t
>(std::max(0, cs.cap[inew]))) {
1883 hi.push_front(item);
1886 std::size_t demoted;
1888 std::size_t r =
static_cast<std::size_t
>(uniform01(g_rr[ci]) *
1889 static_cast<double>(hi.size()));
1890 r = std::min(r, hi.size() - 1);
1891 std::list<std::size_t>::iterator it = hi.begin();
1892 std::advance(it,
static_cast<long>(r));
1896 demoted = hi.back();
1898 hi.push_front(item);
1901 lo.push_front(demoted);
1903 std::list<std::size_t>::iterator it = lo.begin();
1904 std::advance(it,
static_cast<long>(std::min(kpos, lo.size())));
1905 lo.insert(it, demoted);
1908 static const std::size_t HELD =
static_cast<std::size_t
>(-2);
1915 auto access_cache = [&](std::size_t ai, std::size_t lid) -> std::size_t {
1916 const LnCacheAccess& ca = accesses[ai];
1917 LnInv& v = invs[lines[lid].inv];
1918 const std::size_t ci = ca.caches[std::min(v.trep, ca.caches.size() - 1)];
1919 LnCacheState& cs = caches[ci];
1921 const bool seen = !v.pre;
1922 if (seen) ++cs.reads;
1923 const double u = uniform01(g_item[ci]);
1924 std::size_t item = ca.cdf.size() - 1;
1925 for (std::size_t i = 0; i < ca.cdf.size(); ++i)
1926 if (u <= ca.cdf[i]) {
1930 for (std::size_t l = 0; l < cs.levels.size(); ++l)
1931 if (std::find(cs.levels[l].begin(), cs.levels[l].end(), item) != cs.levels[l].end()) {
1932 if (seen) ++cs.hits;
1933 cache_promote(ci, item, l);
1937 if (item < cs.inflight.size() && cs.inflight[item]) {
1938 cs.held[item].push_back(std::make_pair(lid, ai));
1941 if (seen) ++cs.misses;
1942 cs.inflight[item] = 1;
1945 v.fetch_item = item;
1948 if (seen) ++cs.misses;
1949 cache_insert(ci, item);
1953 auto release_fetch = [&](std::size_t vi) {
1954 LnInv& v = invs[vi];
1955 if (!v.fetching)
return;
1957 LnCacheState& cs = caches[v.fetch_cache];
1958 const std::size_t item = v.fetch_item;
1959 cs.inflight[item] = 0;
1960 cache_insert(v.fetch_cache, item);
1961 std::vector<std::pair<std::size_t, std::size_t> > held;
1962 held.swap(cs.held[item]);
1965 for (std::size_t k = 0; k < held.size(); ++k) {
1966 if (!invs[lines[held[k].first].inv].pre) ++caches[v.fetch_cache].delayed;
1967 set_act(held[k].first, accesses[held[k].second].hit);
1968 runnable.push_back(held[k].first);
1979 auto complete_request = [&](std::size_t vi) -> std::size_t {
1980 send_reply(vi,
true);
1982 LnInv& v = invs[vi];
1984 cur_q[v.entry] -= 1.0;
1985 const bool seen = !v.pre;
1986 if (seen) completions[v.task] += 1.0;
1988 cur_q[v.task] -= 1.0;
1991 const std::size_t ts = v.ts, task = v.task;
1992 --tocc[ts][
static_cast<std::size_t
>(v.tcol)];
1994 while (!tadm[ts].empty()) {
1995 const std::size_t head = tadm[ts].front();
1996 const int c = invs[head].tcol;
1997 if (!admits(task, tocc[ts], c))
break;
1998 tadm[ts].pop_front();
1999 ++tocc[ts][
static_cast<std::size_t
>(c)];
2001 runnable.push_back(invs[head].root);
2004 LnInv& w = invs[vi];
2005 const std::size_t caller = w.caller, cust = w.cust;
2006 const bool early = w.early_reply;
2008 free_invs.push_back(vi);
2009 if (cust != LN_NONE) {
2013 const std::size_t rt = custs[cust].ref_task;
2015 resp_sum[rt] += now - custs[cust].t_start;
2016 resp_cnt[rt] += 1.0;
2020 e.t = now + (has_think[rt] ? think[rt].next(g_think[rt]) : 0.0);
2026 return (caller != LN_NONE && !early) ? caller : LN_NONE;
2030 auto finish_line = [&](std::size_t lid) -> std::size_t {
2031 const std::size_t vi = lines[lid].inv;
2032 LnInv& v = invs[vi];
2033 const bool br = lines[lid].branch;
2034 if (v.pending < 0)
return complete_request(vi);
2035 const long rem = v.pending - 1;
2036 if (br) free_line(lid);
2038 invs[vi].pending = -1;
2039 return complete_request(vi);
2041 invs[vi].pending = rem;
2053 auto complete_activity = [&](std::size_t lid) -> std::size_t {
2054 finish_act(lines[lid]);
2058 if (!warm && done >= warm_events) reset_stats();
2059 const std::size_t act = lines[lid].act;
2060 const std::size_t vi = lines[lid].inv;
2061 if (access_of_driver[act] != LN_NONE) {
2062 const std::size_t nx = access_cache(access_of_driver[act], lid);
2063 if (nx == HELD)
return LN_NONE;
2067 const std::vector<std::size_t>& sc = succ_act[act];
2068 const bool br = lines[lid].branch;
2071 const std::size_t j = sc[0];
2072 const std::uint64_t fid = lines[lid].fork_id;
2076 LnJoin& jn = invs[vi].joins[j];
2077 std::map<std::pair<std::size_t, std::uint64_t>,
long>::iterator it =
2078 jn.discard.find(std::make_pair(act, fid));
2079 if (it == jn.discard.end()) it = jn.discard.find(std::make_pair(act, LN_ANYFORK));
2080 if (it != jn.discard.end()) {
2081 if (--it->second <= 0) jn.discard.erase(it);
2082 if (jn.got.empty() && jn.discard.empty()) invs[vi].joins.erase(j);
2083 return finish_line(lid);
2086 LnJoin& jn = invs[vi].joins[j];
2088 if (br) free_line(lid);
2089 const std::size_t req = join_quorum[j] > 0 ? join_quorum[j] : 1;
2090 if (jn.got.size() >= req) {
2093 std::uint64_t rf = jn.got.begin()->second;
2094 for (
const std::pair<const std::size_t, std::uint64_t>& g : jn.got)
2095 if (g.second != rf) rf = LN_ANYFORK;
2096 for (std::size_t in : join_inputs[j])
2097 if (jn.got.find(in) == jn.got.end()) ++jn.discard[std::make_pair(in, rf)];
2099 if (jn.discard.empty()) invs[vi].joins.erase(j);
2100 if (invs[vi].pending >= 0) invs[vi].pending -=
static_cast<long>(req - 1);
2101 set_act(invs[vi].root, j);
2102 return invs[vi].root;
2106 if (!invs[vi].replied && !br) {
2107 bool reply = branch_reply[act] != 0 && branch_reply[act] == invs[vi].entry;
2108 if (!reply && phase_of(act) == 1)
2109 for (std::size_t s : sc)
2110 if (phase_of(s) == 2) {
2117 if (!reply && sc.empty() && invs[vi].pending > 1) reply =
true;
2118 if (reply) send_reply(vi,
false);
2120 if (invs[vi].replied && invs[vi].fetching) release_fetch(vi);
2121 if (sc.empty())
return finish_line(lid);
2122 if (sc.size() == 1) {
2123 set_act(lid, sc[0]);
2126 if (!or_prob[act].empty()) {
2127 const double u = uniform01(g_route[act]);
2129 std::size_t pick = sc.back();
2130 for (std::size_t k = 0; k < sc.size(); ++k) {
2131 cum += or_prob[act][k];
2142 invs[vi].pending = (invs[vi].pending < 0 ? 1 : invs[vi].pending) +
2143 static_cast<long>(sc.size()) - 1;
2144 if (br) free_line(lid);
2145 const std::uint64_t fork = ++fork_ctr;
2146 std::vector<std::size_t> kids;
2147 for (std::size_t k = 0; k < sc.size(); ++k) {
2148 kids.push_back(new_line(vi, sc[k],
true));
2149 lines[kids.back()].fork_id = fork;
2151 for (std::size_t k = kids.size(); k-- > 1;) runnable.push_front(kids[k]);
2159 auto spawn_request = [&](std::size_t entry, std::size_t rep,
bool pre) {
2160 const std::size_t vi = new_inv(entry, rep, LN_NONE, LN_NONE);
2162 if (pre) invs[vi].pre =
true;
2163 if (arrive_at_task(vi)) runnable.push_back(invs[vi].root);
2170 advance = [&](std::size_t lid) {
2171 while (lid != LN_NONE) {
2172 const std::size_t vi = lines[lid].inv;
2175 if (!invs[vi].has_thread) {
2176 if (!acquire_thread(vi))
return;
2180 if (!invs[vi].started) {
2181 LnInv& v = invs[vi];
2185 cur_q[v.entry] += 1.0;
2187 LnLine& L = lines[lid];
2188 const std::size_t act = L.act;
2190 lid = complete_request(vi);
2193 if (L.act_t0 < 0.0) {
2201 if (L.dem_done == 0) {
2203 if (has_hostdem[act]) {
2210 if (L.dem_done == 1) {
2212 if (has_actthink[act]) {
2214 e.t = now + actthink[act].next(g_actthink[act]);
2221 const std::vector<std::size_t>& calls =
2222 (act <
lsn.callsof.size()) ?
lsn.callsof[act] : std::vector<std::size_t>();
2223 if (L.call_pos < calls.size()) {
2226 const std::size_t cidx = calls[L.call_pos];
2227 if (L.calls_left == NOTDRAWN) L.calls_left = sample_call_count(cidx);
2228 if (L.calls_left == 0) {
2230 L.calls_left = NOTDRAWN;
2234 if (L.calls_left == 0) {
2236 L.calls_left = NOTDRAWN;
2238 const std::size_t callee =
lsn.callpair_dst[cidx];
2239 const std::size_t rep =
2240 callee_replica(invs[vi].task, invs[vi].trep,
lsn.parent[callee]);
2244 const std::size_t child = new_inv(callee, rep, lid, LN_NONE);
2245 if (!arrive_at_task(child))
return;
2246 lid = invs[child].root;
2251 spawn_request(callee, rep, invs[vi].pre);
2257 L.calls_left = NOTDRAWN;
2258 lid = complete_activity(lid);
2265 auto start_cycle = [&](std::size_t c) {
2266 const std::size_t t = custs[c].ref_task;
2267 custs[c].t_start = now;
2268 const std::size_t vi = new_inv(
lsn.entriesof[t][0], custs[c].repl, LN_NONE, c);
2269 if (arrive_at_task(vi)) advance(invs[vi].root);
2271 bool any_ref =
false;
2272 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
2273 if (t >=
lsn.isref.size() || !
lsn.isref[t])
continue;
2275 const std::size_t n = (task_threads[t] == std::numeric_limits<std::size_t>::max())
2278 const std::vector<std::size_t>& es =
2279 (t <
lsn.entriesof.size()) ?
lsn.entriesof[t] : std::vector<std::size_t>();
2281 throw InputError(
"SolverLDES (native LN engine): reference task '" +
lsn.names[t] +
2282 "' declares no entry to invoke");
2285 for (std::size_t m = 0; m < repl[t]; ++m) {
2286 for (std::size_t k = 0; k < n; ++k) {
2290 custs.push_back(cu);
2291 start_cycle(custs.size() - 1);
2298 std::vector<Sampler> arrival(nidx + 1);
2299 std::vector<Rng> g_arr;
2300 g_arr.reserve(nidx + 1);
2301 for (std::size_t k = 0; k <= nidx; ++k)
2302 g_arr.push_back(
engine::ln_rng(ln_seed,
static_cast<long long>(k) * 10 + 5000));
2303 for (std::size_t e =
lsn.eshift + 1; e <=
lsn.eshift +
lsn.nentries; ++e) {
2304 if (e >=
lsn.has_arrival.size() || !
lsn.has_arrival[e] || e >=
lsn.arrival.size() ||
2305 lsn.arrival[e].disabled)
2307 arrival[e] = Sampler(
lsn.arrival[e],
"the open arrivals at '" +
lsn.names[e] +
"'");
2308 for (std::size_t m = 0; m < repl[
lsn.parent[e]]; ++m) {
2310 ev.t = now + arrival[e].next(g_arr[e]);
2319 throw InputError(
"SolverLDES (native LN engine): the model has no reference task, so "
2320 "nothing drives it");
2323 while (!evq.empty() && done < max_events) {
2324 const LnEvent ev = evq.top();
2331 start_cycle(ev.who);
2336 if (ev.kind == 2 || ev.kind == 3) {
2337 const std::size_t ts = ev.who, th = ev.aux;
2338 if (ev.gen != thr_gen[ts][th])
continue;
2340 if (thr_state[ts][th] == ThreadState::DELAYOFF)
2341 thr_state[ts][th] = ThreadState::OFF;
2346 thr_state[ts][th] = ThreadState::ACTIVE;
2347 if (!thr_held[ts][th] && !thr_queue[ts].empty()) {
2348 const std::size_t nxt = dequeue(thr_queue[ts], task_sched[thr_owner[ts]], inv_key);
2349 thr_held[ts][th] =
true;
2350 touch(thr_owner[ts]);
2351 cur_u[thr_owner[ts]] += 1.0;
2352 invs[nxt].has_thread =
true;
2359 if (!invs[nxt].started) {
2360 LnInv& w = invs[nxt];
2362 w.entry_t0 = std::max(w.task_t0, thr_setup_t0[ts][th]);
2365 tot_q[w.entry] += now - std::max(w.entry_t0, warm_t);
2366 cur_q[w.entry] += 1.0;
2368 advance(invs[nxt].root);
2376 const std::size_t hs = ev.who;
2377 if (ev.gen != pool_gen[hs])
continue;
2378 const std::size_t host = host_of_slot[hs];
2380 std::vector<std::size_t> finished, keep;
2381 for (std::size_t lid : pool_jobs[hs])
2382 (lines[lid].remaining <= 1e-9 ? finished : keep).push_back(lid);
2383 pool_jobs[hs].swap(keep);
2385 for (std::size_t lid : finished) {
2388 if (!invs[lines[lid].inv].pre) {
2389 completions[host] += 1.0;
2390 act_host_resid[lines[lid].act] += now - lines[lid].host_t0;
2392 release_host_admission(lid);
2401 const std::size_t hs = ev.who;
2402 if (ev.gen != ps_gen[hs])
continue;
2403 const std::size_t host = host_of_slot[hs];
2405 std::vector<std::size_t> finished, keep;
2406 for (std::size_t lid : ps_jobs[hs])
2407 (lines[lid].remaining <= 1e-9 ? finished : keep).push_back(lid);
2408 const std::size_t n0 = ps_jobs[hs].size();
2409 ps_jobs[hs].swap(keep);
2411 cur_u[host] -= ps_busy(hs, n0) - ps_busy(hs, ps_jobs[hs].size());
2413 for (std::size_t lid : finished) {
2416 if (!invs[lines[lid].inv].pre) {
2417 completions[host] += 1.0;
2418 act_host_resid[lines[lid].act] += now - lines[lid].host_t0;
2420 release_host_admission(lid);
2436 spawn_request(ev.who, ev.aux,
false);
2438 nx.t = now + arrival[ev.who].next(g_arr[ev.who]);
2445 const std::size_t lid = ev.who;
2446 if (ev.gen != lines[lid].host_gen)
continue;
2447 const std::size_t act = lines[lid].act;
2448 const std::size_t host =
lsn.host_of(act);
2451 const std::size_t hs = host_slot(host, invs[lines[lid].inv].trep);
2456 if (!invs[lines[lid].inv].pre) {
2457 completions[host] += 1.0;
2458 act_host_resid[act] += now - lines[lid].host_t0;
2460 if (preemptive(host_sched[host]))
2461 in_service[hs].erase(std::find(in_service[hs].begin(), in_service[hs].end(), lid));
2462 if (!host_queue[hs].empty()) {
2463 const std::size_t nxt = dequeue(host_queue[hs], host_sched[host], line_key);
2466 begin_host(nxt, hs);
2468 release_host_admission(lid);
2478 if (done < max_events && evq.empty()) {
2479 for (std::size_t s = 0; s < nhost_slots; ++s)
2480 if (!hadm[s].empty())
2481 throw InputError(
"SolverLDES (native LN engine): the admission constraint on "
2482 "host '" +
lsn.names[host_of_slot[s]] +
"' deadlocked the model: " +
2483 std::to_string(hadm[s].size()) +
2484 " request(s) are blocked with no event left");
2485 for (std::size_t s = 0; s < ntask_slots; ++s)
2486 if (!tadm[s].empty())
2487 throw InputError(
"SolverLDES (native LN engine): the admission constraint on "
2488 "task '" +
lsn.names[thr_owner[s]] +
"' deadlocked the model: " +
2489 std::to_string(tadm[s].size()) +
2490 " request(s) are blocked with no event left");
2494 for (std::size_t k = 1; k <= nidx; ++k) touch(k);
2496 const double span = now - warm_t;
2506 res.WLN =
Matrix<double>(nidx + 1, 1, std::numeric_limits<double>::quiet_NaN());
2507 for (std::size_t k = 1; k <= nidx; ++k) {
2509 res.QLN(k, 0) = tot_q[k] / span;
2522 const bool is_task = (k >
lsn.tshift && k <=
lsn.tshift +
lsn.ntasks);
2523 const std::size_t cap_k = is_task ? task_threads[k] : host_servers[k];
2524 const double c = (cap_k == std::numeric_limits<std::size_t>::max())
2526 :
static_cast<double>(is_task ? cap_k * repl[k] : cap_k);
2527 res.ULN(k, 0) = tot_u[k] / (span * c);
2528 if (is_task) res.UThreadLN(k, 0) = res.ULN(k, 0);
2529 res.TLN(k, 0) = completions[k] / span;
2531 if (resp_cnt[k] > 0.0) res.RLN(k, 0) = resp_sum[k] / resp_cnt[k];
2533 res.entry_resp_samples.swap(entry_resp_samples);
2549 std::vector<double> task_resid(nidx + 1, 0.0);
2550 std::vector<bool> task_has_act(nidx + 1,
false);
2551 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts && a <= nidx; ++a) {
2552 const std::size_t task =
lsn.parent[a];
2553 const double x_task = (task >= 1 && task <= nidx) ? res.TLN(task, 0) : 0.0;
2554 const double resid = (x_task > 0.0) ? act_host_resid[a] / (span * x_task) : 0.0;
2555 res.WLN(a, 0) = resid;
2556 if (task >= 1 && task <= nidx) {
2557 task_resid[task] += resid;
2558 task_has_act[task] =
true;
2561 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks && t <= nidx; ++t)
2562 if (task_has_act[t]) res.WLN(t, 0) = task_resid[t];
2581 auto host_mult = [&](std::size_t k) ->
double {
2582 std::size_t cur = k;
2583 for (std::size_t hops = 0; hops <= nidx; ++hops) {
2584 if (cur < 1 || cur > nidx)
return 1.0;
2585 if (cur >
lsn.hshift && cur <=
lsn.hshift +
lsn.nhosts) {
2586 const double m = cur <
lsn.mult.size() ?
lsn.mult[cur] : 1.0;
2587 return (m > 0.0 && std::isfinite(m)) ? m : 1.0;
2589 const std::size_t p = cur <
lsn.parent.size() ?
lsn.parent[cur] : 0;
2590 if (p == 0 || p == cur)
return 1.0;
2595 std::vector<double> proc_util(nidx + 1, 0.0), task_proc_util(nidx + 1, 0.0);
2596 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts && a <= nidx; ++a) {
2597 const double d = (a <
lsn.hostdem.size() && !
lsn.hostdem[a].disabled)
2600 proc_util[a] = res.TLN(a, 0) * d;
2601 const std::size_t task =
lsn.parent[a];
2602 if (task >= 1 && task <= nidx) task_proc_util[task] += proc_util[a];
2604 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks && t <= nidx; ++t)
2605 res.ULN(t, 0) = task_proc_util[t] / host_mult(t);
2606 for (std::size_t e =
lsn.eshift + 1; e <=
lsn.eshift +
lsn.nentries && e <= nidx; ++e) {
2608 const std::vector<std::size_t>& acts =
2609 (e <
lsn.actsof.size()) ?
lsn.actsof[e] : std::vector<std::size_t>();
2610 for (std::size_t i = 0; i < acts.size(); ++i)
2611 if (acts[i] <= nidx) u += proc_util[acts[i]];
2612 res.ULN(e, 0) = u / host_mult(e);
2614 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts && a <= nidx; ++a) {
2615 const std::size_t task =
lsn.parent[a];
2616 const double tu = (task >= 1 && task <= nidx) ? task_proc_util[task] : 0.0;
2618 if (tu > 0.0 && tu < 1.0)
2619 ua = std::min(1.0, proc_util[a] / tu);
2621 ua = (proc_util[a] > 0.0) ? 1.0 : 0.0;
2622 res.ULN(a, 0) = ua / host_mult(a);
2627 for (std::size_t ai = 0; ai < accesses.size(); ++ai) {
2628 long long reads = 0, hits = 0, misses = 0, delayed = 0;
2629 for (std::size_t ci : accesses[ai].caches) {
2630 reads += caches[ci].reads;
2631 hits += caches[ci].hits;
2632 misses += caches[ci].misses;
2633 delayed += caches[ci].delayed;
2635 if (reads <= 0 || !(span > 0.0))
continue;
2636 const double n =
static_cast<double>(reads);
2637 res.cache_task.push_back(
lsn.parent[accesses[ai].entry]);
2638 res.cache_entry.push_back(accesses[ai].entry);
2639 res.cache_hit.push_back(
static_cast<double>(hits) / n);
2640 res.cache_miss.push_back(
static_cast<double>(misses) / n);
2641 res.cache_delayed.push_back(
static_cast<double>(delayed) / n);
2642 res.cache_read_rate.push_back(n / span);
2645 res.simulated_time = span;
2646 res.warmup_time = warm_t;
2647 res.completions = cycles;
2648 res.events =
static_cast<long long>(done);