forked from GitHub/gf-core
393 lines
10 KiB
Markdown
393 lines
10 KiB
Markdown
# LPGF testsuite & benchmark
|
|
|
|
## Testsuite
|
|
|
|
LPGF must be equivalent to PGF in terms of linearisation output.
|
|
|
|
Possible exceptions:
|
|
- No handling of variants (design choice)
|
|
- Rendering of missing functions
|
|
|
|
**N.B.**
|
|
Phrasebook doesn't compile with RGL after 1131058b68c204a8d1312d2e2a610748eb8032cb
|
|
|
|
### Running
|
|
|
|
Because Stack insists on rebuilding things all the time, I use separate `.stack-work` folders for testing and benchmarking.
|
|
|
|
Assumes treebank in same folder with same abstract name as grammar, e.g. `unittests/Params.treebank`
|
|
|
|
```
|
|
stack build --work-dir .stack-work-test --test --no-run-tests
|
|
stack test --work-dir .stack-work-test gf:test:lpgf # all LPGF tests
|
|
stack test --work-dir .stack-work-test gf:test:lpgf --test-arguments="unittests/Params" # specific grammar
|
|
stack test --work-dir .stack-work-test gf:test:lpgf --test-arguments="foods/Foods Fre Ger" # specific grammar and languages
|
|
stack test --work-dir .stack-work-test gf:test:lpgf --test-arguments="phrasebook/Phrasebook"
|
|
```
|
|
|
|
Set environment variable `DEBUG=1` to enable dumping of intermediate formats into `DEBUG/` folder.
|
|
|
|
---
|
|
|
|
## Benchmark
|
|
|
|
Compare performance metrics between LPGF and PGF[2]. Note: correctness is not checked here.
|
|
|
|
### Compilation
|
|
|
|
Comparing PGF, LPGF along following criteria:
|
|
|
|
- Time
|
|
- Memory
|
|
- Binary file size
|
|
|
|
### Runtime (linearisation)
|
|
|
|
Comparing PGF, PGF2, LPGF along following criteria:
|
|
|
|
- Time
|
|
- Memory
|
|
|
|
### Running
|
|
|
|
Run each command separately so that memory measurements are isolated.
|
|
The `+RTS -T -RTS` is so that GHC can report its own memory usage.
|
|
|
|
```
|
|
stack build --work-dir .stack-work-bench --bench --no-run-benchmarks &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "compile pgf testsuite/lpgf/foods/Foods*.gf +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "compile lpgf testsuite/lpgf/foods/Foods*.gf +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "run pgf Foods.pgf testsuite/lpgf/foods/Foods-all.trees +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "run pgf2 Foods.pgf testsuite/lpgf/foods/Foods-all.trees +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "run lpgf Foods.lpgf testsuite/lpgf/foods/Foods-all.trees +RTS -T -RTS"
|
|
```
|
|
|
|
```
|
|
stack build --work-dir .stack-work-bench --bench --no-run-benchmarks &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "compile pgf testsuite/lpgf/phrasebook/Phrasebook*.gf +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "compile lpgf testsuite/lpgf/phrasebook/Phrasebook*.gf +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "run pgf Phrasebook.pgf testsuite/lpgf/phrasebook/Phrasebook-10000.trees +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "run pgf2 Phrasebook.pgf testsuite/lpgf/phrasebook/Phrasebook-10000.trees +RTS -T -RTS" &&
|
|
stack bench --work-dir .stack-work-bench --benchmark-arguments "run lpgf Phrasebook.lpgf testsuite/lpgf/phrasebook/Phrasebook-10000.trees +RTS -T -RTS"
|
|
```
|
|
|
|
## Profiling
|
|
|
|
```
|
|
stack build --work-dir .stack-work-profile --profile --bench --no-run-benchmarks &&
|
|
stack bench --work-dir .stack-work-profile --profile --benchmark-arguments "compile lpgf testsuite/lpgf/phrasebook/PhrasebookFre.gf +RTS -T -p -h -RTS"
|
|
```
|
|
|
|
Produced files:
|
|
- `lpgf-bench.prof` - total time and memory allocation (`-p`)
|
|
- `lpgf-bench.hp` - heap profile (`-h`)
|
|
|
|
Open heap profile graph on-the-fly:
|
|
```
|
|
stack exec -- hp2ps -c lpgf-bench.hp && open lpgf-bench.ps
|
|
```
|
|
|
|
Convert and copy timestamped files into `PROF/`:
|
|
```
|
|
TS="$(date +%Y-%m-%d_%H%M)" &&
|
|
stack exec -- hp2ps -c lpgf-bench.hp &&
|
|
mv lpgf-bench.prof PROF/$TS.prof &&
|
|
mv lpgf-bench.ps PROF/$TS.ps &&
|
|
mv lpgf-bench.hs PROF/$TS.hp
|
|
```
|
|
|
|
**Resources**
|
|
|
|
- https://downloads.haskell.org/ghc/8.6.5/docs/html/users_guide/profiling.html
|
|
- http://book.realworldhaskell.org/read/profiling-and-optimization.html
|
|
- https://wiki.haskell.org/Performance
|
|
|
|
|
|
### Honing in
|
|
|
|
```
|
|
stack build --test --bench --no-run-tests --no-run-benchmarks &&
|
|
stack bench --benchmark-arguments "compile lpgf testsuite/lpgf/phrasebook/PhrasebookFre.gf +RTS -T -RTS"
|
|
```
|
|
|
|
**Baseline PGF**
|
|
- compile: 1.600776s
|
|
- size: 2.88 MB Phrasebook.pgf
|
|
Max memory: 328.20 MB
|
|
|
|
**Baseline LPGF = B**
|
|
- compile: 12.401099s
|
|
- size: 3.01 MB Phrasebook.lpgf
|
|
Max memory: 1.33 GB
|
|
|
|
**Baseline LPGF String instead of Text**
|
|
- compile: 12.124689s
|
|
- size: 3.01 MB Phrasebook.lpgf
|
|
Max memory: 1.34 GB
|
|
|
|
**Baseline LPGF with impossible pruning**
|
|
- compile: 7.406503s
|
|
- size: 3.01 MB Phrasebook.lpgf
|
|
Max memory: 1.13 GB
|
|
|
|
|
|
**B -extractStrings**
|
|
- compile: 13.822735s
|
|
- size: 5.78 MB Phrasebook.lpgf
|
|
Max memory: 1.39 GB
|
|
|
|
**B -cleanupRecordFields**
|
|
- compile: 13.670776s
|
|
- size: 3.01 MB Phrasebook.lpgf
|
|
Max memory: 1.48 GB
|
|
|
|
**No generation at all = E**
|
|
- compile: 0.521001s
|
|
- size: 3.27 KB Phrasebook.lpgf
|
|
Max memory: 230.69 MB
|
|
|
|
**+ Concat, Literal, Error, Predef, Tuple, Variant, Commented**
|
|
- compile: 1.503594s
|
|
- size: 3.27 KB Phrasebook.lpgf
|
|
Max memory: 395.31 MB
|
|
|
|
**+ Var, Pre, Selection**
|
|
- compile: 1.260184s
|
|
- size: 3.28 KB Phrasebook.lpgf
|
|
Max memory: 392.17 MB
|
|
|
|
**+ Record**
|
|
- compile: 1.659233s
|
|
- size: 7.07 KB Phrasebook.lpgf
|
|
Max memory: 397.41 MB
|
|
|
|
**+ Projection = X**
|
|
- compile: 1.446217s
|
|
- size: 7.94 KB Phrasebook.lpgf
|
|
Max memory: 423.62 MB
|
|
|
|
**X + Param**
|
|
- compile: 2.073838s
|
|
- size: 10.82 KB Phrasebook.lpgf
|
|
Max memory: 619.71 MB
|
|
|
|
**X + Table**
|
|
- compile: 11.26558s
|
|
- size: 2.48 MB Phrasebook.lpgf
|
|
Max memory: 1.15 GB
|
|
|
|
**RawIdents**
|
|
- compile: 5.393466s
|
|
- size: 3.01 MB Phrasebook.lpgf
|
|
Max memory: 1.12 GB
|
|
|
|
### Repeated terms in compilation
|
|
|
|
**Param and Table**
|
|
|
|
| Concr | Total | Unique | Perc |
|
|
|:--------------|-------:|-------:|-----:|
|
|
| PhrasebookEng | 8673 | 1724 | 20% |
|
|
| PhrasebookSwe | 14802 | 2257 | 15% |
|
|
| PhrasebookFin | 526225 | 4866 | 1% |
|
|
|
|
**Param**
|
|
|
|
| Concr | Total | Unique | Perc |
|
|
|:--------------|-------:|-------:|-----:|
|
|
| PhrasebookEng | 3211 | 78 | 2% |
|
|
| PhrasebookSwe | 7567 | 69 | 1% |
|
|
| PhrasebookFin | 316355 | 310 | 0.1% |
|
|
|
|
**Table**
|
|
|
|
| Concr | Total | Unique | Perc |
|
|
|:--------------|-------:|-------:|-----:|
|
|
| PhrasebookEng | 5470 | 1654 | 30% |
|
|
| PhrasebookSwe | 7243 | 2196 | 30% |
|
|
| PhrasebookFin | 209878 | 4564 | 2% |
|
|
|
|
### After impelementing state monad for table memoisation
|
|
|
|
**worse!**
|
|
- compile: 12.55848s
|
|
- size: 3.01 MB Phrasebook.lpgf
|
|
Max memory: 2.25 GB
|
|
|
|
**Params**
|
|
|
|
| Concr | Total | Misses | Perc |
|
|
|:--------------|-------:|-------:|------:|
|
|
| PhrasebookEng | 3211 | 72 | 2% |
|
|
| PhrasebookSwe | 7526 | 61 | 1% |
|
|
| PhrasebookFin | 135268 | 333 | 0.2% |
|
|
| PhrasebookFre | 337102 | 76 | 0.02% |
|
|
|
|
**Tables**
|
|
|
|
| Concr | Total | Misses | Perc |
|
|
|:--------------|------:|-------:|-----:|
|
|
| PhrasebookEng | 3719 | 3170 | 85% |
|
|
| PhrasebookSwe | 4031 | 3019 | 75% |
|
|
| PhrasebookFin | 36875 | 21730 | 59% |
|
|
| PhrasebookFre | 41397 | 32967 | 80% |
|
|
|
|
Conclusions:
|
|
- map itself requires more memory than actual compilation
|
|
- lookup/insert is also as bad as actual compilation
|
|
|
|
Tried HashMap (deriving Hashable for LinValue), no inprovement.
|
|
Using show on LinValue for keys is incredibly slow.
|
|
|
|
# Notes on compilation
|
|
|
|
## 1 (see unittests/Params4)
|
|
|
|
**param defns**
|
|
P = P1 | P2
|
|
Q = Q1 | Q2
|
|
R = RP P | RPQ P Q | R0
|
|
X = XPQ P Q
|
|
|
|
**translation**
|
|
NB: tuples may be nested, but will be concatted at runtime
|
|
|
|
P1 = <1>
|
|
P2 = <2>
|
|
|
|
Q1 = <1>
|
|
Q2 = <2>
|
|
|
|
R P1 = <1,1>
|
|
R P2 = <1,2>
|
|
RPQ P1 Q1 = <2,1,1>
|
|
RPQ P1 Q2 = <2,1,2>
|
|
RPQ P2 Q1 = <2,2,1>
|
|
RPQ P2 Q2 = <2,2,2>
|
|
R0 = <3>
|
|
|
|
XPQ P1 Q1 = <1,1,1>
|
|
XPQ P1 Q2 = <1,1,2>
|
|
XPQ P2 Q1 = <1,2,1>
|
|
XPQ P2 Q2 = <1,2,2>
|
|
|
|
P => Str
|
|
<"P1","P2">
|
|
|
|
{p:P ; q:Q} => Str
|
|
<<"P1;Q1","P1;Q2">,<"P2;Q1","P2;Q2">>
|
|
|
|
{p=P2; q=Q1}
|
|
<<2>,<1>>
|
|
|
|
R => Str
|
|
< <"RP P1","RP P2">,
|
|
< <"RPQ P1 Q1","RPQ P1 Q2">,
|
|
<"RPQ P2 Q1","RPQ P2 Q2"> >,
|
|
"R0"
|
|
>
|
|
|
|
X => Str
|
|
<<<"XPQ P1 Q1","XPQ P1 Q2">,
|
|
<"XPQ P2 Q1","XPQ P2 Q2">>>
|
|
|
|
{p=P2 ; r=R0}
|
|
<<2>,<3>>
|
|
|
|
{p=P2 ; r1=RP P1 ; r2=RPQ P1 Q2 ; r3=R0 }
|
|
< <2> , <1, 1> , <2, 1, 2> , <3>>
|
|
|
|
## 2 (see unittests/Params5)
|
|
|
|
**param defns**
|
|
|
|
P = P1 | PQ Q
|
|
Q = Q1 | QR R
|
|
R = R1 | R2
|
|
|
|
**translation**
|
|
|
|
P1 = <1>
|
|
PQ Q1 = <2,1>
|
|
PQ QR R1 = <2,2,1>
|
|
PQ QR R2 = <2,2,2>
|
|
|
|
Q1 = <1>
|
|
QR R1 = <2,1>
|
|
QR R2 = <2,2>
|
|
|
|
R1 = <1>
|
|
R2 = <2>
|
|
|
|
P => Str
|
|
<"P1",<"PQ Q1",<"PQ (QR R1)","PQ (QR R2)">>>
|
|
|
|
{q:Q ; p:P} => Str
|
|
< <"Q1;P1",<"Q1;PQ Q1",<"Q1;PQ (QR R1)","Q1;PQ (QR R2)">>>,
|
|
<
|
|
<"QR R1;P1",<"QR R1;PQ Q1",<"QR R1;PQ (QR R1)","QR R1;PQ (QR R2)">>>,
|
|
<"QR R2;P1",<"QR R2;PQ Q1",<"QR R2;PQ (QR R1)","QR R2;PQ (QR R2)">>>
|
|
>
|
|
>
|
|
|
|
{q=Q1 ; p=P1} = <<1>,<1>>
|
|
{q=Q1 ; p=PQ Q1} = <<1>,<2,1>>
|
|
{q=Q1 ; p=PQ (QR R1)} = <<1>,<2,2,1>>
|
|
{q=Q1 ; p=PQ (QR R2)} = <<1>,<2,2,2>>
|
|
|
|
{q=QR R1 ; p=P1} = <<2,1>,<1>>
|
|
{q=QR R1 ; p=PQ Q1} = <<2,1>,<2,1>>
|
|
{q=QR R1 ; p=PQ (QR R1)} = <<2,1>,<2,2,1>>
|
|
{q=QR R1 ; p=PQ (QR R2)} = <<2,1>,<2,2,2>>
|
|
|
|
{q=QR R2 ; p=P1} = <<2,2>,<1>>
|
|
{q=QR R2 ; p=PQ Q1} = <<2,2>,<2,1>>
|
|
{q=QR R2 ; p=PQ (QR R1)} = <<2,2>,<2,2,1>>
|
|
{q=QR R2 ; p=PQ (QR R2)} = <<2,2>,<2,2,2>>
|
|
|
|
**NOTE**: GF will swap q and p in record, as part of record field sorting, resulting in the following:
|
|
|
|
{p:P ; q:Q} => Str
|
|
< <"P1;Q1", <"P1;QR R1","P1;QR R2">>,
|
|
< <"PQ Q1;Q1", <"PQ Q1;QR R1","PQ Q1;QR R2">>,
|
|
< <"PQ (QR R1);Q1", <"PQ (QR R1);QR R1","PQ (QR R1);QR R2">>,
|
|
<"PQ (QR R2);Q1", <"PQ (QR R2);QR R1","PQ (QR R2);QR R2">>
|
|
>
|
|
>
|
|
>
|
|
|
|
{p=P1 ; q=Q1} = <<1>,<1>>
|
|
{p=P1 ; q=QR R1} = <<1>,<2,1>>
|
|
{p=P1 ; q=QR R2} = <<1>,<2,2>>
|
|
|
|
{p=PQ Q1 ; q=Q1} = <<2,1>,<1>>
|
|
{p=PQ Q1 ; q=QR R1} = <<2,1>,<2,1>>
|
|
{p=PQ Q1 ; q=QR R2} = <<2,1>,<2,2>>
|
|
|
|
{p=PQ (QR R1) ; q=Q1} = <<2,2,1>,<1>>
|
|
{p=PQ (QR R1) ; q=QR R1} = <<2,2,1>,<2,1>>
|
|
{p=PQ (QR R1) ; q=QR R2} = <<2,2,1>,<2,2>>
|
|
|
|
{p=PQ (QR R2) ; q=Q1} = <<2,2,2>,<1>>
|
|
{p=PQ (QR R2) ; q=QR R1} = <<2,2,2>,<2,1>>
|
|
{p=PQ (QR R2) ; q=QR R2} = <<2,2,2>,<2,2>>
|
|
|
|
|
|
{pp: {p:P} ; q:Q} => Str
|
|
|
|
{pp={p=P1} ; q=Q1} = <<<1>>,<1>>
|
|
{pp={p=P1} ; q=QR R1} = <<<1>>,<2,1>>
|
|
{pp={p=P1} ; q=QR R2} = <<<1>>,<2,2>>
|
|
|
|
{pp={p=PQ Q1} ; q=Q1} = <<<2,1>>, <1>>
|
|
{pp={p=PQ Q1} ; q=QR R1} = <<<2,1>>, <2,1>>
|
|
{pp={p=PQ Q1} ; q=QR R2} = <<<2,1>>, <2,2>>
|
|
|
|
{pp={p=PQ (QR R1)} ; q=Q1} = <<<2,2,1>>,<1>>
|
|
{pp={p=PQ (QR R1)} ; q=QR R1} = <<<2,2,1>>,<2,1>>
|
|
{pp={p=PQ (QR R1)} ; q=QR R2} = <<<2,2,1>>,<2,2>>
|
|
|
|
{pp={p=PQ (QR R2)} ; q=Q1} = <<<2,2,2>>,<1>>
|
|
{pp={p=PQ (QR R2)} ; q=QR R1} = <<<2,2,2>>,<2,1>>
|
|
{pp={p=PQ (QR R2)} ; q=QR R2} = <<<2,2,2>>,<2,2>>
|