Skip to content
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

move PNode.comment to a side channel, reducing memory usage during compilation by a factor 1.25x #18760

Merged
merged 6 commits into from
Aug 29, 2021

Conversation

timotheecour
Copy link
Member

@timotheecour timotheecour commented Aug 27, 2021

the comment field is rarely used yet each PNode has to pay the price, until this PR.

TNode.sizeof reduces from 40 to 32; interestingly, the peakmem usage drops by a very similar factor:

nim --eval:'echo 40/32'
1.25

nim --eval:'echo 847 / 670'
1.264179104477612

which can be attributed to fact that TNode allocations dwarf all other gc allocations, as demonstrated in other PRs (eg #13067)

result with --hint:GCStats:

before

Hint: gc: refc; opt: speed; options: -d:release
187965 lines; 23.781s; 847.062MiB peakmem; proj: /Users/timothee/git_clone/nim/Nim_temp6/compiler/nim.nim; out: /Users/timothee/git_clone/nim/Nim_temp6/bin/nim.devel.d4 [SuccessX]
[GC] total memory: 888209408
[GC] occupied memory: 718308016
[GC] stack scans: 50083
[GC] stack cells: 4230
[GC] cycle collections: 0
[GC] max threshold: 0
[GC] zct capacity: 2304
[GC] max cycle table size: 0
[GC] max pause time [ms]: 0
[GC] max stack size: 95952

after

Hint: gc: refc; opt: speed; options: -d:release
187985 lines; 8.223s; 670.09MiB peakmem; proj: /Users/timothee/git_clone/nim/Nim_prs/compiler/nim.nim; out: /Users/timothee/git_clone/nim/Nim_prs/bin/nim.pr_PNode_comment_sidechannel.d1 [SuccessX]
[GC] total memory: 702640128
[GC] occupied memory: 653233632
[GC] stack scans: 50194
[GC] stack cells: 4201
[GC] cycle collections: 0
[GC] max threshold: 0
[GC] zct capacity: 2304
[GC] max cycle table size: 0
[GC] max pause time [ms]: 0
[GC] max stack size: 95024

links

EDIT 1

unfortunately this involves a tradeoff bw compile times and memory consumption:
with -d:danger:
new:
188426 lines; 7.677s; 673.766MiB peakmem;
old:
187965 lines; 7.011s; 847.379MiB peakmem

(-d:release shows similar ratios for the time)

the bulk of the performance difference lies in the table accesses; more precisely:

roc comment*(n: PNode): string {.inline.} =
  count1.inc
  count4 = max(gconfig.comments.len, count4)
  gconfig.comments.getOrDefault(n.nodeId)

proc `comment=`*(n: PNode, a: string) {.inline.} =
  let id = n.nodeId
  if a.len > 0:
    count2.inc
    gconfig.comments[id] = a
  else:
    count3.inc
    if id in gconfig.comments:
      count3b.inc
      gconfig.comments.del(id)
    # gconfig.comments.del(id)
  count4 = max(gconfig.comments.len, count4)

(count1, count2, count3, count3b, count4)
(8831908, 33607, 23779703, 1278, 32330)

=> biggest cost is if id in gconfig.comments:

better table insertion/deletion or hashing algorithms might help here (hashWangYi1 is expensive!)

EDIT 2

solved via #18760 (comment)

future work

@timotheecour timotheecour force-pushed the pr_PNode_comment_sidechannel branch from 4133106 to 9ca09df Compare August 27, 2021 18:15
@timotheecour timotheecour changed the title move PNode.comment so a side channel, reducing memory usage by a factor 1.25x move PNode.comment so a side channel, reducing memory usage during compilation by a factor 1.25x Aug 27, 2021
@timotheecour timotheecour marked this pull request as draft August 27, 2021 19:46
@Varriount
Copy link
Contributor

Just to make sure I understand how this works, this change saves space because not all AST nodes have comments?

@timotheecour
Copy link
Member Author

Just to make sure I understand how this works, this change saves space because not all AST nodes have comments?

yes, as you can see above, ~ 1/260 PNode's have a non-empty comment field, which makes sense.

The problem right now is that the cost of the lookup is higher than i expected so it ends up being a space vs speed tradeoff unless someone has concrete ideas for how to improve this tradeoff

compiler/ast.nim Outdated
var gconfig {.threadvar.}: Gconfig

proc comment*(n: PNode): string =
gconfig.comments.getOrDefault(n.nodeId)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Educated guess: if you add another node flag you can skip the getOrDefault step in 99% of all cases and bring back the speed of the old approach.

Copy link
Member Author

@timotheecour timotheecour Aug 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, I've now implement this and it works, giving same memory improvement without incurring a performance cost;
however there was 1 subtlety but it all works fine, see comments in the PR; TLDR: there's a small amount of leak in the comments table that is entirely justified for performance reasons; future work can improve this if needed, but it probably won't be needed.

Note:

adding a proc =destroy(a: var TNode) would not work well:

  • would incur a large performance overhead, negating any gains (interestingly, also would increase peakmem metric; design bug?); and furthermore wouldn't work with --gc:arc

@timotheecour timotheecour force-pushed the pr_PNode_comment_sidechannel branch from 9a664fc to 3a7cae5 Compare August 28, 2021 21:17
@timotheecour
Copy link
Member Author

@Araq PTAL, see the comments in last commit

@timotheecour timotheecour changed the title move PNode.comment so a side channel, reducing memory usage during compilation by a factor 1.25x move PNode.comment to a side channel, reducing memory usage during compilation by a factor 1.25x Aug 28, 2021
@timotheecour timotheecour marked this pull request as ready for review August 28, 2021 21:24
@timotheecour timotheecour force-pushed the pr_PNode_comment_sidechannel branch from 3a7cae5 to 6f5acd2 Compare August 29, 2021 00:41
compiler/parser.nim Outdated Show resolved Hide resolved
@Araq Araq added the merge_when_passes_CI mergeable once green label Aug 29, 2021
@Araq Araq merged commit fa7c1aa into nim-lang:devel Aug 29, 2021
@timotheecour timotheecour deleted the pr_PNode_comment_sidechannel branch August 29, 2021 17:07
@timotheecour timotheecour added the TODO: followup needed remove tag once fixed or tracked elsewhere label Aug 29, 2021
haxscramper added a commit to haxscramper/hnimast that referenced this pull request Nov 23, 2021
- CHANGED ::
  - Implementation improvements for the code pretty printer - more input
    nodes supported.
  - Clean up the implementation of the tree-sitter wrapper generator.
  - More predictable `treeRepr` output - nodes with comments are no longer
    split apart at random places.
  - Make a ton of `func` nodes into `proc`, because accessing comment field
    is no longer a side-effect-free operations, most likely due to the
    nim-lang/Nim#18760
- ADDED ::
  - Tree-sitter wrapper generator now can produce library wrappers that do
    not depend on hmisc for operation.
  - `addPragma` for enum declarations
- REMOVED ::
  - `nimble_aux.nim` and dependency on the nimble - I no longer work on the
    nim-lang/RFCs#398 and I see no reason to try
    and revese-engineer the dependency management solutions, `nimph`
    provides much better approach in this case (edit `nim.cfg`, then user
    can simply dump it as needed).
PMunch pushed a commit to PMunch/Nim that referenced this pull request Mar 28, 2022
…mpilation by a factor 1.25x (nim-lang#18760)

* move PNode.comment so a side channel, reducing memory usage

* fix a bug

* fixup

* use sfHasComment to speedup comment lookups

* fix for IC

* Update compiler/parser.nim

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merge_when_passes_CI mergeable once green TODO: followup needed remove tag once fixed or tracked elsewhere
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants