diff --git a/include/networkit/community/ParallelLeidenView.hpp b/include/networkit/community/ParallelLeidenView.hpp index 5fb6dbcca..a160e5ac1 100644 --- a/include/networkit/community/ParallelLeidenView.hpp +++ b/include/networkit/community/ParallelLeidenView.hpp @@ -209,7 +209,7 @@ class ParallelLeidenView final : public CommunityDetectionAlgorithm { int maxInnerIterations = 20; // Maximum moves per node within a Leiden iteration. - int maxRequeuesPerNode = 24; + int maxMovesPerNode = 20; // Optional convergence stop: minimum relative reduction in community count per inner iter. // 0.0 disables this criterion. diff --git a/networkit/cpp/community/ParallelLeidenView.cpp b/networkit/cpp/community/ParallelLeidenView.cpp index 35cd1ac9f..85ba5a727 100644 --- a/networkit/cpp/community/ParallelLeidenView.cpp +++ b/networkit/cpp/community/ParallelLeidenView.cpp @@ -52,11 +52,11 @@ ParallelLeidenView::ParallelLeidenView(const Graph &graph, int iterations, bool // Keep default if parsing fails. } } - if (const char *maxRequeuesPerNodeEnv = std::getenv("ICEBUG_LEIDEN_MAX_REQUEUES_PER_NODE")) { + if (const char *maxMovesPerNodeEnv = std::getenv("ICEBUG_LEIDEN_MAX_MOVES_PER_NODE")) { try { - const int parsed = std::stoi(maxRequeuesPerNodeEnv); + const int parsed = std::stoi(maxMovesPerNodeEnv); if (parsed > 0) { - maxRequeuesPerNode = parsed; + maxMovesPerNode = parsed; } } catch (...) { // Keep default if parsing fails. @@ -177,7 +177,7 @@ void ParallelLeidenView::run() { changed = false; INFO("Using move gain epsilon=", std::setprecision(6), moveGainMarginEpsilon, " | max inner iterations=", maxInnerIterations, - " | max requeues per node=", maxRequeuesPerNode, + " | max moves per node=", maxMovesPerNode, " | vector oversize=", VECTOR_OVERSIZE, " | min community reduction=", minCommunityReduction); @@ -385,7 +385,7 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & std::vector gainMarginMin(omp_get_max_threads(), std::numeric_limits::max()); std::vector gainMarginMax(omp_get_max_threads(), std::numeric_limits::lowest()); std::vector totalNodesPerThread(omp_get_max_threads(), 0); - std::vector requeuesBlockedByNodeLimit(omp_get_max_threads(), 0); + std::vector nodePassesSkippedByMoveLimit(omp_get_max_threads(), 0); std::atomic_int singleton(0); // Track whether nodes are idle, queued, currently processed, or need another pass after the @@ -395,8 +395,8 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & val.store(Idle); } - std::vector> requeuesPerNode(graph.upperNodeIdBound()); - for (auto &count : requeuesPerNode) { + std::vector> movesPerNode(graph.upperNodeIdBound()); + for (auto &count : movesPerNode) { count.store(0); } @@ -438,22 +438,8 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & std::unordered_map cutWeights; std::vector pointers; - auto admitNodeRequeue = [&](node candidate) { - std::uint32_t used = requeuesPerNode[candidate].load(std::memory_order_relaxed); - - while (true) { - if (used >= maxRequeuesPerNode) { - requeuesBlockedByNodeLimit[tid]++; - return false; - } - - if (requeuesPerNode[candidate].compare_exchange_weak( - used, used + 1, std::memory_order_relaxed, std::memory_order_relaxed)) { - return true; - } - - // On CAS failure, `used` has been refreshed with the current value. - } + auto moveLimitReached = [&](node candidate) { + return movesPerNode[candidate].load() >= maxMovesPerNode; }; auto pushNewNode = [&](node queuedNode) { @@ -478,15 +464,7 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & std::uint8_t expected = Idle; if (nodeState[queuedNode].compare_exchange_strong(expected, Queued)) { - if (admitNodeRequeue(queuedNode)) { - pushNewNode(queuedNode); - } else { - std::uint8_t rollbackExpected = Queued; - const bool resetToIdle = nodeState[queuedNode].compare_exchange_strong( - rollbackExpected, Idle); - assert(resetToIdle); - tlx::unused(resetToIdle); - } + pushNewNode(queuedNode); return; } state = expected; @@ -504,25 +482,18 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & auto finishProcessingNode = [&](node processedNode) { uint8_t expected = Processing; + if (nodeState[processedNode].compare_exchange_strong(expected, Idle)) { return; } - assert(expected == Reprocess); - if (!admitNodeRequeue(processedNode)) { - expected = Reprocess; - const bool resetToIdle = - nodeState[processedNode].compare_exchange_strong(expected, Idle); - assert(resetToIdle); - tlx::unused(resetToIdle); - return; - } + assert(expected == Reprocess); expected = Reprocess; - const bool markedQueued = - nodeState[processedNode].compare_exchange_strong(expected, Queued); + const bool markedQueued = nodeState[processedNode].compare_exchange_strong(expected, Queued); assert(markedQueued); tlx::unused(markedQueued); + pushNewNode(processedNode); }; @@ -551,11 +522,16 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & } std::uint8_t expectedState = Queued; - const bool startedProcessing = - nodeState[u].compare_exchange_strong(expectedState, Processing); + const bool startedProcessing = nodeState[u].compare_exchange_strong(expectedState, Processing); assert(startedProcessing); tlx::unused(startedProcessing); + if (moveLimitReached(u)) { + nodePassesSkippedByMoveLimit[tid]++; + finishProcessingNode(u); + continue; + } + index currentCommunity = result[u]; double maxDelta = std::numeric_limits::lowest(); index bestCommunity = none; @@ -626,6 +602,10 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & const int tid = omp_get_thread_num(); moved[tid]++; + const uint32_t previousMoves = + movesPerNode[u].fetch_add(1, std::memory_order_relaxed); + assert(previousMoves < maxMovesPerNode); + if (singletonMove) { // move node to empty community singleton++; movedToSingleton[tid]++; @@ -722,15 +702,15 @@ ParallelLeidenView::MoveStats ParallelLeidenView::parallelMove(const GraphType & const count totalWorked = std::accumulate(totalNodesPerThread.begin(), totalNodesPerThread.end(), static_cast(0)); - const count totalRequeuesBlockedByNodeLimit = - std::accumulate(requeuesBlockedByNodeLimit.begin(), requeuesBlockedByNodeLimit.end(), - static_cast(0)); + const count totalNodePassesSkippedByMoveLimit = + std::accumulate(nodePassesSkippedByMoveLimit.begin(), + nodePassesSkippedByMoveLimit.end(), static_cast(0)); if (Aux::Log::isLogLevelEnabled(Aux::Log::LogLevel::DEBUG)) { tlx::unused(totalWorked); DEBUG("Total worked: ", totalWorked, " Total moved: ", totalMoved, - " Moved to singleton community: ", singleton, - " Requeues blocked by node limit: ", totalRequeuesBlockedByNodeLimit); + " Moved to singleton community: ", singleton, + " Node passes skipped by move limit: ", totalNodePassesSkippedByMoveLimit); } MoveStats stats; stats.moved = totalMoved;