Performance: Evaluation of 200 lines takes >130s #3654
Description
I'm opening an issue because #2850 told me to.
Context: I'm evaluating CUE as a configuration language for our hospital information system with about 120.000 loc (just 2,5 MB) of declarative configuration that consists of data (catalogs) and DSL code. The configuration uses files with XML and (Emacs) org-mode syntax. Currently our Haskell backend parses and checks the configuration in less than a second. I'd like to replace that configuration using CUE.
Thank's to @myitcv I've managed to write CUE definitions that check my input and generate my old-style org-mode configuration. But just with about 40 lines of test data cue eval
already needs 130s on my Mac M2. Haskell's lazyness has got it's surprises so I'm used to think differently about performance for different languages.
Why does my CUE program take so long and what can I do about it? How do I need to think about performance when writing CUE code? Which constructions can be evaluated in linear / quadratic / cubic (or worse?) time? Am I using too many disjunctions?
I've tried the new evaluator (using my own build at commit f2775f8) but that didn't change anything.
Without my org-generation the checking is fast. So maybe CUE is just not suited as a programming language for generating my old-style org-syntax configuration format?
import "strings"
import "encoding/json"
#FinDefSetElemType: {
#Type!: _
}
#FinDefSetDefinition: {
#Elem!: #FinDefSetElemType
#Alternatives: {
allBut: {
AllBut: [...#Elem.#Type]
}
only: {
Only: [...#Elem.#Type]
}
}
}
#mkFinDefSet: {
#Definition!: #FinDefSetDefinition
#Type: or([for _, v in #Definition.#Alternatives {v}])
}
#FinDefSetElemType: {
#Type: _
toString!: {
_input: #Type
_output: string
}
}
#FinDefSetDefinition: {
#Elem!: #FinDefSetElemType
#Alternatives: {
allBut: {
AllBut: [...#Elem.#Type]
let xs = [for x in AllBut {(#Elem.toString & {_input: x})._output}]
let str = strings.Join(xs, "\",\"")
orgSyntax: "AllBut [\(str)]"
}
only: {
Only: [...#Elem.#Type]
let xs = [for x in Only {(#Elem.toString & {_input: x})._output}]
let str = strings.Join(xs, "\",\"")
orgSyntax: "Only [\(str)]"
}
}
}
#ConfigText: string | {UseSectionName: true}
#ConfigTextRule: #ConfigText | {Append: #ConfigText} | {Prepend: #ConfigText}
#ConfigTextRules: #ConfigTextRule | [#ConfigTextRule, #ConfigTextRule, ...#ConfigTextRule]
#FinDefSetConfigTextElem: #FinDefSetElemType
#FinDefSetConfigTextElem: {
#Type: #ConfigTextRules
}
#FinDefSetOfConfigText: (
#mkFinDefSet & {
#Definition: {#Elem: #FinDefSetConfigTextElem}
}).#Type
#PatSetCfg: {
wards?: *null | #FinDefSetOfConfigText
facilities?: *null | #FinDefSetOfConfigText
stayKind?: "OnlyInpatients" | "InAndOutpatients" | "OnlyOutpatients" | "OnlyConsultations"
id?: *null | #ConfigTextRules
servers?: *null | #FinDefSetOfConfigText
includeInpatientsBeforeStayDays?: int & >=0 & <30
}
#PatSetNode: string | {
name: string
config?: #PatSetCfg
children?: [...#PatSetNode]
}
#FinDefSetConfigTextElem: {
toString: {
_input: #ConfigTextRules
_output: (#orgForConfigTextRules & {source: _input}).orgSyntax
}
}
#orgForConfigText: {
IN=source: #ConfigText
orgSyntax: [
if (IN & string) != _|_ {
json.Marshal(IN)
},
if IN.UseSectionName != _|_ {
"UseSectionName"
},
"Wrong input \(IN) in orgForConfigText",
][0]
}
#orgForConfigTextRule: {
IN=source: #ConfigTextRule
orgSyntax: [
if IN.Append != _|_ {
"Append " + (#orgForConfigText & {source: IN.Append}).orgSyntax
},
if IN.Prepend != _|_ {
"Prepend " + (#orgForConfigText & {source: IN.Prepend}).orgSyntax
},
(#orgForConfigText & {source: IN}).orgSyntax,
][0]
}
#orgForConfigTextRules: {
IN=source: #ConfigTextRules
orgSyntax: {
let INL = IN & [...]
[
if INL != _|_ {
let elems = [for x in IN {(#orgForConfigTextRule & {source: x}).orgSyntax}]
let result = strings.Join(elems, "\",\"")
"[\(result)]"
},
(#orgForConfigTextRule & {source: IN}).orgSyntax,
][0]
}
}
#PatSetCfg: {
wards?: _
facilities?: _
stayKind?: _
id?: _
includeInpatientsBeforeStayDays?: _
let parts = [
if wards != _|_ {
"- wards :: " + wards.orgSyntax
},
if facilities != _|_ {
"- facilities :: " + facilities.orgSyntax
},
if stayKind != _|_ {
"- stayKind :: " + stayKind
},
if id != _|_ {
"- id :: " + (#orgForConfigText & {source: id}).orgSyntax
},
if includeInpatientsBeforeStayDays != _|_ {
"- includeInpatientsBeforeStayDays :: \(includeInpatientsBeforeStayDays)"
},
]
orgSyntax: strings.Join(parts, "\n")
}
_nodes2: ["prod-live-demo-int-node-n-01", "prod-live-real-int-node-n-02", "staging-live-real-int-node-n-02", "dev-live-fake-int-node-n-02"]
top: #PatSetNode
top: {
name: "Top"
children: [abteilung]
}
abteilung: #PatSetNode
abteilung: {
name: "Abteilung"
config: {
wards: AllBut: []
facilities: Only: [{UseSectionName: true}]
stayKind: "OnlyInpatients"
includeInpatientsBeforeStayDays: 3
}
children: [abteilungAbteilung]
}
abteilungAbteilung: #PatSetNode
abteilungAbteilung: {
name: "Abteilung/Abteilung"
children: [node2]
}
node2: #PatSetNode
node2: {
name: "Node2"
config: servers: Only: _nodes2
children: [
"KIM-ENDO",
kimGast,
]
}
kimGast: #PatSetNode
kimGast: {
name: "KIM-GAST"
config: {
wards: AllBut: ["KIM6T"]
}
}