Skip to content

[WIP] context based reordering #26

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 76 additions & 19 deletions compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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))
}

Expand All @@ -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) {
Expand Down
17 changes: 16 additions & 1 deletion compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

}
4 changes: 2 additions & 2 deletions engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
8 changes: 4 additions & 4 deletions operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
149 changes: 94 additions & 55 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
Loading