Skip to content

Commit

Permalink
Merge pull request #47 from nextmv-io/ds/fix/alloc
Browse files Browse the repository at this point in the history
Fix memory consumption when multiple models are used
  • Loading branch information
dirkschumacher authored May 20, 2024
2 parents 918df10 + 5e73b56 commit fe8392c
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 29 deletions.
29 changes: 13 additions & 16 deletions solution.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,7 @@ func NewSolution(

random := rand.New(rand.NewSource(m.Random().Int63()))

maxExpressionIndex := -1
for _, expression := range model.expressions {
if expression.Index() > maxExpressionIndex {
maxExpressionIndex = expression.Index()
}
}
nExpressions := len(model.expressions)

solution := &solutionImpl{
model: m,
Expand All @@ -143,8 +138,8 @@ func NewSolution(
slack: make([]float64, 0, nrStops),
start: make([]float64, 0, nrStops),
end: make([]float64, 0, nrStops),
values: make([][]float64, maxExpressionIndex+1),
cumulativeValues: make([][]float64, maxExpressionIndex+1),
values: make(map[int][]float64, nExpressions),
cumulativeValues: make(map[int][]float64, nExpressions),
stopToPlanUnit: make([]*solutionPlanStopsUnitImpl, nrStops),
constraintStopData: make(map[ModelConstraint][]Copier),
objectiveStopData: make(map[ModelObjective][]Copier),
Expand Down Expand Up @@ -577,12 +572,12 @@ func (s *solutionImpl) addInitialSolution(m Model) error {
type solutionImpl struct {
model Model
scores map[ModelObjective]float64
values [][]float64
values map[int][]float64
objectiveStopData map[ModelObjective][]Copier
constraintStopData map[ModelConstraint][]Copier
objectiveSolutionData map[ModelObjective]Copier
constraintSolutionData map[ModelConstraint]Copier
cumulativeValues [][]float64
cumulativeValues map[int][]float64

// TODO: explore if stopToPlanUnit should rather contain interfaces
stopToPlanUnit []*solutionPlanStopsUnitImpl
Expand Down Expand Up @@ -690,6 +685,7 @@ func (s *solutionImpl) Copy() Solution {
// to the correct size
nrStops := len(s.stop)
nrVehicles := len(s.vehicles)
nrExpressions := len(model.expressions)
ints := make([]int, 5*nrStops+3*nrVehicles)
first, ints := common.CopySliceFrom(ints, s.first)
vehicleIndices, ints := common.CopySliceFrom(ints, s.vehicleIndices)
Expand All @@ -699,12 +695,12 @@ func (s *solutionImpl) Copy() Solution {
next, ints := common.CopySliceFrom(ints, s.next)
stopPosition, ints := common.CopySliceFrom(ints, s.stopPosition)
stop, _ := common.CopySliceFrom(ints, s.stop)
floats := make([]float64, 5*nrStops)
floats := make([]float64, (5+2*nrExpressions)*nrStops)
start, floats := common.CopySliceFrom(floats, s.start)
end, floats := common.CopySliceFrom(floats, s.end)
arrival, floats := common.CopySliceFrom(floats, s.arrival)
slack, floats := common.CopySliceFrom(floats, s.slack)
cumulativeTravelDuration, _ := common.CopySliceFrom(floats, s.cumulativeTravelDuration)
cumulativeTravelDuration, floats := common.CopySliceFrom(floats, s.cumulativeTravelDuration)
solution := &solutionImpl{
arrival: arrival,
slack: slack,
Expand All @@ -713,7 +709,7 @@ func (s *solutionImpl) Copy() Solution {
constraintSolutionData: make(map[ModelConstraint]Copier, len(s.constraintSolutionData)),
objectiveSolutionData: make(map[ModelObjective]Copier, len(s.objectiveSolutionData)),
cumulativeTravelDuration: cumulativeTravelDuration,
cumulativeValues: make([][]float64, len(s.cumulativeValues)),
cumulativeValues: make(map[int][]float64, len(s.cumulativeValues)),
stopToPlanUnit: make([]*solutionPlanStopsUnitImpl, len(s.stopToPlanUnit)),
end: end,
first: first,
Expand All @@ -725,7 +721,7 @@ func (s *solutionImpl) Copy() Solution {
start: start,
stop: stop,
stopPosition: stopPosition,
values: make([][]float64, len(s.values)),
values: make(map[int][]float64, len(s.values)),
vehicleIndices: vehicleIndices,
random: random,
fixedPlanUnits: newSolutionPlanUnitCollectionBaseImpl(
Expand All @@ -752,8 +748,9 @@ func (s *solutionImpl) Copy() Solution {
}

for _, expression := range model.expressions {
solution.cumulativeValues[expression.Index()] = slices.Clone(s.cumulativeValues[expression.Index()])
solution.values[expression.Index()] = slices.Clone(s.values[expression.Index()])
eIndex := expression.Index()
solution.cumulativeValues[eIndex], floats = common.CopySliceFrom(floats, s.cumulativeValues[eIndex])
solution.values[eIndex], floats = common.CopySliceFrom(floats, s.values[eIndex])
}

for _, constraint := range model.constraintsWithStopUpdater {
Expand Down
12 changes: 2 additions & 10 deletions solution_move_stops_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

package nextroute

import (
"github.com/nextmv-io/nextroute/common"
)

// SolutionMoveStopsGeneratorChannel generates all possible moves for a given
// vehicle and plan unit.
//
Expand Down Expand Up @@ -101,12 +97,8 @@ func SolutionMoveStopsGenerator(
preAllocatedMoveContainer *PreAllocatedMoveContainer,
shouldStop func() bool,
) {
source := common.Map(stops, func(stop SolutionStop) SolutionStop {
return stop
})
target := common.Map(vehicle.SolutionStops(), func(stop SolutionStop) SolutionStop {
return stop
})
source := stops
target := vehicle.SolutionStops()
m := preAllocatedMoveContainer.singleStopPosSolutionMoveStop
m.(*solutionMoveStopsImpl).reset()
m.(*solutionMoveStopsImpl).planUnit = planUnit
Expand Down
4 changes: 1 addition & 3 deletions solution_plan_stops_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ func (p *solutionPlanStopsUnitImpl) SolutionStops() SolutionStops {
}

func (p *solutionPlanStopsUnitImpl) solutionStopsImpl() []SolutionStop {
solutionStops := make([]SolutionStop, len(p.solutionStops))
copy(solutionStops, p.solutionStops)
return solutionStops
return p.solutionStops
}

func (p *solutionPlanStopsUnitImpl) IsPlanned() bool {
Expand Down
76 changes: 76 additions & 0 deletions solution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// © 2019-present nextmv.io inc

package nextroute_test

import (
"testing"
"time"

"github.com/nextmv-io/nextroute"
)

func BenchmarkAllocationsSolution(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
model, err := createModel(singleVehiclePlanSequenceModel())
if err != nil {
b.Error(err)
}

maximum := nextroute.NewVehicleTypeDurationExpression(
"maximum duration",
3*time.Minute,
)
expression := nextroute.NewStopExpression("test", 2.0)

cnstr, err := nextroute.NewMaximum(expression, maximum)
if err != nil {
b.Error(err)
}

err = model.AddConstraint(cnstr)
if err != nil {
b.Error(err)
}
b.StartTimer()
_, err = nextroute.NewSolution(model)
if err != nil {
b.Fatal(err)
}
}
}

// TestLimitAllocations tests the number of allocations in the solution creation.
// We want to ensure that the number of allocations is limited and does not grow
// accidentally.
func TestLimitAllocations(t *testing.T) {
model, err := createModel(singleVehiclePlanSequenceModel())
if err != nil {
t.Error(err)
}

maximum := nextroute.NewVehicleTypeDurationExpression(
"maximum duration",
3*time.Minute,
)
expression := nextroute.NewStopExpression("test", 2.0)

cnstr, err := nextroute.NewMaximum(expression, maximum)
if err != nil {
t.Error(err)
}

err = model.AddConstraint(cnstr)
if err != nil {
t.Error(err)
}
allocs := testing.AllocsPerRun(2, func() {
_, err = nextroute.NewSolution(model)
if err != nil {
t.Fatal(err)
}
})
if allocs > 66 {
t.Errorf("expected 66 allocations, got %v", allocs)
}
}

0 comments on commit fe8392c

Please sign in to comment.