From 07eed8d6ae6bed7842e8a101bf2e6ac5f2bff8c7 Mon Sep 17 00:00:00 2001 From: dthuerck Date: Wed, 12 Apr 2017 16:31:49 +0200 Subject: [PATCH] Fixes and improvements in tree_sampler. * Avoiding unnecessary operations by earlier termination. * Fixed acyclicity violation with rescue iterations. * Allowed 'relaxing' maximality, avoiding rescue loops (this is now the default option). All in all, a 1M node test graph with 1 root is processed about ~10x faster now. --- demo/mapmap_demo.cc | 3 +- mapmap/header/mapmap.h | 17 +- mapmap/header/tree_sampler.h | 11 +- mapmap/source/mapmap.impl.h | 17 +- mapmap/source/tree_sampler.impl.h | 289 +++++++++++++----------------- test/test_coordinate_set.cc | 49 +++-- 6 files changed, 189 insertions(+), 197 deletions(-) diff --git a/demo/mapmap_demo.cc b/demo/mapmap_demo.cc index 87d2e4f..50a19b7 100644 --- a/demo/mapmap_demo.cc +++ b/demo/mapmap_demo.cc @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2017, Daniel Thuerck + * Copyright (C) 2016, Daniel Thuerck * All rights reserved. * * This software may be modified and distributed under the terms @@ -178,6 +178,7 @@ main( ctr.spanning_tree_multilevel_after_n_iterations = 5; ctr.force_acyclic = true; ctr.min_acyclic_iterations = 5; + ctr.relax_acyclic_maximal = true; /* construct optimizer */ mapmap.set_graph(graph.get()); diff --git a/mapmap/header/mapmap.h b/mapmap/header/mapmap.h index 8a21262..7c66734 100644 --- a/mapmap/header/mapmap.h +++ b/mapmap/header/mapmap.h @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2017, Daniel Thuerck + * Copyright (C) 2016, Daniel Thuerck * TU Darmstadt - Graphics, Capture and Massively Parallel Computing * All rights reserved. * @@ -108,7 +108,7 @@ class mapMAP _s_t initial_labelling(); _s_t opt_step_spanning_tree(); _s_t opt_step_multilevel(); - _s_t opt_step_acyclic(); + _s_t opt_step_acyclic(bool relax_maximality); bool check_termination(); protected: @@ -182,19 +182,20 @@ class mapMAP struct mapMAP_control { /* switch modes on/off */ - bool use_multilevel = true; - bool use_spanning_tree = true; - bool use_acyclic = true; + bool use_multilevel; + bool use_spanning_tree; + bool use_acyclic; /* multilevel settings */ /* none */ /* spanning tree settings */ - uint_t spanning_tree_multilevel_after_n_iterations = 5; + uint_t spanning_tree_multilevel_after_n_iterations; /* acyclic settings */ - bool force_acyclic = true; /* force using acyclic even if terminated */ - uint_t min_acyclic_iterations = 5; + bool force_acyclic; /* force using acyclic even if terminated */ + uint_t min_acyclic_iterations; + bool relax_acyclic_maximal; }; NS_MAPMAP_END diff --git a/mapmap/header/tree_sampler.h b/mapmap/header/tree_sampler.h index 3e50759..614e229 100644 --- a/mapmap/header/tree_sampler.h +++ b/mapmap/header/tree_sampler.h @@ -33,26 +33,27 @@ class TreeSampler /** * Select around k roots, satisfying the following conditions: * - each component is covered by at least one root, - * - if ACYCLIC = true, roots cannot be adjacent nodes, * - number of components <= number of roots <= number of nodes. * * After each component is covered, the remaining nodes * are sampled according to the components' size. + * + * Conflict checking is deferred to a later stage. */ void select_random_roots(const luint_t k, std::vector& roots); /** * Samples a maximal (acyclic) coordinate set, given a set - * of roots to grow from. To achieve maximality, additional roots + * of roots to grow from. To achieve maximality, additional roots * may be included. - * + * * Note: If a component is not covered by the root set, * it is also left out of the tree. * * Transfers ownership to the caller. */ std::unique_ptr> sample(std::vector& roots, - const bool record_dependencies); + bool record_dependencies, bool relax = true); public: const uint_t p_chunk_size = 16; @@ -95,6 +96,8 @@ class TreeSampler std::vector m_rem_degrees; std::vector> m_markers; std::vector> m_node_locks; + std::vector> m_in_queue; + tbb::atomic m_rem_nodes; }; template diff --git a/mapmap/source/mapmap.impl.h b/mapmap/source/mapmap.impl.h index df5318c..9be2ef4 100644 --- a/mapmap/source/mapmap.impl.h +++ b/mapmap/source/mapmap.impl.h @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2017, Daniel Thuerck + * Copyright (C) 2016, Daniel Thuerck * TU Darmstadt - Graphics, Capture and Massively Parallel Computing * All rights reserved. * @@ -235,6 +235,13 @@ throw() { /* initialize control flow with standard values */ mapMAP_control std_control; + std_control.use_multilevel = true; + std_control.use_spanning_tree = true; + std_control.use_acyclic = true; + std_control.spanning_tree_multilevel_after_n_iterations = 5; + std_control.force_acyclic = true; + std_control.min_acyclic_iterations = 5; + std_control.relax_acyclic_maximal = true; return optimize(solution, std_control); } @@ -337,7 +344,7 @@ throw() { ++ac_it; - m_objective = opt_step_acyclic(); + m_objective = opt_step_acyclic(control_flow.relax_acyclic_maximal); record_time_from_start(); print_status(); @@ -642,7 +649,8 @@ template FORCEINLINE _s_t mapMAP:: -opt_step_acyclic() +opt_step_acyclic( + bool relax_maximality) { std::vector<_iv_st> ac_solution = m_solution; @@ -654,7 +662,8 @@ opt_step_acyclic() sampler.select_random_roots(m_num_roots, roots); /* grow trees in forest */ - std::unique_ptr> tree = sampler.sample(roots, true); + std::unique_ptr> tree = sampler.sample(roots, true, + relax_maximality); /* create tree optimizer (std: DP) and pass parameters and modules */ CombinatorialDynamicProgramminginc_edges(next_root)) - { - const GraphEdge& e = m_graph->edges()[e_id]; - const luint_t other_node = (e.node_a == next_root ? - e.node_b : e.node_a); - - if(root_marker[other_node] > 0u) - { - violation = true; - break; - } - } - - if(!violation) - break; - - /* in case of violation, deactivate node */ - if(component_sizes[next_component] > 1) - std::swap(m_component_lists[next_component][root_index], - m_component_lists[next_component] - [m_component_lists[next_component].size() - 1]); - - --component_sizes[next_component]; - - /* no more nodes in this component - skip selection */ - if(component_sizes[next_component] == 0) - { - skip = true; - break; - } - - /* select next candidate */ - std::uniform_int_distribution ud(0, - component_sizes[next_component] - 1); - root_index = ud(rnd); - next_root = m_component_lists[next_component] - [root_index]; - } - - if (skip) - continue; - } - + /* defer conflict handling to main procedure */ roots.push_back(next_root); root_marker[next_root] = 1u; @@ -165,7 +112,8 @@ std::unique_ptr> TreeSampler:: sample( std::vector& roots, - const bool record_dependencies) + bool record_dependencies, + bool relax) { const luint_t num_nodes = m_graph->num_nodes(); const luint_t num_edges = m_graph->edges().size(); @@ -175,6 +123,9 @@ sample( /* create acceleration structure for edge sampling */ create_adj_acc(); + /* for early termination, count number of nodes remaining */ + m_rem_nodes = m_graph->num_nodes(); + /* work queues */ m_w_in = &m_w_a; m_w_out = &m_w_b; @@ -183,54 +134,30 @@ sample( m_w_out->reserve(num_nodes); m_w_new.reserve(num_nodes); - /* clear markers and set root neighbour markers */ + /* clear markers */ m_markers.clear(); m_markers.resize(num_nodes, 0u); - for(luint_t i = 0; i < roots.size(); ++i) - { - /* add root nodes to initial queue */ - m_w_in->push_back(roots[i]); - - /* mark nodes as roots */ - m_tree->raw_parent_ids()[roots[i]] = roots[i]; - } - - const tbb::blocked_range root_range(0, roots.size()); - tbb::parallel_for(root_range, - [&](const tbb::blocked_range& range) - { - for(luint_t i = range.begin(); i != range.end(); ++i) - { - const luint_t r = roots[i]; - - for(const luint_t& e_id : m_graph->inc_edges(r)) - { - const GraphEdge& e = m_graph->edges()[e_id]; - const luint_t other_node = (e.node_a == r ? e.node_b : - e.node_a); - //if (m_markers[other_node] < 2u) - ++m_markers[other_node]; - } - } - }); + /* put roots into queue */ + for(const luint_t r : roots) + m_tree->raw_parent_ids()[r] = r; + m_w_new.assign(roots.begin(), roots.end()); + m_rem_nodes -= roots.size(); /* copy original degrees and initialize locks */ m_node_locks.resize(num_nodes); m_rem_degrees.resize(num_nodes); - tbb::parallel_for(tbb::blocked_range(0, num_nodes), - [&](const tbb::blocked_range& r) - { - for(luint_t i = r.begin(); i != r.end(); ++i) - { - m_node_locks[i] = 0; - m_rem_degrees[i] = m_graph->nodes()[i].incident_edges.size(); - } - }); + m_in_queue.resize(num_nodes); + + for(luint_t i = 0; i < num_nodes; ++i) + m_rem_degrees[i] = m_graph->nodes()[i].incident_edges.size(); + std::fill(m_node_locks.begin(), m_node_locks.end(), 0); /* start actual growing process */ luint_t it = 0; - while(true) + /* exploit first iteration to solve root conflicts */ + bool skip_ph1 = true; + while(m_rem_nodes > 0 || skip_ph1) { m_w_in = (it % 2 == 0 ? &m_w_a : &m_w_b); m_w_out = (it % 2 == 0 ? &m_w_b : &m_w_a); @@ -238,13 +165,21 @@ sample( m_w_out->clear(); m_w_out->reserve(num_nodes); - m_w_new.clear(); - m_w_new.reserve(num_nodes); - m_w_conflict.clear(); + std::fill(m_in_queue.begin(), m_in_queue.end(), 0); - /* phase I: try growing new branches */ - sample_phase_I(); + /* phase I: try growing new branches (or just copy selected nodes) */ + if(!skip_ph1) + { + m_w_new.clear(); + m_w_new.reserve(num_nodes); + + sample_phase_I(); + } + else + { + m_w_out->assign(m_w_in->begin(), m_w_in->end()); + } /* phase II: update markers and detect collisions */ sample_phase_II(); @@ -253,22 +188,43 @@ sample( { /* phase III: resolve conflicts */ sample_phase_III(); + + /* record roots from conflict-resolved rescue nodes */ + if(skip_ph1) + { + for(const luint_t& cand : (*m_w_in)) + { + if(m_tree->raw_parent_ids()[cand] == cand) + { + roots.push_back(cand); + --m_rem_nodes; + } + } + } } + skip_ph1 = false; + + /* append new (not removed nodes to queue) */ + for(const luint_t& n : m_w_new) + m_w_out->push_back(n); /** * if procedure gets stuck - add nodes with marker 0 * as new nodes (that respect acyclicity). */ - if(ACYCLIC && m_w_out->size() == 0u) + if(ACYCLIC && !relax && m_w_out->empty()) { sample_rescue(); - for(luint_t i = 0; i < m_w_out->size(); ++i) - roots.push_back((*m_w_out)[i]); + /* defer conflict-solving and root recording to later */ + skip_ph1 = true; + } + else + { + /* after rescue, defer break until after conflict handling */ + if(m_w_out->empty()) + break; } - - if(m_w_out->size() == 0u) - break; ++it; } @@ -344,7 +300,7 @@ void TreeSampler:: sample_phase_I() { - tbb::blocked_range in_range(0, m_w_in->size(), 32); + tbb::blocked_range in_range(0, m_w_in->size()); tbb::parallel_for(in_range, [&](const tbb::blocked_range& r) @@ -403,10 +359,8 @@ sample_phase_I() m_tree->raw_parent_ids()[o_node] = in_node; m_w_new.push_back(o_node); - if(m_rem_degrees[o_node] > 0u) - { - m_w_out->push_back(o_node); - } + /* one node less to consider */ + --m_rem_nodes; } /* release lock */ @@ -423,7 +377,9 @@ sample_phase_I() inc_list_ix], m_adj[m_adj_offsets[in_node] + m_rem_degrees[in_node] - 1]); - m_w_out->push_back(in_node); + + if(!m_in_queue[in_node].fetch_and_store(1)) + m_w_out->push_back(in_node); } --m_rem_degrees[in_node]; } @@ -433,7 +389,8 @@ sample_phase_I() } else { - m_w_out->push_back(in_node); + if(!m_in_queue[in_node].fetch_and_store(1)) + m_w_out->push_back(in_node); } } }); @@ -447,7 +404,6 @@ TreeSampler:: sample_phase_II() { tbb::blocked_range new_range(0, m_w_new.size()); - tbb::parallel_for(new_range, [&](const tbb::blocked_range& r) { @@ -479,8 +435,10 @@ sample_phase_II() o_node)); } - /* update marker */ - ++m_markers[o_node]; + /* update marker and remove node from consideration */ + if(m_markers[o_node].fetch_and_increment() == 1 && + !o_in_tree) + --m_rem_nodes; } } }); @@ -497,7 +455,7 @@ sample_phase_III() if (num_conflicts_found == 0) return; - tbb::atomic num_nodes_removed = 0; + tbb::concurrent_vector del; tbb::blocked_range conflict_range(0, num_conflicts_found); tbb::parallel_for(conflict_range, [&](const tbb::blocked_range& r) @@ -525,7 +483,7 @@ sample_phase_III() [remove_node] != invalid_luint_t); /** - * skip removal operation if already happended by + * skip removal operation if already happened by * another conflict pair. */ if(is_in_tree) @@ -535,23 +493,15 @@ sample_phase_III() m_tree->raw_parent_ids()[remove_node] = invalid_luint_t; - ++num_nodes_removed; + del.push_back(remove_node); - /* decrement marker of adjacent nodes */ - for(const luint_t& e_id : - m_graph->inc_edges(remove_node)) - { - const GraphEdge e = - m_graph->edges()[e_id]; - const luint_t o_node = (e.node_a == remove_node ? - e.node_b : e.node_a); - - --m_markers[o_node]; - } + /* if removing 'new' root, don't restore table */ + if(remove_node != old_parent) + ++m_rem_degrees[old_parent]; /* rollback parent and put it into queue */ - ++m_rem_degrees[old_parent]; - m_w_out->push_back(old_parent); + if(!m_in_queue[old_parent].fetch_and_store(1)) + m_w_out->push_back(old_parent); } /* release lock */ @@ -560,22 +510,45 @@ sample_phase_III() } }); - /* remove nodes not in tree from queue (queue filter step) */ - if(num_nodes_removed > 0) + /* update markers of nodes adjacent to deleted candidates */ + for(const luint_t& remove_node : del) { - /* abuse m_new as temporary storage */ - m_w_new.clear(); - m_w_new.assign(m_w_out->begin(), m_w_out->end()); - m_w_out->clear(); - m_w_out->reserve(m_w_new.size()); - - tbb::parallel_do(m_w_new.begin(), m_w_new.end(), - [&](const luint_t& n) + /* decrement marker of adjacent nodes */ + for(const luint_t& e_id : m_graph->inc_edges(remove_node)) + { + const GraphEdge e = m_graph->edges()[e_id]; + const luint_t o_node = (e.node_a == remove_node ? + e.node_b : e.node_a); + const bool o_in_tree = (m_tree->raw_parent_ids()[o_node] != + invalid_luint_t); + + /* if marker-threshold passed while decrementing, reconsider node */ + if(m_markers[o_node].fetch_and_decrement() == 2 && !o_in_tree) { - /* add only nodes to queue which are in the tree */ - if(m_tree->raw_parent_ids()[n] != invalid_luint_t) - m_w_out->push_back(n); - }); + /* abuse m_in_queue to correctly handle reconsideration */ + m_in_queue[o_node] = 255; + ++m_rem_nodes; + } + } + } + + /* root case: put back deleted nodes if possible */ + for(const luint_t& remove_node : del) + { + if(m_markers[remove_node] < 2 && m_in_queue[remove_node] < 255) + ++m_rem_nodes; + } + + /* for correctness, make sure there are only tree-nodes in the queue */ + if(!del.empty()) + { + /* filter removed nodes from m_w_new */ + std::vector filter_new; + for(const luint_t& newn : m_w_new) + if(m_tree->raw_parent_ids()[newn] != invalid_luint_t) + filter_new.push_back(newn); + + m_w_new.assign(filter_new.begin(), filter_new.end()); } } @@ -589,7 +562,6 @@ sample_rescue() std::mt19937_64 rnd(m_rnd_dev()); /* find potential nodes with marker 0 */ - tbb::concurrent_vector candidates; tbb::blocked_range node_range(0, m_graph->num_nodes()); tbb::parallel_for(node_range, [&](const tbb::blocked_range& r) @@ -600,29 +572,16 @@ sample_rescue() [i] != invalid_luint_t); if(!is_in_tree && m_markers[i] == 0) - candidates.push_back(i); + { + m_tree->raw_parent_ids()[i] = i; + + m_w_new.push_back(i); + --m_rem_nodes; + } } }); - if(candidates.size() == 0) - return; - - /* add 1 candidate as new root */ - std::uniform_int_distribution d(0, candidates.size() - 1); - const luint_t new_root = candidates[d(rnd)]; - m_w_out->push_back(new_root); - m_tree->raw_parent_ids()[new_root] = new_root; - - /* update markers */ - for(const luint_t& e_id : m_graph->inc_edges(new_root)) - { - const GraphEdge e = m_graph->edges()[e_id]; - const luint_t o_node = (e.node_a == new_root ? - e.node_b : e.node_a); - - /* update marker */ - ++m_markers[o_node]; - } + /* resolve conflicts later */ } NS_MAPMAP_END diff --git a/test/test_coordinate_set.cc b/test/test_coordinate_set.cc index 05f8baf..13e5975 100644 --- a/test/test_coordinate_set.cc +++ b/test/test_coordinate_set.cc @@ -6,7 +6,7 @@ * This software may be modified and distributed under the terms * of the BSD license. See the LICENSE file for details. */ - + #include #include @@ -29,8 +29,8 @@ class mapMAPTestCoordinateSet : public testing::Test public: using cost_t = float; - const uint_t num_components = 4; - const uint_t component_dim = 20; + const uint_t num_components = 1; + const uint_t component_dim = 100; public: mapMAPTestCoordinateSet() @@ -56,7 +56,7 @@ class mapMAPTestCoordinateSet : public testing::Test m_sampler = std::unique_ptr>( new TreeSampler(m_graph.get())); - m_tree = m_sampler->sample(m_roots, true); + m_tree = m_sampler->sample(m_roots, true, false); /* retrieve tree's nodes */ const luint_t num_nodes = m_graph->num_nodes(); @@ -68,7 +68,7 @@ class mapMAPTestCoordinateSet : public testing::Test } } - void + void TearDown() { @@ -95,8 +95,8 @@ TEST_F(mapMAPTestCoordinateSet, TestIsAcyclic) std::vector in_tree(num_nodes, 0); for(luint_t i = 0; i < num_nodes; ++i) { - TreeNode node = m_tree->node(i); - in_tree[i] = (node.is_in_tree ? 1u : 0u); + TreeNode node = m_tree->node(i); + in_tree[i] = (node.is_in_tree ? 1u : 0u); } /* Phase II : check the mentioned criterion */ @@ -112,7 +112,7 @@ TEST_F(mapMAPTestCoordinateSet, TestIsAcyclic) for(const luint_t& e_id : m_graph->inc_edges(i)) { const GraphEdge& e = m_graph->edges()[e_id]; - const luint_t other_node = (e.node_a == + const luint_t other_node = (e.node_a == i ? e.node_b : e.node_a); if(in_tree[other_node] > 0u) @@ -122,10 +122,29 @@ TEST_F(mapMAPTestCoordinateSet, TestIsAcyclic) /* Check if retrieved nodes are parent or children */ for(const luint_t& o_n : adjacent_in_tree) { - auto found = std::find(&node.children_ids[0], - &node.children_ids[node.degree], o_n); - - ASSERT_TRUE(node.parent_id == o_n || found != + auto found = std::find(&node.children_ids[0], + &node.children_ids[node.degree], o_n); + + if(!(node.parent_id == o_n || found != + &node.children_ids[node.degree])) + { + std::cout << "Problem in " << o_n << " (parent " << + m_tree->node(o_n).parent_id << ") with " << + i << " (in turn with parent " << + node.parent_id << ", degree " << node.degree << ")" << std::endl; + + std::cout << "Children " << i << ": "; + for(luint_t c = 0; c < node.degree; ++c) + std::cout << node.children_ids[c] << " "; + std::cout << std::endl; + + std::cout << "Children " << o_n << ": "; + for(luint_t c = 0; c < m_tree->node(o_n).degree; ++c) + std::cout << m_tree->node(o_n).children_ids[c] << " "; + std::cout << std::endl; + } + + ASSERT_TRUE(node.parent_id == o_n || found != &node.children_ids[node.degree]); } } @@ -138,11 +157,11 @@ TEST_F(mapMAPTestCoordinateSet, TestIsMaximal) std::vector in_tree(num_nodes, 0); for(luint_t i = 0; i < num_nodes; ++i) { - TreeNode node = m_tree->node(i); + TreeNode node = m_tree->node(i); in_tree[i] = (node.is_in_tree ? 1u : 0u); } - /* + /* * Phase II : count neighboring nodes in tree for free nodes and * test that marker is >= 2 (otherwise the node could have been added * as coordinate. @@ -159,7 +178,7 @@ TEST_F(mapMAPTestCoordinateSet, TestIsMaximal) for (const luint_t& e_id : m_graph->inc_edges(i)) { const GraphEdge& e = m_graph->edges()[e_id]; - const luint_t other_node = (e.node_a == + const luint_t other_node = (e.node_a == i ? e.node_b : e.node_a); TreeNode o_node = m_tree->node(other_node);