lang: Add unification optimizations
This adds a unification optimizations API, and uses it to optimize the embedded provisioner. With these turned on, type unification drops from around 1m45s to 2.5s which is a 40x speedup.
This commit is contained in:
@@ -84,9 +84,10 @@ type LangArgs struct {
|
|||||||
OnlyDownload bool `arg:"--only-download" help:"stop after downloading any missing imports"`
|
OnlyDownload bool `arg:"--only-download" help:"stop after downloading any missing imports"`
|
||||||
Update bool `arg:"--update" help:"update all dependencies to the latest versions"`
|
Update bool `arg:"--update" help:"update all dependencies to the latest versions"`
|
||||||
|
|
||||||
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`
|
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`
|
||||||
SkipUnify bool `arg:"--skip-unify" help:"skip type unification"`
|
SkipUnify bool `arg:"--skip-unify" help:"skip type unification"`
|
||||||
UnifySolver *string `arg:"--unify-name" help:"pick a specific unification solver"`
|
UnifySolver *string `arg:"--unify-name" help:"pick a specific unification solver"`
|
||||||
|
UnifyOptimizations []string `arg:"--unify-optimizations" help:"list of unification optimizations to request (experts only)"`
|
||||||
|
|
||||||
Depth int `arg:"--depth" default:"-1" help:"max recursion depth limit (-1 is unlimited)"`
|
Depth int `arg:"--depth" default:"-1" help:"max recursion depth limit (-1 is unlimited)"`
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/lang/embedded"
|
"github.com/purpleidea/mgmt/lang/embedded"
|
||||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||||
"github.com/purpleidea/mgmt/lang/types"
|
"github.com/purpleidea/mgmt/lang/types"
|
||||||
|
"github.com/purpleidea/mgmt/lang/unification/simplesolver" // TODO: remove me!
|
||||||
"github.com/purpleidea/mgmt/util"
|
"github.com/purpleidea/mgmt/util"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
"github.com/purpleidea/mgmt/util/password"
|
"github.com/purpleidea/mgmt/util/password"
|
||||||
@@ -383,6 +384,12 @@ func (obj *provisioner) Customize(a interface{}) (*cli.RunArgs, error) {
|
|||||||
|
|
||||||
// Make any changes here that we want to...
|
// Make any changes here that we want to...
|
||||||
runArgs.RunLang.SkipUnify = true // speed things up for known good code
|
runArgs.RunLang.SkipUnify = true // speed things up for known good code
|
||||||
|
name := simplesolver.Name
|
||||||
|
// TODO: Remove these optimizations when the solver is faster overall.
|
||||||
|
runArgs.RunLang.UnifySolver = &name
|
||||||
|
runArgs.RunLang.UnifyOptimizations = []string{
|
||||||
|
simplesolver.OptimizationSkipFuncCmp,
|
||||||
|
}
|
||||||
libConfig.TmpPrefix = true
|
libConfig.TmpPrefix = true
|
||||||
libConfig.NoPgp = true
|
libConfig.NoPgp = true
|
||||||
|
|
||||||
|
|||||||
@@ -268,6 +268,10 @@ func (obj *GAPI) Cli(info *gapi.Info) (*gapi.Deploy, error) {
|
|||||||
if name := args.UnifySolver; name != nil && *name != "" {
|
if name := args.UnifySolver; name != nil && *name != "" {
|
||||||
unificationStrategy[unification.StrategyNameKey] = *name
|
unificationStrategy[unification.StrategyNameKey] = *name
|
||||||
}
|
}
|
||||||
|
if len(args.UnifyOptimizations) > 0 {
|
||||||
|
// TODO: use a query string parser instead?
|
||||||
|
unificationStrategy[unification.StrategyOptimizationsKey] = strings.Join(args.UnifyOptimizations, ",")
|
||||||
|
}
|
||||||
|
|
||||||
if !args.SkipUnify {
|
if !args.SkipUnify {
|
||||||
// apply type unification
|
// apply type unification
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ func (obj *Lang) Init(ctx context.Context) error {
|
|||||||
|
|
||||||
// apply type unification
|
// apply type unification
|
||||||
logf := func(format string, v ...interface{}) {
|
logf := func(format string, v ...interface{}) {
|
||||||
|
// TODO: Remove the masked logger here when unification is clean!
|
||||||
if obj.Debug { // unification only has debug messages...
|
if obj.Debug { // unification only has debug messages...
|
||||||
obj.Logf("unification: "+format, v...)
|
obj.Logf("unification: "+format, v...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ const (
|
|||||||
|
|
||||||
// StrategyNameKey is the string key used when choosing a solver name.
|
// StrategyNameKey is the string key used when choosing a solver name.
|
||||||
StrategyNameKey = "name"
|
StrategyNameKey = "name"
|
||||||
|
|
||||||
|
// StrategyOptimizationsKey is the string key used to tell the solver
|
||||||
|
// about the specific optimizations you'd like to request. The format
|
||||||
|
// can be specific to each solver.
|
||||||
|
StrategyOptimizationsKey = "optimizations"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init contains some handles that are used to initialize every solver. Each
|
// Init contains some handles that are used to initialize every solver. Each
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ package simplesolver
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||||
"github.com/purpleidea/mgmt/lang/types"
|
"github.com/purpleidea/mgmt/lang/types"
|
||||||
@@ -43,6 +45,17 @@ const (
|
|||||||
// Name is the prefix for our solver log messages.
|
// Name is the prefix for our solver log messages.
|
||||||
Name = "simple"
|
Name = "simple"
|
||||||
|
|
||||||
|
// OptimizationSkipFuncCmp is the magic flag name to include the skip
|
||||||
|
// func cmp optimization which can speed up some simple programs. If
|
||||||
|
// this is specified, then OptimizationHeuristicalDrop is redundant.
|
||||||
|
OptimizationSkipFuncCmp = "skip-func-cmp"
|
||||||
|
|
||||||
|
// OptimizationHeuristicalDrop is the magic flag name to include to tell
|
||||||
|
// the solver to drop some func compares. This is a less aggressive form
|
||||||
|
// of OptimizationSkipFuncCmp. This is redundant if
|
||||||
|
// OptimizationSkipFuncCmp is true.
|
||||||
|
OptimizationHeuristicalDrop = "heuristical-drop"
|
||||||
|
|
||||||
// AllowRecursion specifies whether we're allowed to use the recursive
|
// AllowRecursion specifies whether we're allowed to use the recursive
|
||||||
// solver or not. It uses an absurd amount of memory, and might hang
|
// solver or not. It uses an absurd amount of memory, and might hang
|
||||||
// your system if a simple solution doesn't exist.
|
// your system if a simple solution doesn't exist.
|
||||||
@@ -71,6 +84,19 @@ type SimpleInvariantSolver struct {
|
|||||||
|
|
||||||
Debug bool
|
Debug bool
|
||||||
Logf func(format string, v ...interface{})
|
Logf func(format string, v ...interface{})
|
||||||
|
|
||||||
|
// skipFuncCmp tells the solver to skip the slow loop entirely. This may
|
||||||
|
// prevent some correct programs from completing type unification.
|
||||||
|
skipFuncCmp bool
|
||||||
|
|
||||||
|
// heuristicalDrop tells the solver to drop some func compares. This was
|
||||||
|
// determined heuristically and needs checking to see if it's even a
|
||||||
|
// sensible algorithmic approach. This is a less aggressive form of
|
||||||
|
// skipFuncCmp. This is redundant if skipFuncCmp is true.
|
||||||
|
heuristicalDrop bool
|
||||||
|
|
||||||
|
// zTotal is a heuristic counter to measure the size of the slow loop.
|
||||||
|
zTotal int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init contains some handles that are used to initialize the solver.
|
// Init contains some handles that are used to initialize the solver.
|
||||||
@@ -80,6 +106,22 @@ func (obj *SimpleInvariantSolver) Init(init *unification.Init) error {
|
|||||||
obj.Debug = init.Debug
|
obj.Debug = init.Debug
|
||||||
obj.Logf = init.Logf
|
obj.Logf = init.Logf
|
||||||
|
|
||||||
|
optimizations, exists := init.Strategy[unification.StrategyOptimizationsKey]
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// TODO: use a query string parser instead?
|
||||||
|
for _, x := range strings.Split(optimizations, ",") {
|
||||||
|
if x == OptimizationSkipFuncCmp {
|
||||||
|
obj.skipFuncCmp = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if x == OptimizationHeuristicalDrop {
|
||||||
|
obj.heuristicalDrop = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,7 +688,12 @@ Loop:
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// is there another EqualityWrapFuncInvariant with the same Expr1 pointer?
|
// is there another EqualityWrapFuncInvariant with the same Expr1 pointer?
|
||||||
for _, fn := range fnInvariants {
|
fnDone := make(map[int]struct{})
|
||||||
|
for z, fn := range fnInvariants {
|
||||||
|
if obj.skipFuncCmp {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
obj.zTotal++
|
||||||
// XXX: I think we're busy in this loop a lot.
|
// XXX: I think we're busy in this loop a lot.
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -693,6 +740,7 @@ Loop:
|
|||||||
eqInvariants = append(eqInvariants, newEq)
|
eqInvariants = append(eqInvariants, newEq)
|
||||||
// TODO: add it as a generator instead?
|
// TODO: add it as a generator instead?
|
||||||
equalities = append(equalities, newEq)
|
equalities = append(equalities, newEq)
|
||||||
|
fnDone[z] = struct{}{} // XXX: heuristical drop
|
||||||
}
|
}
|
||||||
|
|
||||||
// both solved or both unsolved we skip
|
// both solved or both unsolved we skip
|
||||||
@@ -706,6 +754,7 @@ Loop:
|
|||||||
solved[rhsExpr] = lhsTyp // yay, we learned something!
|
solved[rhsExpr] = lhsTyp // yay, we learned something!
|
||||||
//used = append(used, i) // mark equality as used up when complete!
|
//used = append(used, i) // mark equality as used up when complete!
|
||||||
obj.Logf("%s: solved partial rhs func arg equality", Name)
|
obj.Logf("%s: solved partial rhs func arg equality", Name)
|
||||||
|
fnDone[z] = struct{}{} // XXX: heuristical drop
|
||||||
} else if err := newTyp.Cmp(lhsTyp); err != nil {
|
} else if err := newTyp.Cmp(lhsTyp); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial rhs func arg equality")
|
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial rhs func arg equality")
|
||||||
}
|
}
|
||||||
@@ -726,6 +775,7 @@ Loop:
|
|||||||
solved[lhsExpr] = rhsTyp // yay, we learned something!
|
solved[lhsExpr] = rhsTyp // yay, we learned something!
|
||||||
//used = append(used, i) // mark equality as used up when complete!
|
//used = append(used, i) // mark equality as used up when complete!
|
||||||
obj.Logf("%s: solved partial lhs func arg equality", Name)
|
obj.Logf("%s: solved partial lhs func arg equality", Name)
|
||||||
|
fnDone[z] = struct{}{} // XXX: heuristical drop
|
||||||
} else if err := newTyp.Cmp(rhsTyp); err != nil {
|
} else if err := newTyp.Cmp(rhsTyp); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial lhs func arg equality")
|
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial lhs func arg equality")
|
||||||
}
|
}
|
||||||
@@ -756,6 +806,7 @@ Loop:
|
|||||||
eqInvariants = append(eqInvariants, newEq)
|
eqInvariants = append(eqInvariants, newEq)
|
||||||
// TODO: add it as a generator instead?
|
// TODO: add it as a generator instead?
|
||||||
equalities = append(equalities, newEq)
|
equalities = append(equalities, newEq)
|
||||||
|
fnDone[z] = struct{}{} // XXX: heuristical drop
|
||||||
}
|
}
|
||||||
|
|
||||||
// both solved or both unsolved we skip
|
// both solved or both unsolved we skip
|
||||||
@@ -769,6 +820,7 @@ Loop:
|
|||||||
solved[rhsExpr] = lhsTyp // yay, we learned something!
|
solved[rhsExpr] = lhsTyp // yay, we learned something!
|
||||||
//used = append(used, i) // mark equality as used up when complete!
|
//used = append(used, i) // mark equality as used up when complete!
|
||||||
obj.Logf("%s: solved partial rhs func return equality", Name)
|
obj.Logf("%s: solved partial rhs func return equality", Name)
|
||||||
|
fnDone[z] = struct{}{} // XXX: heuristical drop
|
||||||
} else if err := newTyp.Cmp(lhsTyp); err != nil {
|
} else if err := newTyp.Cmp(lhsTyp); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial rhs func return equality")
|
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial rhs func return equality")
|
||||||
}
|
}
|
||||||
@@ -789,6 +841,7 @@ Loop:
|
|||||||
solved[lhsExpr] = rhsTyp // yay, we learned something!
|
solved[lhsExpr] = rhsTyp // yay, we learned something!
|
||||||
//used = append(used, i) // mark equality as used up when complete!
|
//used = append(used, i) // mark equality as used up when complete!
|
||||||
obj.Logf("%s: solved partial lhs func return equality", Name)
|
obj.Logf("%s: solved partial lhs func return equality", Name)
|
||||||
|
fnDone[z] = struct{}{} // XXX: heuristical drop
|
||||||
} else if err := newTyp.Cmp(rhsTyp); err != nil {
|
} else if err := newTyp.Cmp(rhsTyp); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial lhs func return equality")
|
return nil, errwrap.Wrapf(err, "can't unify, invariant illogicality with partial lhs func return equality")
|
||||||
}
|
}
|
||||||
@@ -800,6 +853,21 @@ Loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // end big slow loop
|
||||||
|
if obj.heuristicalDrop {
|
||||||
|
fnDoneList := []int{}
|
||||||
|
for k := range fnDone {
|
||||||
|
fnDoneList = append(fnDoneList, k)
|
||||||
|
}
|
||||||
|
sort.Ints(fnDoneList)
|
||||||
|
|
||||||
|
for z := len(fnDoneList) - 1; z >= 0; z-- {
|
||||||
|
zx := fnDoneList[z] // delete index that was marked as done!
|
||||||
|
fnInvariants = append(fnInvariants[:zx], fnInvariants[zx+1:]...)
|
||||||
|
if obj.Debug {
|
||||||
|
obj.Logf("zTotal: %d, had: %d, removing: %d", obj.zTotal, len(fnInvariants), len(fnDoneList))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// can we solve anything?
|
// can we solve anything?
|
||||||
@@ -1279,6 +1347,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
solutions = append(solutions, invar)
|
solutions = append(solutions, invar)
|
||||||
}
|
}
|
||||||
|
obj.Logf("zTotal: %d", obj.zTotal)
|
||||||
return &unification.InvariantSolution{
|
return &unification.InvariantSolution{
|
||||||
Solutions: solutions,
|
Solutions: solutions,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user