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

Ditch ordered mapping #141

Merged
merged 13 commits into from Mar 24, 2021
Merged

Ditch ordered mapping #141

merged 13 commits into from Mar 24, 2021

Conversation

darabos
Copy link
Contributor

@darabos darabos commented Feb 11, 2021

The idea (from @xandrew-lynx) being that MappingToOrdered takes up a lot of memory. The tests seem to be passing locally. I haven't measured the impact on memory use yet. I also haven't thought backward compatibility entirely through.

@darabos
Copy link
Contributor Author

darabos commented Feb 11, 2021

I mean they passed before I added the assertion... 😅

@darabos
Copy link
Contributor Author

darabos commented Feb 12, 2021

I did some testing.
image

The red is master, blue is this PR. The memory use decreased a bit, but the runtime increased a bit more. It's just 4 runs each with 8 different random graphs with 10,000,000 vertices and 30,000,000 edges. We compute shortest path on it. (This was one of the benchmark problems I used on the 256 GB test.)

Copy link
Contributor

@xandrew-lynx xandrew-lynx left a comment

Choose a reason for hiding this comment

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

The code looks great to me!

Unfortunately, I guess. Given the benchmark it would have been a better news if I spotted something terrible in this... :)

I can kinda, reluctantly, accept that this can be slower, although even that I find unexpected. But how come we don't see a bigger memory impact? How did you measure memory usage?

@@ -38,12 +38,12 @@ func init() {
defer networkit.DeletePartition(p)
vs := &VertexSet{}
vs.MappingToUnordered = make([]int64, p.NumberOfSubsets())
vs.MappingToOrdered = make(map[int64]SphynxId)
mappingToOrdered := make(map[int64]SphynxId)
ss := p.GetSubsetIdsVector()
defer networkit.DeleteUint64Vector(ss)
for i := range vs.MappingToUnordered {
vs.MappingToUnordered[i] = int64(ss.Get(i))
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we know that ss.Get(i) is monotonous is i? If so, let's add a comment stating this somewhere.

@@ -25,6 +25,7 @@ func doStripDuplicateEdgesFromBundle(es *EdgeBundle) *EdgeBundle {
uniqueBundle.EdgeMapping[i] = id
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we just use i instead of id here? Would that violate some contract? Then I guess we wouldn't need to sort.

rows[j].Src = int64(i)
j++
} else {
i++
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding an assertion for vs1.MappingToUnordered[i] < rows[j].Src here. Or if we are worried it has performance price then maybe just a comment.

rows[j].Dst = int64(i)
j++
} else {
i++
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

if vs.MappingToUnordered[i] == row.Field(idIndex).Int() {
values.Index(i).Set(row.Field(valueIndex))
defined.Index(i).Set(true)
j++
Copy link
Contributor

Choose a reason for hiding this comment

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

Same applies here: add a comment or an assertion in an else branch.


func doVertexSetIntersection(vertexSets []*VertexSet) (intersection *VertexSet, firstEmbedding *EdgeBundle) {
mergeVertices := make(MergeVertexEntrySlice, len(vertexSets[0].MappingToUnordered))
mergeVertices := make([]MergeVertexEntry, len(vertexSets[0].MappingToUnordered))
Copy link
Contributor

Choose a reason for hiding this comment

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

We could avoid this map as well via a multi-merge in a single nested loop. But no need to it in this PR.

return vs.MappingToOrdered
type VertexSet struct {
// This slice contains the Spark IDs in ascending order.
MappingToUnordered []int64
Copy link
Contributor

Choose a reason for hiding this comment

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

Cool! At least VertexSet will not lie about its estimated memory usage.

@darabos
Copy link
Contributor Author

darabos commented Feb 15, 2021

Thanks for the comments! (Hi Gabor!)

I can kinda, reluctantly, accept that this can be slower, although even that I find unexpected. But how come we don't see a bigger memory impact? How did you measure memory usage?

I restarted Sphynx (but not LynxKite). Then I changed the random seed and waited for the average shortest path to get computed in this workspace:
image

Then I looked at "RES" in top. You can see the data I collected in the same benchmarking spreadsheet I used last week in the "no mapping" sheet.

I've set up pprof now. (Click to enlarge.)

Before After
profile002 profile004

While this profile is talking about 1 GB, top shows 6.4 GB RES. I guess that's just fallout from memory management for GC.

@olahg
Copy link
Contributor

olahg commented Feb 16, 2021

Awesome! I don't pretend I understand all of this, but here's some remarks:

  • If it's just the memory usage you're worried about, maybe you could just use the ordered mapping when you need it, and then throw it away without caching it.
  • The upper 32 bits of the original ids are random, but the lower 32 bits are totally predictable. Maybe it could be somehow utilized to save more memory.
  • Sorting in Go is slow. I remember experimenting with it which working an the ranking attribute operation, and a c++ version was about twice as fast as the Go version. Maybe you could just sort lazily, only those vertex sets that really need it.
  • You could sort considerably faster if you implemented your own non-interface-based custom sort. The interface-based solution calls a function for every swap and every comparison, which do their own bounds checking for every access.

@darabos
Copy link
Contributor Author

darabos commented Mar 2, 2021

Thanks for the comments, Gabor!

I checked with pprof and indeed sorting is a significant part of it:
image

But a large part of sorting is the reflection! (In the central area.)

I switched to a fast concurrent sort library (https://github.com/jfcg/sorty) and avoided reflection during sorting. It's much better:

image

I can barely see sorting on this chart now. (There's a bit on the very left.)

This is reflected in the overall speed. The slight slowdown at least is gone.

image

Is this good to merge? Am I missing any compatibility issues?

@darabos darabos changed the title [WIP] Ditch ordered mapping Ditch ordered mapping Mar 2, 2021
Copy link
Contributor

@olahg olahg left a comment

Choose a reason for hiding this comment

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

LGTM

@darabos darabos added this to the LynxKite 4.3.0 milestone Mar 24, 2021
Copy link
Contributor

@xandrew-lynx xandrew-lynx left a comment

Choose a reason for hiding this comment

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

Thanks, looks great!

@darabos darabos merged commit 6083147 into master Mar 24, 2021
@darabos darabos deleted the darabos-no-ordered-mapping branch March 24, 2021 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants