stevensonmt

stevensonmt

Troubleshooting/benchmarking deleting vertex in large graph with libgraph

I’m trying to fix this issue raised for libgraph:
https://github.com/bitwalker/libgraph/issues/80

I think the problem is that the current delete vertex code loops over every entry in the collections of total edges, in edges, and out edges for every deletion. I thought that could be improved by looping over the total edges once and simply reducing into the new collections all at the same time. In my simple test of a million vertices each with 999_998 edges I found very minimal if any real difference in the time it takes to run the deletion compared to the current implementation. Can anyone figure out why my intuition about this problem is wrong? Or alternatively why my test is wrong?

current implementation:

  def delete_vertex(
        %__MODULE__{out_edges: oe, in_edges: ie, edges: em, vertex_identifier: vertex_identifier} =
          g,
        v
      ) do
    vs = g.vertices
    ls = g.vertex_labels

    with v_id <- vertex_identifier.(v),
         true <- Map.has_key?(vs, v_id),
         oe <- Map.delete(oe, v_id),
         ie <- Map.delete(ie, v_id),
         vs <- Map.delete(vs, v_id),
         ls <- Map.delete(ls, v_id) do
      oe = for {id, ns} <- oe, do: {id, MapSet.delete(ns, v_id)}, into: %{}
      ie = for {id, ns} <- ie, do: {id, MapSet.delete(ns, v_id)}, into: %{}
      em = for {{id1, id2}, _} = e <- em, v_id != id1 && v_id != id2, do: e, into: %{}
      %__MODULE__{g | vertices: vs, vertex_labels: ls, out_edges: oe, in_edges: ie, edges: em}
    else
      _ -> g
    end
  end

attempt to fix:

  def delete_vertex(
        %__MODULE__{vertices: vs, vertex_labels: ls, out_edges: oe, in_edges: ie, edges: em, vertex_identifier: vertex_identifier} =
          g,
        v
      ) do
   
    with v_id <- vertex_identifier.(v),
         true <- Map.has_key?(vs, v_id),
         oe <- Map.delete(oe, v_id),
         ie <- Map.delete(ie, v_id),
         vs <- Map.delete(vs, v_id),
         ls <- Map.delete(ls, v_id) do
    {em, oe, ie} = 
        em
        |> Enum.reduce({em, oe, ie}, fn 
                                     {{v_id, id2} = k, _}, {e, o, i} -> {Map.delete(e, k), o, Map.update!(i, id2, fn ns -> MapSet.delete(ns, v_id) end)}
                                     {{id1, v_id} = k, _}, {e, o, i} -> {Map.delete(e, k), Map.update!(o, id1, fn ns -> MapSet.delete(ns, v_id) end), i} 
                                      _, acc -> acc 
                       end)

      %__MODULE__{g | vertices: vs, vertex_labels: ls, out_edges: oe, in_edges: ie, edges: em}
    else
      _ -> g
    end
  end

simple test in iex:

g = Graph.new()
vs = 1..1_000_000 |> Enum.to_list()
vs |> Enum.each(fn i -> Graph.add_vertex(g, i) end)
vs |> Enum.zip(Enum.reverse(vs)) |> Enum.each(fn {i,o} -> Graph.add_edge(g, i, o) end)
vs |> Enum.zip(Enum.reverse(vs)) |> Enum.each(fn {i,o} -> Graph.add_edge(g, o, i) end)
:timer.tc(fn -> 1..1_000_000//7 |> Enum.each(fn i -> Graph.delete_vertex(g, i) end) end, :millisecond)

First 9 of 9 Posts Switch mode

MrDoops

MrDoops

Test doesn’t seem quite right.

Maybe try something like

g =
  Graph.new()
  |> Graph.add_edges(
    Enum.map(1..1_000_000, fn i-> {i, 1_000_000 - i} end)
  )

:timer.tc(
  fn ->
    g
    |> Graph.delete_vertex(
      1..1_000_000
      |> Enum.random()
    )
  end,
  :millisecond
)

> {976, #Graph<type: directed, num_vertices: 999896, num_edges: 999998>}

Know that in most graphs implementations in general that performance will be affected depending on how many edges and potentially how connected a component is.

Libgraph is just maps and sets (which are maps also) so will perform at Erlang map implementation thresholds and :erlang.phash2/1.

stevensonmt

stevensonmt

Thanks. Will try that tonight. Actually, I’m not sure that test is going to be the best either. Introducing the Enum.random call during the timed portion could mean the result is unduly influenced by the random algorithm.

I know there’s no improving past a certain limit, but it still seems to me that iterating over 3 maps key by key versus reducing over a single map key by key should be significantly different with a large enough data set.

stevensonmt

stevensonmt

yeah, that was more demonstrative. For a single vertex deletion with the old version I get 1228 msec versus the new version at 45 msec, almost 30x improvement. Thanks for the help.

bartblast

bartblast

Creator of Hologram

Hey @stevensonmt, thanks for working on this! It would greatly help with Hologram’s incremental compilation and live reloading. Hopefully there is also a new release of libgraph. I had to temporarily vendor it in Hologram, since the last release is from 2022.

stevensonmt

stevensonmt

curious what you mean by “vendor” the lib in this context?

bartblast

bartblast

Creator of Hologram

I included it as part of the Hologram package: Vendor libgraph · bartblast/hologram@1241d4f · GitHub

Hologram used an unreleased version with a specific bug fix:

# It seems that libgraph is not maintained - there are pending pull requests and not released commits.
{:libgraph,
  git: "https://github.com/bitwalker/libgraph",
  ref: "460cdfd9a163a533bdab0a160ba7ccf888047927"},

All dependencies in your Hex release must also be Hex releases to make dependency resolution possible.

stevensonmt

stevensonmt

I guess if you’re having to resort to that you could base it on my recent commit to test the performance gain until @bitwalker can review and merge it.

stevensonmt

stevensonmt

Just to revisit this, I don’t think the test in this thread was a valid assessment of the issue. I had missed that both implementations would scale linearly with the number of edges but that only the old implementation was scaling with number of nodes that have any edges. So tons of nodes with tons of edges each yields minimal difference but tons of nodes with few edges each makes a big difference. New implementation in the issue tracker I think now correctly addresses the issue by scaling only with the number of edges involving the node being deleted, independent of the total number of nodes or edges in the graph.

bartblast

bartblast

Creator of Hologram

Great! After the maintainer of libgraph (bitwalker) merges the pull requests I’ll benchmark your updates on a live organism.

— All posts loaded —

Where Next?

Trending in Questions Top

jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
silverdr
Using Phoenix.LiveView.TagEngine as an EEx.Engine is deprecated! To compile HEEx, use Phoenix.LiveView.TagEngine.compile/2 instead. Sta...
New
saveman71
Hello ! We want new/edit form pages to POST/PUT to their own URL rather than the resources REST defaults (post /things, put /things/:id)...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
michallepicki
I am using Oban and occasionally, shortly after a deployment, a handful of jobs can fail because of dependency on other parts of the syst...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement