586 using Cand = std::pair<uint32_t, Tuple>;
587 std::deque<Cand> candidates;
588 seed([&](
const Op& op,
const Tuple& t, uint64_t) {
589 candidates.emplace_back(ops.rank(op), t);
597 const auto done = [&](
const uint64_t key) {
600 std::deque<uint64_t> keys;
601 const auto key_at = [&keys, keyed](
size_t i) -> uint64_t {
return keyed ? keys[i] : 0; };
603 keys.resize(candidates.size(), 0);
604 threading::dynamic_parallel_for(
606 std::max(1, num_threads),
608 [&](
size_t b,
size_t e) {
609 for (size_t i = b; i < e; ++i) {
610 const auto& [op, t] = candidates[i];
611 if (!t.is_valid(m)) continue;
612 keys[i] = queue_key(m, *ops.name[op], t);
613 if (keys[i] != 0) m_queued.add(keys[i]);
626 ~RestoreRenew() { slot = std::move(saved); }
630 [&next, &ops, keyed,
this, driver = restore.saved](
633 const std::vector<Tuple>& newts) -> std::vector<std::pair<Op, Tuple>> {
634 const std::vector<std::pair<Op, Tuple>> tups = driver(mm, op, newts);
635 std::vector<Cand> ranked;
636 ranked.reserve(tups.size());
637 for (
const auto& [o, t] : tups) ranked.emplace_back(ops.rank(o), t);
638 std::vector<uint64_t> ks;
640 ks.assign(tups.size(), 0);
641 for (
size_t k = 0; k < tups.size(); ++k) {
642 if (!tups[k].second.is_valid(mm))
continue;
643 ks[k] =
queue_key(mm, tups[k].first, tups[k].second);
644 if (ks[k] != 0) m_queued.add(ks[k]);
647 std::lock_guard<std::mutex> lock(next.mutex);
648 next.ops.insert(next.ops.end(), ranked.begin(), ranked.end());
649 next.keys.insert(next.keys.end(), ks.begin(), ks.end());
653 enum : uint8_t { kStale = 0, kSurvives = 1, kRefused = 2 };
654 constexpr size_t kInlineCommit = 64;
655 size_t attempts = 0, successes = 0, failures = 0, rounds = 0, committed_attempts = 0;
656 const size_t waits_before = m_waits.load();
657 using Clock = std::chrono::steady_clock;
658 Clock::duration screen_time{}, commit_time{};
659 while (!candidates.empty()) {
661 const auto screen_start = Clock::now();
663 std::vector<uint8_t> verdict(candidates.size(), kStale);
664 threading::dynamic_parallel_for(
666 std::max(1, num_threads),
668 [&](
size_t b,
size_t e) {
669 OperationDryRunScope dry;
670 for (size_t i = b; i < e; ++i) {
671 const auto& [op, t] = candidates[i];
672 if (!t.is_valid(m)) continue;
673 const Op& name = *ops.name[op];
674 if (!is_weight_up_to_date(
676 std::tuple<double, Op, Tuple>(priority(m, name, t), name, t))) {
679 verdict[i] = (*ops.fn[op])(m, t) ? kSurvives : kRefused;
687 for (
size_t i = 0; i < candidates.size(); ++i) {
688 if (verdict[i] == kSurvives) {
690 candidates[n] = std::move(candidates[i]);
697 if (verdict[i] == kRefused) {
698 on_fail(m, *ops.name[candidates[i].first], candidates[i].second);
703 std::vector<uint8_t>().swap(verdict);
704 candidates.resize(n);
705 candidates.shrink_to_fit();
708 keys.shrink_to_fit();
712 const auto commit_start = Clock::now();
713 screen_time += commit_start - screen_start;
714 const size_t committed_before = committed_attempts;
715 if (n > kInlineCommit) {
718 [&](
const Push& push) {
719 for (
size_t i = 0; i < candidates.size(); ++i) {
720 push(*ops.name[candidates[i].first], candidates[i].second, key_at(i));
724 std::deque<Cand>().swap(candidates);
725 std::deque<uint64_t>().swap(keys);
728 successes += size_t(cnt_success);
729 failures += size_t(cnt_fail);
730 attempts += size_t(cnt_success) + size_t(cnt_fail);
731 committed_attempts += size_t(cnt_success) + size_t(cnt_fail);
733 const auto inline_start = Clock::now();
740 using Elem = std::tuple<double, uint32_t, Tuple, size_t>;
741 std::priority_queue<Elem> q;
742 for (
size_t k = 0; k < n; ++k) {
743 const auto& [op, t] = candidates[k];
744 q.emplace(
priority(m, *ops.name[op], t), op, t, k);
746 for (; !q.empty(); q.pop()) {
747 const auto& [weight, op, t, k] = q.top();
748 const Op& name = *ops.name[op];
749 const uint64_t key = key_at(k);
750 if (!t.is_valid(m) ||
755 if (must_wait && must_wait(m, name, t)) {
756 m_waits.fetch_add(1, std::memory_order_relaxed);
757 next.ops.emplace_back(op, t);
758 if (keyed) next.keys.push_back(key);
762 m.reserve_free_slots(m.cell_slot_bound(t), 1);
763 auto newtup = (*ops.fn[op])(m, t);
766 ++committed_attempts;
777 m_stats.serial_tail_seconds +=
778 std::chrono::duration<double>(Clock::now() - inline_start).count();
785 if (committed_attempts == committed_before && !next.ops.empty()) {
786 for (
size_t i = 0; i < next.ops.size(); ++i) {
787 done(keyed ? next.keys[i] : 0);
788 m_wait_defects.fetch_add(1, std::memory_order_relaxed);
789 on_fail(m, *ops.name[next.ops[i].first], next.ops[i].second);
793 std::deque<Cand>().swap(next.ops);
794 std::deque<uint64_t>().swap(next.keys);
797 const auto round_commit = Clock::now() - commit_start;
798 commit_time += round_commit;
803 candidates = std::move(next.ops);
804 keys = std::move(next.keys);
809 cnt_success = int(successes);
810 cnt_fail = int(failures);
811 logger().info(
"executed: {} | success / fail: {} / {}", attempts, successes, failures);
812 const auto secs = [](Clock::duration d) {
813 return std::chrono::duration<double>(d).count();
816 " screened: {} rounds, {} of {} attempts got past the screen, {} waits; screen "
817 "{:.4}s, commit {:.4}s",
821 m_waits.load() - waits_before,
825 log_contention(committed_attempts);
826 check_queued_drained();
859 const bool keyed = bool(queue_key);
860 const size_t n = s.ops.size();
862 assert(n < std::numeric_limits<uint32_t>::max());
864 enum : uint8_t { kOther, kVertex, kEdge, kFace, kTet };
865 std::vector<uint8_t> kind(ops.name.size(), kOther);
866 for (
size_t o = 0; o < ops.name.size(); ++o) {
867 const Op& name = *ops.name[o];
868 if (name.rfind(
"vertex", 0) == 0) {
870 }
else if (name.rfind(
"edge", 0) == 0) {
872 }
else if (name.rfind(
"face", 0) == 0) {
874 }
else if (name.rfind(
"tet", 0) == 0) {
880 using Key = std::array<uint32_t, 4>;
881 constexpr uint32_t kDropped = std::numeric_limits<uint32_t>::max();
882 const auto id = [](
size_t v) {
883 assert(v < std::numeric_limits<uint32_t>::max());
886 std::vector<std::pair<Key, uint32_t>> keyed_entries(n);
887 threading::dynamic_parallel_for(n, std::max(1, num_threads), 256, [&](
size_t b,
size_t e) {
888 for (
size_t i = b; i < e; ++i) {
889 const auto& [o, t] = s.ops[i];
890 Key k = {{kDropped, 0, 0, 0}};
891 if (t.is_valid(m) && should_renew(priority(m, *ops.name[o], t))) {
892 k = {{o, kDropped, uint32_t(i), 0}};
894 case kVertex: k = {{o, id(t.vid(m)), 0, 0}};
break;
895 case kEdge: k = {{o, id(t.vid(m)), id(t.switch_vertex(m).vid(m)), 0}};
break;
897 if constexpr (std::is_base_of<TetMesh, AppMesh>::value) {
898 k = {{o, id(t.tid(m)), 0, 0}};
902 std::array<size_t, 3> v;
903 if constexpr (std::is_base_of<TetMesh, AppMesh>::value) {
904 const auto fv = m.get_face_vertices(t);
905 v = {{fv[0].vid(m), fv[1].vid(m), fv[2].vid(m)}};
907 v = m.oriented_tri_vids(t);
909 std::sort(v.begin(), v.end());
910 k = {{o, id(v[0]), id(v[1]), id(v[2])}};
916 keyed_entries[i] = {k, uint32_t(i)};
919 std::sort(keyed_entries.begin(), keyed_entries.end());
921 std::vector<uint8_t> kept(n, 0);
922 for (
size_t j = 0; j < n && keyed_entries[j].first[0] != kDropped; ++j) {
923 if (j > 0 && keyed_entries[j].first == keyed_entries[j - 1].first)
continue;
924 kept[keyed_entries[j].second] = 1;
926 std::vector<std::pair<Key, uint32_t>>().swap(keyed_entries);
928 for (
size_t i = 0; i < n; ++i) {
930 if (keyed && s.keys[i] != 0 && !m_queued.remove(s.keys[i])) ++m_untracked_done;
934 s.ops[w] = std::move(s.ops[i]);
935 if (keyed) s.keys[w] = s.keys[i];
940 s.ops.shrink_to_fit();
943 s.keys.shrink_to_fit();
954 std::is_base_of<TetMesh, AppMesh>::value || std::is_base_of<TriMesh, AppMesh>::value) {
955 const bool live_stop =
956 stopping_criterion_checking_frequency != std::numeric_limits<size_t>::max();
957 if (sink ==
nullptr && screen_before_commit && policy != ExecutionPolicy::kSeq &&
959 return run_screened(m, seed);
970 using OpId = uint32_t;
982 bool stopped =
false;
983 bool progressed =
false;
994 Elem(
double w, OpId o,
const Tuple& t, uint32_t r, uint64_t k)
1003 bool operator<(
const Elem& b)
const
1005 return std::tie(weight, op, tup, retry, key) <
1006 std::tie(b.weight, b.op, b.tup, b.retry, b.key);
1017 const OpTable table(edit_operation_maps);
1018 const auto& op_name = table.name;
1019 const auto& op_fn = table.fn;
1020 const auto id_of = [&table](
const Op& name) -> OpId {
return table.rank(name); };
1022 std::atomic<bool> stop(
false);
1028 if (!sink) m_queued.clear();
1029 std::atomic<size_t> untracked_done(0);
1030 const auto key_of = [&](
const Op& op,
const Tuple& e) -> uint64_t {
1031 return queue_key ? queue_key(m, op, e) : 0;
1033 const auto track = [&](
const uint64_t key) {
1034 if (key != 0) m_queued.add(key);
1036 const auto done = [&](
const uint64_t key) {
1037 if (key != 0 && !m_queued.remove(key)) {
1038 untracked_done.fetch_add(1, std::memory_order_relaxed);
1046 const bool track_live_success =
1047 stopping_criterion_checking_frequency != std::numeric_limits<size_t>::max();
1048 std::atomic<size_t> live_success(0);
1050 std::vector<LocalQueue> queues(num_threads);
1051 SharedQueue final_queue;
1058 std::atomic<size_t> lock_failures(0);
1059 std::atomic<size_t> overflowed(0);
1060 std::vector<double> task_seconds(queues.size(), 0.);
1081 std::atomic_int& success_total;
1082 std::atomic_int& fail_total;
1083 std::atomic<size_t>& lock_failure_total;
1084 std::atomic<size_t>& overflow_total;
1087 size_t lock_failure = 0;
1088 size_t overflow = 0;
1091 success_total.fetch_add(success, std::memory_order_relaxed);
1092 fail_total.fetch_add(fail, std::memory_order_relaxed);
1093 lock_failure_total.fetch_add(lock_failure, std::memory_order_relaxed);
1094 overflow_total.fetch_add(overflow, std::memory_order_relaxed);
1110 auto run_single_queue = [&](
auto& Q,
1114 std::vector<Elem>* carry) {
1115 CountFlusher counts{cnt_success, cnt_fail, lock_failures, overflowed};
1125 std::vector<Elem> second_chance;
1131 size_t since_refill = 0;
1132 const auto refill = [&] {
1133 for (
auto& e : second_chance) {
1134 Q.emplace(std::move(e));
1136 second_chance.clear();
1140 if (!second_chance.empty() && deferral_window > 0 &&
1141 since_refill >= deferral_window) {
1144 if (!Q.try_pop(ele_in_queue)) {
1145 if (second_chance.empty()) {
1148 if (carry !=
nullptr) {
1152 for (
auto& e : second_chance) carry->push_back(std::move(e));
1153 second_chance.clear();
1161 std::this_thread::yield();
1165 auto& [weight, op, retry, tup, key] = ele_in_queue;
1168 if (!tup.is_valid_unlocked(m)) {
1173 std::vector<Elem> renewed_elements;
1175 auto locked_vid = lock_vertices(
1180 counts.lock_failure++;
1182 if (retry < max_retry_limit) {
1183 second_chance.push_back(ele_in_queue);
1187 final_queue.emplace(ele_in_queue);
1191 if (tup.is_valid(m)) {
1192 const Op& op_str = *op_name[op];
1193 if (!is_weight_up_to_date(
1195 std::tuple<double, Op, Tuple>(weight, op_str, tup))) {
1197 operation_cleanup(m);
1200 if (must_wait && must_wait(m, op_str, tup)) {
1203 operation_cleanup(m);
1204 m_waits.fetch_add(1, std::memory_order_relaxed);
1205 std::lock_guard<std::mutex> lock(sink->mutex);
1206 sink->ops.emplace_back(op, tup);
1207 if (queue_key) sink->keys.push_back(key);
1211 m_wait_defects.fetch_add(1, std::memory_order_relaxed);
1213 on_fail(m, op_str, tup);
1215 operation_cleanup(m);
1219 operation_cleanup(m);
1220 m_waits.fetch_add(1, std::memory_order_relaxed);
1222 if (retry < max_retry_limit) {
1223 second_chance.push_back(ele_in_queue);
1227 final_queue.emplace(ele_in_queue);
1232 m.reserve_free_slots(m.cell_slot_bound(tup), 1);
1233 }
else if (budget !=
nullptr) {
1235 if (m.cell_slot_bound(tup) > budget->cells || budget->verts < 1) {
1238 operation_cleanup(m);
1239 Q.emplace(ele_in_queue);
1241 budget->stopped =
true;
1245 const auto usage_before = AppMesh::slot_usage_of_this_thread();
1246 auto newtup = (*op_fn[op])(m, tup);
1248 if (budget !=
nullptr) {
1249 const auto usage = AppMesh::slot_usage_of_this_thread();
1250 budget->cells -= usage.cells - usage_before.cells;
1251 budget->verts -= usage.verts - usage_before.verts;
1252 budget->progressed =
true;
1254 std::vector<std::pair<Op, Tuple>> renewed_tuples;
1256 renewed_tuples = renew_neighbor_tuples(m, op_str, newtup.value());
1258 if (track_live_success) {
1259 live_success.fetch_add(1, std::memory_order_relaxed);
1262 on_fail(m, op_str, tup);
1267 for (
const auto& [o, e] : renewed_tuples) {
1268 auto val = priority(m, o, e);
1269 if (should_renew(val)) {
1270 const uint64_t k = key_of(o, e);
1272 renewed_elements.emplace_back(val, id_of(o), e, 0, k);
1278 operation_cleanup(m);
1280 for (
auto& e : renewed_elements) {
1284 if (stop.load(std::memory_order_acquire)) {
1287 if (track_live_success && live_success.load(std::memory_order_relaxed) >
1288 stopping_criterion_checking_frequency) {
1289 if (stopping_criterion(m)) {
1297 if (policy == ExecutionPolicy::kSeq) {
1298 seed([&](
const Op& op,
const Tuple& e, uint64_t) {
1299 if (!e.is_valid(m)) {
1302 const uint64_t k = key_of(op, e);
1304 final_queue.emplace(priority(m, op, e), id_of(op), e, 0, k);
1306 run_single_queue(final_queue, 0,
true,
nullptr,
nullptr);
1308 seed([&](
const Op& op,
const Tuple& e,
const uint64_t counted) {
1309 if (!e.is_valid(m)) {
1310 if (sink) done(counted);
1315 const uint64_t k = sink ? counted : key_of(op, e);
1316 if (!sink) track(k);
1319 const size_t part = get_partition_id(m, e);
1320 if (part >= queues.size()) queues.resize(part + 1);
1321 queues[part].emplace(priority(m, op, e), id_of(op), e, 0, k);
1324 using clock = std::chrono::steady_clock;
1325 const auto t_parallel = clock::now();
1343 const size_t n_tasks = size_t(std::max(1, num_threads));
1344 std::atomic<size_t> next_queue{0};
1345 std::vector<SlotBudget> budgets(n_tasks);
1346 std::vector<unsigned> boost(n_tasks, 0);
1348 for (
size_t round = 0;; ++round) {
1352 const size_t cell_room =
1353 std::max(m.cell_capacity() / 4, m.cell_storage_capacity() - m.cell_capacity());
1354 const size_t vert_room =
1355 std::max(m.vert_capacity() / 4, m.vert_storage_capacity() - m.vert_capacity());
1356 const size_t cell_share = std::max<size_t>(4096, cell_room / n_tasks);
1357 const size_t vert_share = std::max<size_t>(1024, vert_room / n_tasks);
1358 size_t cells = 0, verts = 0;
1359 for (
size_t t = 0; t < n_tasks; ++t) {
1360 budgets[t] = SlotBudget{cell_share << boost[t], vert_share << boost[t]};
1361 cells += budgets[t].cells;
1362 verts += budgets[t].verts;
1364 m.reserve_free_slots(cells, verts);
1367 next_queue.store(0, std::memory_order_relaxed);
1368 for (
int task_id = 0; task_id < n_tasks; task_id++) {
1369 tg.run([&, task_id] {
1370 const auto t0 = clock::now();
1371 SlotBudget& budget = budgets[task_id];
1372 const auto done_for_round = [&] {
1373 return budget.stopped || stop.load(std::memory_order_acquire);
1377 std::vector<Elem> carry;
1379 for (
size_t part; (part = next_queue.fetch_add(1)) < queues.size();) {
1380 for (
auto& e : carry) queues[part].emplace(std::move(e));
1389 if (done_for_round())
break;
1394 if (!carry.empty()) {
1395 for (
auto& e : carry) queues[last].emplace(std::move(e));
1397 if (!done_for_round()) {
1407 task_seconds[task_id] +=
1408 std::chrono::duration<double>(clock::now() - t0).count();
1413 for (
size_t t = 0; t < n_tasks; ++t) {
1414 if (!budgets[t].stopped)
continue;
1416 if (!budgets[t].progressed) boost[t] = std::min(boost[t] + 1, 24u);
1419 st.rounds = round + 2;
1421 st.parallel_seconds = std::chrono::duration<double>(clock::now() - t_parallel).count();
1422 st.final_queue_size = final_queue.size();
1424 logger().debug(
"Parallel Complete, remains element {}", final_queue.size());
1426 const auto t_tail = clock::now();
1427 run_single_queue(final_queue, 0,
true,
nullptr,
nullptr);
1428 st.serial_tail_seconds = std::chrono::duration<double>(clock::now() - t_tail).count();
1431 st.lock_failures = lock_failures.load(std::memory_order_relaxed);
1432 st.overflowed = overflowed.load(std::memory_order_relaxed);
1433 if (!task_seconds.empty()) {
1434 const auto mm = std::minmax_element(task_seconds.begin(), task_seconds.end());
1435 st.idlest_task_seconds = *mm.first;
1436 st.busiest_task_seconds = *mm.second;
1447 "executed: {} | success / fail: {} / {}",
1448 (
int)cnt_success + (
int)cnt_fail,
1451 if (st.rounds > 1) {
1453 " parallel region ran in {} rounds (storage grown between them)",
1460 m_untracked_done += untracked_done.load();
1466 if (queue_key && !stop.load()) {
1467 const size_t left = m_queued.size();
1468 const size_t untracked = untracked_done.load();
1469 if (left > 0 || untracked > 0) {
1471 "queue_key bookkeeping: {} keys still counted as queued after the pass, {} "
1472 "finished elements were never counted",