|
Wildmeshing Toolkit
|
Classes | |
| struct | PassStats |
| What the last pass cost in contention, as opposed to in work. More... | |
Public Types | |
| using | Tuple = typename AppMesh::Tuple |
Public Member Functions | |
| ExecutePass (const ExecutionPolicy &policy_=ExecutionPolicy::kSeq) | |
| Construct a new Execute Pass object. It contains the name-to-operation map and the functions that define the rules for operations. | |
| ExecutePass (ExecutePass &)=delete | |
| bool | operator() (AppMesh &m, const std::vector< std::pair< Op, Tuple > > &operation_tuples) |
| Executes the operations for an application when the lambda function is invoked. The rules that are customizly defined for applications are applied. | |
| int | get_cnt_success () const |
| int | get_cnt_fail () const |
| const PassStats & | stats () const |
Public Attributes | |
| std::map< Op, std::function< std::optional< std::vector< Tuple > >(AppMesh &, const Tuple &)> > | edit_operation_maps |
| A dictionary that registers names with operations. | |
| std::function< double(const AppMesh &, Op op, const Tuple &)> | priority |
| Priority function (default to edge length) | |
| std::function< bool(double)> | should_renew = [](double) { return true; } |
| check on wheather new operations should be added to the priority queue | |
| std::function< std::vector< std::pair< Op, Tuple > >(const AppMesh &, Op, const std::vector< Tuple > &)> | renew_neighbor_tuples |
| renew neighboring Tuples after each operation depends on the operation | |
| std::function< bool(AppMesh &, const Tuple &, int task_id)> | lock_vertices |
| lock the vertices concerned depends on the operation | |
| std::function< bool(const AppMesh &)> | stopping_criterion |
| Stopping Criterion based on the whole mesh For efficiency, not every time is checked. In serial, this may go over all the elements. For parallel, this involves synchronization. So there is a checking frequency. | |
| size_t | stopping_criterion_checking_frequency = std::numeric_limits<size_t>::max() |
Cumulative successful operations before stopping_criterion is first consulted. | |
| std::function< bool(const AppMesh &, const std::tuple< double, Op, Tuple > &t)> | is_weight_up_to_date |
| Should Process drops some Tuple from being processed. For example, if the energy is out-dated. This is in addition to calling tuple valid. | |
| std::function< void(const AppMesh &, Op, const Tuple &t)> | on_fail |
| used to collect operations that are not finished and used for later re-execution | |
| ExecutionPolicy | policy |
| int | num_threads = 1 |
| size_t | max_retry_limit = 10 |
| Attempts an operation gets at claiming its ring before it is handed to the serial queue drained after the barrier. | |
| size_t | deferral_window = 128 |
| Operations to run before handing deferred operations back to the queue, or 0 to wait until the queue empties. | |
Private Member Functions | |
| void | operation_cleanup (AppMesh &m) |
| size_t | get_partition_id (const AppMesh &m, const Tuple &e) |
| void | log_contention () const |
Private Attributes | |
| std::atomic_int | cnt_success = 0 |
| std::atomic_int | cnt_fail = 0 |
| PassStats | m_stats |
|
inline |
Construct a new Execute Pass object. It contains the name-to-operation map and the functions that define the rules for operations.
|
inlineprivate |
Debug-level because it is per pass and there are many passes per iteration. Enable with the logger at debug to see whether contention is worth acting on.
|
inline |
Executes the operations for an application when the lambda function is invoked. The rules that are customizly defined for applications are applied.
| m | |
| operation_tuples | a vector of pairs of operation's name and the Tuple to be operated on |
| size_t wmtk::ExecutePass< AppMesh >::deferral_window = 128 |
Operations to run before handing deferred operations back to the queue, or 0 to wait until the queue empties.
Bounding this is what makes the second-chance list pay. A per-thread queue holds thousands of operations, so draining only at queue-empty means a deferred operation waits that long, by which time the mesh around it has moved, is_weight_up_to_date rejects it, and the work has to be rediscovered. That inflates the attempt count by roughly a fifth and eats most of the per-operation saving.
Measured at 16 threads, three reps, optimization wall time against the pre-deferral baseline, with attempts in brackets:
103197 (base 23.0M) 101881 (base 48.8M) window 0 -2.2% [28.5M] -20.4% [45.7M] window 32 -16.3% [23.7M] -21.6% [43.7M] window 128 -17.9% [23.1M] -26.4% [41.9M] window 512 -17.0% [23.7M] -25.5% [42.7M]
128 is at or near best on both and keeps the attempt count closest to the baseline's. The result is not sharp between 32 and 512; it is sharp against 0.
| std::function<bool(const AppMesh&, const std::tuple<double, Op, Tuple>& t)> wmtk::ExecutePass< AppMesh >::is_weight_up_to_date |
Should Process drops some Tuple from being processed. For example, if the energy is out-dated. This is in addition to calling tuple valid.
| std::function<bool(AppMesh&, const Tuple&, int task_id)> wmtk::ExecutePass< AppMesh >::lock_vertices |
lock the vertices concerned depends on the operation
| size_t wmtk::ExecutePass< AppMesh >::max_retry_limit = 10 |
Attempts an operation gets at claiming its ring before it is handed to the serial queue drained after the barrier.
10 was swept and left alone. Measured on 128k-tet Thingi10K 103197 at 16 threads, three reps each, in µs per attempted operation:
immediate requeue (before the second-chance deferral below existed): 1: +17.0% 2: +7.2% 3: +6.2% 5: +4.6% 10: best 20: +6.2% with the deferral: 2: 2.170 3: 1.969 10: 1.974 (baseline at 10 was 2.527)
So under the old immediate-requeue behaviour the value mattered a lot – a low limit sent everything it stopped retrying to the serial queue, and that tail (23% of scheduler time at 10, 39% at 1) cost more than the spinning it avoided. With the deferral the retries are nearly free, the overflow pressure disappears, and 3 and 10 become indistinguishable. Left at 10 because nothing argues for moving it.
Below about 3 it still hurts: at 2 the overflow rate climbs enough to be visible again.
| std::function<void(const AppMesh&, Op, const Tuple& t)> wmtk::ExecutePass< AppMesh >::on_fail |
used to collect operations that are not finished and used for later re-execution
| std::function<double(const AppMesh&, Op op, const Tuple&)> wmtk::ExecutePass< AppMesh >::priority |
Priority function (default to edge length)
| std::function<std::vector<std::pair<Op, Tuple> >(const AppMesh&, Op, const std::vector<Tuple>&)> wmtk::ExecutePass< AppMesh >::renew_neighbor_tuples |
renew neighboring Tuples after each operation depends on the operation
| std::function<bool(const AppMesh&)> wmtk::ExecutePass< AppMesh >::stopping_criterion |
Stopping Criterion based on the whole mesh For efficiency, not every time is checked. In serial, this may go over all the elements. For parallel, this involves synchronization. So there is a checking frequency.
| size_t wmtk::ExecutePass< AppMesh >::stopping_criterion_checking_frequency = std::numeric_limits<size_t>::max() |
Cumulative successful operations before stopping_criterion is first consulted.
Despite the name this is a threshold, not a period: the count it is tested against is never reset, so once the pass has had this many successes the criterion is consulted after every subsequent operation. Both current users rely on exactly that – they set the criterion to return true and the threshold to the number of collapses needed to reach a target vertex count, making this a decimation counter that stops the pass on its first check. It is not a "check every N operations" knob, and writing a genuinely periodic criterion against it would evaluate that criterion on every operation forever after.
Left at the default, the criterion is never consulted at all, which is the case for every tetwild/triwild/simwild pass.
(There used to be a cnt_update member here that was incremented per success and reset inside the check, as if the threshold were a period. Nothing ever read it – the reset was on a branch the always-true criteria above never reach – so it was one more contended atomic on the hot path buying nothing, and it is gone.)