Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BENCH] Bron-Kerbosch clique detection #157

Merged
merged 63 commits into from
Oct 19, 2023
Merged

[BENCH] Bron-Kerbosch clique detection #157

merged 63 commits into from
Oct 19, 2023

Conversation

Hromz
Copy link
Contributor

@Hromz Hromz commented Oct 12, 2023

Added a benchmark for the Bron-Kerbosch algorithm. This implementation consists of sparse, dense, custom, and random graphs. Additionally, I added ms. instead of nn and BigO notation. If you think that is inappropriate here, let me know and I will remove it.

@codecov
Copy link

codecov bot commented Oct 12, 2023

Codecov Report

All modified lines are covered by tests ✅

see 4 files with indirect coverage changes

📢 Thoughts on this report? Let us know!.

@bobluppes bobluppes added the performance Benchmarks or performance improvements label Oct 13, 2023
Copy link
Owner

@bobluppes bobluppes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks quite good already, a very extensive set of benchmarks 👍🏻

Comment on lines 35 to 47
std::vector<graaf::edge_id_t> clique{};
clique.reserve((clique_size * (clique_size - 1)) / 2);

// Constructing a clique
for (size_t i{start_vertex}; i < end_vertex; ++i) {
for (size_t j{i + 1}; j < end_vertex; ++j) {
clique.emplace_back(vertices[i], vertices[j]);
}
}

for (auto& edge_t : clique) {
graph.add_edge(edge_t.first, edge_t.second, 1);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the need of storing the edges in the vector clique first? Can we not directly add them to the graph? i.e.

Suggested change
std::vector<graaf::edge_id_t> clique{};
clique.reserve((clique_size * (clique_size - 1)) / 2);
// Constructing a clique
for (size_t i{start_vertex}; i < end_vertex; ++i) {
for (size_t j{i + 1}; j < end_vertex; ++j) {
clique.emplace_back(vertices[i], vertices[j]);
}
}
for (auto& edge_t : clique) {
graph.add_edge(edge_t.first, edge_t.second, 1);
}
// Constructing a clique
for (size_t i{start_vertex}; i < end_vertex; ++i) {
for (size_t j{i + 1}; j < end_vertex; ++j) {
clique.emplace_back(vertices[i], vertices[j]);
graph.add_edge(vertices[i], vertices[j], 1);
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my bad, I overconfused myself.

Comment on lines 50 to 51

static void bron_kerbosh_two_vertices_cliques(benchmark::State& state) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand the code correctly, we create a graph with a linear path of connected vertices:
a -- b -- c -- ...

This was not immediately clear to me when looking at the benchmark name. What do you think about calling this something like bron_kerbosch_linear_graph or bron_kerbosch_two_vertices_per_clique? To make it extra clear we could also add a small comment on top of each benchmark explaining the setup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, that definitely will be a better explanation of what code does.

Comment on lines 60 to 67
for (auto _ : state) {
for (size_t i{0}; i < number_of_edges; ++i) {
graph.add_edge(vertices[i], vertices[i + 1], 1);
}
}

graaf::algorithm::bron_kerbosch(graph);
state.SetComplexityN(state.range(0));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the docs the to-be-benchmarked code should go into the body of this for-loop. I therefore think we should add the edges outside of the loop and call bron_kerbosch inside.

In order to make sure the compiler does not optimize the call to the algorithm away (since the result is unused) it would also be good to add benchmark::DoNotOptimize:

Suggested change
for (auto _ : state) {
for (size_t i{0}; i < number_of_edges; ++i) {
graph.add_edge(vertices[i], vertices[i + 1], 1);
}
}
graaf::algorithm::bron_kerbosch(graph);
state.SetComplexityN(state.range(0));
for (size_t i{0}; i < number_of_edges; ++i) {
graph.add_edge(vertices[i], vertices[i + 1], 1);
}
for (auto _ : state) {
auto result = graaf::algorithm::bron_kerbosch(graph);
benchmark::DoNotOptimize(result);
}
state.SetComplexityN(state.range(0));

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for the other benchmarks, maybe you could also take a look there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright!

Comment on lines 80 to 85
for (size_t i{0}; i < number_of_edges; ++i) {
for (size_t j{i + 1}; j < number_of_edges; ++j) {
if (i != j && !graph.has_edge(vertices[i], vertices[j]))
graph.add_edge(vertices[i], vertices[j], 1);
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at these for loops, would the condition in the if-statement ever evaluate to false? I think this is equivalent to:

for (size_t i{0}; i < number_of_edges; ++i) {
      for (size_t j{i + 1}; j < number_of_edges; ++j) {
        graph.add_edge(vertices[i], vertices[j], 1);
      }
    }

Which in turn looks pretty similar to the add_clique function. Could we therefore simply do:

add_clique(graph, vertices, vertices.front(), number_of_edges);

Copy link
Contributor Author

@Hromz Hromz Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ Oh, how I mised that! Thanks for emphasizing this!

Comment on lines 100 to 101

size_t clique_size = 4;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the clique_size not be equal to 3 if we want to make triangle cliques?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad with naming, basically,  it's a triangle with a dot inside (4 vertex clique).

Comment on lines 150 to 151

size_t clique_size = 25;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also do this in a follow-up, but looking at these parameters it looks like we can pull out the clique_size as a second parameter. Then we might be able to consolidate a few of these benchmarks. I.e.

BENCHMARK(bron_kerbosh_connected_cliques)
    ->Ranges({{100, 10000}, {3, 100}})  // total number of edges, clique size
    ->Unit(benchmark::kMillisecond)
    ->Complexity();

Copy link
Contributor Author

@Hromz Hromz Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want I would like to add this in current PR!

Comment on lines 9 to 14

// Generating random number of vertices (1- 30) for a clique
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> random_clique_size(1,
30);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am always a little hesitant adding such non deterministic benchmarks, as it makes comparing results difficult. It might still be nice to have since we use it to determine the runtime complexity as well, just something to be aware of.

@Hromz Hromz requested a review from bobluppes October 16, 2023 17:58
Copy link
Owner

@bobluppes bobluppes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, LGTM

@bobluppes bobluppes merged commit 58423fc into bobluppes:main Oct 19, 2023
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest-accepted performance Benchmarks or performance improvements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants