5

I have a graph ( adjacency_list (listS, vecS, bidirectionalS, VertexVal) ) in which I need to delete 100,000+ nodes. Each node also contains a structure of 2 64-bit integers and another 64-bit integer. The guid check that happens in the code below is checking 1st integer in the structure.

On my laptop ( i7 2.7GHz, 16GB RAM ) it takes about 88 seconds according to VTune.

Following is how I delete the nodes:

  vertex_iterator vi,vi_end;
  boost::tie(vi, vi_end) = boost::vertices(m_graph);
  while (vi!=vi_end) {
    if (m_graph[*vi].guid.part1 == 0) {
      boost::remove_vertex(*vi,m_graph);
      boost::tie(vi, vi_end) = boost::vertices(m_graph);
    } else 
      ++vi;
  }

Vtune shows that the boost::remove_vertex() call takes 88.145 seconds. Is there a more efficient way to delete these vertices? Vtune data for boost::remove_vertex_dispatch(). This is the breakdown of the 88 seconds

Dula
  • 1,404
  • 1
  • 14
  • 29
  • and the graph type is... (you should specify the relevant information. (Bundled) properties, container selections, they'll have a huge impact) – sehe Feb 06 '15 at 23:07
  • adjacency_list with (listS, vecS, bidirectionalS, VertexVal.) – Dula Feb 06 '15 at 23:09
  • Each node also contains a Vertex Value which is a structure that has 3 64-bit integers. – Dula Feb 06 '15 at 23:11
  • The guid is a structure with 2 64-bit integers - 2/3 integers I mentioned above. I'll edit the question - problem solved :) – Dula Feb 06 '15 at 23:16

3 Answers3

9

In your removal branch you re-tie() the iterators:

boost::tie(vi, vi_end) = boost::vertices(m_graph);

This will cause the loop to restart every time you restart the loop. This is exactly Schlemiel The Painter.

I'll find out whether you can trust remove_vertex not triggering a reallocation. If so, it's easily fixed. Otherwise, you'd want an indexer-based loop instead of iterator-based. Or you might be able to work on the raw container (it's a private member, though, as I remember).

Update Using vecS as the container for vertices is going to cause bad performance here:

If the VertexList template parameter of the adjacency_list was vecS, then all vertex descriptors, edge descriptors, and iterators for the graph are invalidated by this operation. <...> If you need to make frequent use of the remove_vertex() function the listS selector is a much better choice for the VertexList template parameter.


This small benchmark test.cpp compares:

  • with -DSTABLE_IT (listS)

    $ ./stable 
    Generated 100000 vertices and 5000 edges in 14954ms
    The graph has a cycle? false
    starting selective removal...
    Done in 0ms
    After: 99032 vertices and 4916 edges
    
  • without -DSTABLE_IT (vecS)

    $ ./unstable 
    Generated 100000 vertices and 5000 edges in 76ms
    The graph has a cycle? false
    starting selective removal...
    Done in 396ms
    After: 99032 vertices and 4916 edges
    
  • using filtered_graph (thanks @cv_and_he in the comments)

    Generated 100000 vertices and 5000 edges in 15ms
    The graph has a cycle? false
    starting selective removal...
    Done in 0ms
    After: 99032 vertices and 4916 edges
    Done in 13ms
    

You can clearly see that removal is much faster for listS but generating is much slower.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    The reason had to be done was because when `remove_vertex` was called all iterators were invalidated. – Dula Feb 06 '15 at 23:29
  • `vecS` had to be used because I had to detect cyclic dependencies. [boost example](http://www.boost.org/doc/libs/1_57_0/libs/graph/doc/file_dependency_example.html) This only worked with `vecS` – Dula Feb 06 '15 at 23:36
  • @Dula for large graphs adding vertices is gonna be much slower, but my test with a random graph already confirm that `listS` is ***much*** faster during the removal phase. In general, the algorithms that work "naturally" with `vecS` still work with `listS` iff you prover the `vertex_index_t` propertymap. I'll have a look later – sehe Feb 06 '15 at 23:48
  • I think slower generation is OK in my use case. Only the deletes happen in bulk. I'll try and modify the graph to use `listS` and post resulting data. – Dula Feb 07 '15 at 00:09
  • @Dula I've added a benchmark confirming the shift in performance characteristics with `vecS` vs. `listS` **updated** with `depth_first_search` to detect cycles (also with `listS`): [**Live On Coliru** (smaller dataset)](http://coliru.stacked-crooked.com/a/a33851249b67e8ef) – sehe Feb 07 '15 at 00:28
  • That's awesome...I'm not on the C++11 standard yet. But I'll try to convert them. Thank you! – Dula Feb 07 '15 at 00:35
  • You don't need c++11. I'm fixing a glitch where I measured the edge deletion too. Hang on: **[Live On Coliru](http://coliru.stacked-crooked.com/a/ce4e4507d964205b)** (fixed answer) – sehe Feb 07 '15 at 00:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70438/discussion-between-sehe-and-dula). – sehe Feb 07 '15 at 00:38
  • @Dula Here's a c++03 version of the bench/demo code **[Live On Coliru](http://coliru.stacked-crooked.com/a/e60c3cdccc430b1c)** – sehe Feb 07 '15 at 00:47
  • 1
    @Dula I remember reading that a good way to "remove" lots of vertices from a graph with a vector vertex_list is creating a filtered_graph that ignores the vertices that are in a set and then simply adding the vertices you want to remove to that set. [Here](http://coliru.stacked-crooked.com/a/0423e864b4573069) is my attemp at implementing it (be sure to check the hasher since I know very little about unordered_sets and it may very well be wrong). The removing part is obviously fast, but I'm not sure how the performance of accessing the filtered_graph after will be (it may be far slower). – llonesmiz Feb 07 '15 at 05:47
  • @cv_and_he That's clever! I've added the timings (after making sure that the predicate behaved the same way :)). The only caveats I can think of is that some algorithms will refuse to work on filtered graphs (possibly due to non-mutable properties?) and of course the increased memory requirements. But I think it's a good way to avoid [adapting custom data structures.](http://www.boost.org/doc/libs/1_57_0/libs/graph/doc/leda_conversion.html) – sehe Feb 07 '15 at 09:07
3

I was able to successfully serialize the graph using Boost serialization routines into a string, parse the string and remove the nodes I didn't need and de-serialize the modified string. For 200,000 total nodes in graph and 100,000 that needs to be deleted I was able to successfully finish the operation in less than 2 seconds.

For my particular use-case each vertex has 3 64bit integers. When it needs to be deleted, I mark 2 of those integers as 0s. A valid vertex would never have a 0. When the point comes to clean up the graph - to delete the "deleted" vertices, I follow the above logic.

In the code below removeDeletedNodes() does the string parsing and removing the vertices and mapping the edge numbers.

enter image description here

Dula
  • 1,404
  • 1
  • 14
  • 29
  • Nice. I'd be interested to know whether `filtered_graph` would be serializable (I suppose it would). In that case it might be faster because of the reduced need for string manipulation. – sehe Mar 11 '15 at 01:33
0

It would be interesting to see more of the Vtune data.

My experience has been that the default Microsoft allocator can be a big bottleneck when deleting tens of thousands of small objects. Does your Vtune graph show a lot of time in delete or free?

If so, consider switching to a third-party allocator. Nedmalloc is said to be good: http://www.nedprod.com/programs/portable/nedmalloc/

Google has one, tcmalloc, which is very well regarded and much faster than the built-in allocators on almost every platform. https://code.google.com/p/gperftools/ tcmalloc is not a drop-in for Windows.

StilesCrisis
  • 15,972
  • 4
  • 39
  • 62