Faster report merging through mutating objects

When we know we have the only reference to a Report or Node object we
can avoid copying the data to change it. Add "Unsafe" variants of
various Merge operations which mutate the receiver, and a new Merger
which takes advantage of them.
This commit is contained in:
Bryan Boreham
2018-06-22 09:54:24 +00:00
parent 6545ba1835
commit 1706746a32
5 changed files with 67 additions and 11 deletions

View File

@@ -72,6 +72,15 @@ func BenchmarkReportMerge(b *testing.B) {
}
}
func BenchmarkReportFastMerge(b *testing.B) {
reports := upgradeReports(readReportFiles(b, *benchReportPath))
merger := NewFastMerger()
b.ResetTimer()
for i := 0; i < b.N; i++ {
merger.Merge(reports)
}
}
func getReport(b *testing.B) report.Report {
r := fixture.Report
if *benchReportPath != "" {

View File

@@ -60,3 +60,21 @@ func (smartMerger) Merge(reports []report.Report) report.Report {
}
return <-c
}
type fastMerger struct{}
// NewFastMerger makes a Merger which merges together reports, mutating the one we are building up
func NewFastMerger() Merger {
return fastMerger{}
}
func (fastMerger) Merge(reports []report.Report) report.Report {
rpt := report.MakeReport()
id := murmur3.New64()
for _, r := range reports {
rpt.UnsafeMerge(r)
id.Write([]byte(r.ID))
}
rpt.ID = fmt.Sprintf("%x", id.Sum64())
return rpt
}

View File

@@ -52,6 +52,10 @@ func BenchmarkDumbMerger(b *testing.B) {
benchmarkMerger(b, app.MakeDumbMerger())
}
func BenchmarkFastMerger(b *testing.B) {
benchmarkMerger(b, app.NewFastMerger())
}
const numHosts = 15
func benchmarkMerger(b *testing.B, merger app.Merger) {

View File

@@ -305,16 +305,21 @@ func (r Report) Copy() Report {
// original is not modified.
func (r Report) Merge(other Report) Report {
newReport := r.Copy()
newReport.DNS = newReport.DNS.Merge(other.DNS)
newReport.Sampling = newReport.Sampling.Merge(other.Sampling)
newReport.Window = newReport.Window + other.Window
newReport.Plugins = newReport.Plugins.Merge(other.Plugins)
newReport.WalkPairedTopologies(&other, func(ourTopology, theirTopology *Topology) {
*ourTopology = ourTopology.Merge(*theirTopology)
})
newReport.UnsafeMerge(other)
return newReport
}
// UnsafeMerge merges another Report into the receiver. The original is modified.
func (r *Report) UnsafeMerge(other Report) {
r.DNS = r.DNS.Merge(other.DNS)
r.Sampling = r.Sampling.Merge(other.Sampling)
r.Window = r.Window + other.Window
r.Plugins = r.Plugins.Merge(other.Plugins)
r.WalkPairedTopologies(&other, func(ourTopology, theirTopology *Topology) {
ourTopology.UnsafeMerge(*theirTopology)
})
}
// WalkTopologies iterates through the Topologies of the report,
// potentially modifying them
func (r *Report) WalkTopologies(f func(*Topology)) {

View File

@@ -163,6 +163,21 @@ func (t Topology) Merge(other Topology) Topology {
}
}
// UnsafeMerge merges the other object into this one, modifying the original.
func (t *Topology) UnsafeMerge(other Topology) {
if t.Shape == "" {
t.Shape = other.Shape
}
if t.Label == "" {
t.Label, t.LabelPlural = other.Label, other.LabelPlural
}
t.Nodes.UnsafeMerge(other.Nodes)
t.Controls = t.Controls.Merge(other.Controls)
t.MetadataTemplates = t.MetadataTemplates.Merge(other.MetadataTemplates)
t.MetricTemplates = t.MetricTemplates.Merge(other.MetricTemplates)
t.TableTemplates = t.TableTemplates.Merge(other.TableTemplates)
}
// Nodes is a collection of nodes in a topology. Keys are node IDs.
// TODO(pb): type Topology map[string]Node
type Nodes map[string]Node
@@ -186,14 +201,19 @@ func (n Nodes) Merge(other Nodes) Nodes {
return n
}
cp := n.Copy()
cp.UnsafeMerge(other)
return cp
}
// UnsafeMerge merges the other object into this one, modifying the original.
func (n *Nodes) UnsafeMerge(other Nodes) {
for k, v := range other {
if n, ok := cp[k]; ok { // don't overwrite
cp[k] = v.Merge(n)
if existing, ok := (*n)[k]; ok { // don't overwrite
(*n)[k] = v.Merge(existing)
} else {
cp[k] = v
(*n)[k] = v
}
}
return cp
}
// Validate checks the topology for various inconsistencies.