diff --git a/compiler.go b/compiler.go index 9cae996..9ec07e6 100644 --- a/compiler.go +++ b/compiler.go @@ -15,6 +15,8 @@ const ( ReduceNesting Option = "reduce_nesting" ConstantFolding Option = "constant_folding" + ContextBasedReordering Option = "context_based_reordering" + Debug Option = "debug" ReportEvent Option = "report_event" InfixNotation Option = "infix_notation" @@ -253,24 +255,45 @@ func parentNode(e *Expr, idx int16) (*node, int16) { } func optimizeReordering(cc *CompileConfig, root *astNode) { - for _, child := range root.children { - optimizeReordering(cc, child) - } - calculateNodeCosts(cc, root) + var helper func(curt, parent *astNode) - if !isBoolOpNode(root.node) { - return + helper = func(curt, parent *astNode) { + for _, child := range curt.children { + helper(child, curt) + } + + calculateNodeCosts(cc, curt, parent) + + if !isBoolOpNode(curt.node) { + return + } + + // reordering child nodes based on node cost + sort.SliceStable(curt.children, func(i, j int) bool { + return curt.children[i].cost < curt.children[j].cost + }) } - // reordering child nodes based on node cost - sort.SliceStable(root.children, func(i, j int) bool { - return root.children[i].cost < root.children[j].cost - }) + helper(root, nil) } -func calculateNodeCosts(conf *CompileConfig, root *astNode) { - children := root.children +func calculateNodeCosts(conf *CompileConfig, curt, parent *astNode) { + n := curt.node + children := curt.children + nodeType := n.flag & nodeTypeMask + + contextCostEnabled := conf.CompileOptions[ContextBasedReordering] && + parent != nil && isBoolOpNode(parent.node) + + if contextCostEnabled && (nodeType == fastOperator || nodeType == selector) { + identifier := getContextCostIdentifier(parent, curt, children) + if score, exist := conf.CostsMap[identifier]; exist { + curt.cost = score + return + } + } + const ( loops float64 = 1 inlinedCall float64 = 1 @@ -283,9 +306,6 @@ func calculateNodeCosts(conf *CompileConfig, root *astNode) { childrenCost float64 ) - n := root.node - nodeType := n.flag & nodeTypeMask - // base cost switch nodeType { case constant: @@ -305,9 +325,17 @@ func calculateNodeCosts(conf *CompileConfig, root *astNode) { } // operation cost - if nodeType == selector || - nodeType == operator || - nodeType == fastOperator { + switch nodeType { + case operator, fastOperator: + var found bool + if contextCostEnabled { + identifier := getContextCostIdentifier(parent, curt, nil) + operationCost, found = conf.CostsMap[identifier] + } + if !found { + operationCost = conf.getCosts(nodeType, n.value.(string)) + } + case selector: operationCost = conf.getCosts(nodeType, n.value.(string)) } @@ -319,7 +347,36 @@ func calculateNodeCosts(conf *CompileConfig, root *astNode) { } } - root.cost = baseCost + operationCost + childrenCost + curt.cost = baseCost + operationCost + childrenCost +} + +func getContextCostIdentifier(parent *astNode, curt *astNode, children []*astNode) string { + var tree Tree + var appendChildren = func(parentIdx int, children ...*astNode) { + if parentIdx != -1 { + tree[parentIdx].ChildCnt = len(children) + tree[parentIdx].ChildIdx = len(tree) + } + + for _, c := range children { + n := c.node + tree = append(tree, TreeNode{ + NodeType: NodeType(n.getNodeType()), + Value: n.value, + Idx: len(tree), + ChildIdx: -1, + ParentIdx: parentIdx, + }) + } + } + + appendChildren(-1, parent) + appendChildren(0, curt) + if len(children) != 0 { + appendChildren(1, children...) + } + + return tree.DumpCode(false) } func optimizeConstantFolding(cc *CompileConfig, root *astNode) { diff --git a/compiler_test.go b/compiler_test.go index f475fdf..cce543e 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -836,7 +836,7 @@ func TestReordering(t *testing.T) { optimizeFastEvaluation(cc, ast) } - calculateNodeCosts(cc, ast) + calculateNodeCosts(cc, ast, nil) optimizeReordering(cc, ast) if len(c.errMsg) != 0 { assertErrStrContains(t, err, c.errMsg, c) @@ -1391,3 +1391,18 @@ func TestCompile(t *testing.T) { }) } } + +func TestGetCostIdentifier(t *testing.T) { + s := `(and (> Adults 1) (now))` + + vals := map[string]interface{}{ + "Origin": "MOW", + "Country": "RU", + "Adults": 1, + + "now": func(*Ctx, []Value) (Value, error) { return time.Now().Unix(), nil }, + } + + _, _ = Eval(s, vals) + +} diff --git a/engine_test.go b/engine_test.go index 3634b3f..ae19cca 100644 --- a/engine_test.go +++ b/engine_test.go @@ -1126,11 +1126,11 @@ func TestExpr_TryEval(t *testing.T) { func TestRandomExpressions(t *testing.T) { const ( - size = 30000 + size = 40000 level = 53 step = size / 100 showSample = false - printProgress = false + printProgress = true ) const ( diff --git a/operator.go b/operator.go index ac54a05..370a624 100644 --- a/operator.go +++ b/operator.go @@ -295,14 +295,14 @@ func (c comparison) execute(_ *Ctx, params []Value) (Value, error) { } func comparisonEquals(_ *Ctx, params []Value) (Value, error) { - if len(params) < 2 { - return nil, errCnt2(equals, params) - } - if len(params) == 2 { return params[0] == params[1], nil } + if len(params) < 2 { + return nil, errCnt2(equals, params) + } + v := params[0] for _, p := range params { if v != p { diff --git a/util.go b/util.go index 3e28c58..3165095 100644 --- a/util.go +++ b/util.go @@ -395,16 +395,31 @@ func IndentByParentheses(s string) string { return strings.TrimSpace(sb.String()) } -func Dump(e *Expr) string { - var getChildIdxes = func(idx int16) (res []int16) { +type TreeNode struct { + NodeType NodeType + SelKey SelectorKey + Value Value + Operator Operator + + Idx int + ChildCnt int + ChildIdx int + ParentIdx int +} + +type Tree []TreeNode + +func GetAstTree(e *Expr) Tree { + nodes := e.nodes + var getChildren = func(idx int) (res []int) { for i, p := range e.parentIdx { - if p == idx && e.nodes[i].getNodeType() != event { - res = append(res, int16(i)) + if int(p) == idx && nodes[i].getNodeType() != event { + res = append(res, i) } } - if e.nodes[idx].getNodeType() == cond { - res = []int16{ + if nodes[idx].getNodeType() == cond { + res = []int{ res[0], // condition node res[1], // true branch res[3], // false branch @@ -413,57 +428,100 @@ func Dump(e *Expr) string { return } - var helper func(int16) (string, bool) + var root int + for idx, pIdx := range e.parentIdx { + if pIdx == -1 { + root = idx + } + } - helper = func(idx int16) (string, bool) { - n := e.nodes[idx] - if n.childCnt == 0 { - return dumpLeafNode(n) + size := len(e.nodes) + tree := make([]TreeNode, 0, size) + queue := make([]int, 0, size) + queue = append(queue, root) + + idx := 0 + for idx < len(queue) { + curtIdx := queue[idx] + children := getChildren(curtIdx) + childIdx := len(queue) + childCnt := len(children) + + curt := nodes[curtIdx] + + n := TreeNode{ + NodeType: NodeType(curt.getNodeType()), + SelKey: curt.selKey, + Value: curt.value, + Operator: curt.operator, + } + n.ChildCnt = childCnt + switch n.NodeType { + case ConstantNode, SelectorNode: + n.ChildIdx = -1 + default: + n.ChildIdx = childIdx } - var sb strings.Builder - sb.WriteString(fmt.Sprintf("(%v", n.value)) + tree = append(tree, n) + queue = append(queue, children...) + idx++ + } + + return tree +} + +func (t Tree) DumpCode(format bool) string { + var helper func(int) (string, bool) - childIdxes := getChildIdxes(idx) + helper = func(idx int) (string, bool) { + n := t[idx] + if n.ChildCnt == 0 { + return dumpLeafNode(n) + } - for _, cIdx := range childIdxes { - cc, isLeaf := helper(cIdx) - if isLeaf { - sb.WriteString(fmt.Sprintf(" %s", cc)) + cIdx := n.ChildIdx + cCnt := n.ChildCnt + + var sb strings.Builder + sb.WriteString("(") + sb.WriteString(n.Value.(string)) + + for i := cIdx; i < cIdx+cCnt; i++ { + s, isLeaf := helper(i) + if !format || isLeaf { + sb.WriteString(" ") + sb.WriteString(s) continue } - for _, cs := range strings.Split(cc, "\n") { + for _, cs := range strings.Split(s, "\n") { sb.WriteString(fmt.Sprintf("\n %s", cs)) } } sb.WriteString(")") return sb.String(), false } + code, _ := helper(0) + return code +} - var rootIdx int16 - for idx, pIdx := range e.parentIdx { - if pIdx == -1 { - rootIdx = int16(idx) - } - } - - res, _ := helper(rootIdx) - return res +func Dump(e *Expr) string { + return GetAstTree(e).DumpCode(true) } -func dumpLeafNode(node *node) (string, bool) { - switch node.getNodeType() { - case event: +func dumpLeafNode(node TreeNode) (string, bool) { + switch node.NodeType { + case EventNode: return "eventNode", false - case selector: - return fmt.Sprint(node.value), true - case operator, fastOperator: - return fmt.Sprintf("(%v)", node.value), false + case SelectorNode: + return fmt.Sprint(node.Value), true + case OperatorNode, FastOperatorNode: + return fmt.Sprintf("(%v)", node.Value), false } var res string - switch v := node.value.(type) { + switch v := node.Value.(type) { case string: res = strconv.Quote(v) case []string: @@ -652,25 +710,6 @@ func DumpTable(expr *Expr, skipEventNode bool) string { return sb.String() } -type SelectorKeys struct { - SelKey SelectorKey - StrKey string -} - -func GetSelectorKeys(e *Expr) []SelectorKeys { - res := make([]SelectorKeys, len(e.nodes)/2) - - for _, n := range e.nodes { - if n.getNodeType() == selector { - res = append(res, SelectorKeys{ - SelKey: n.selKey, - StrKey: n.value.(string), - }) - } - } - return res -} - func HandleDebugEvent(e *Expr) { go func() { var prev LoopEventData diff --git a/util_test.go b/util_test.go index a10239f..6ad8b6e 100644 --- a/util_test.go +++ b/util_test.go @@ -105,7 +105,7 @@ func TestIndentByParentheses(t *testing.T) { ;; hhh0`) } -func TestPrintCode(t *testing.T) { +func TestDump(t *testing.T) { s := ` ;;;; optimize:false