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

Memory leak under Arc/Orc on inline iterators with nested seq. #24402

Open
lilkeet opened this issue Nov 3, 2024 · 2 comments
Open

Memory leak under Arc/Orc on inline iterators with nested seq. #24402

lilkeet opened this issue Nov 3, 2024 · 2 comments

Comments

@lilkeet
Copy link

lilkeet commented Nov 3, 2024

Description

On the arc, orc, and atomicArc mm's: Inline iterators that do not return a lent type leak memory when iterating upon nested sequences. Closures that do not return lent types and inlines that do return lent types do not leak.

iterator myPairsInline*[T](twoDarray: seq[seq[T]]): (int, seq[T]) {.inline.} =
  for indexValuePair in twoDarray.pairs:
    yield indexValuePair

iterator myPairsClosure*[T](twoDarray: seq[seq[T]]): (int, seq[T]) {.closure.} =
  for indexValuePair in twoDarray.pairs:
    yield indexValuePair

template testTotalMem(iter: untyped): int =
  proc innerTestTotalMem(): int {.gensym.} =
    result = 0

    # do the same operation 100 times, which should have similar mem footprint
    # as doing it once.
    for iterNum in 0..100:
      result = max(result, getTotalMem()) # record current mem footprint

      # initialize nested sequence
      var my2dArray: seq[seq[int32]] = @[]

      # fill with some data...
      for i in 0'i32..10_000:
        var z = @[i, i+1]
        my2dArray.add z

      # use that data somehow...
      var otherContainer: seq[int32] = @[]
      var count = 0'i32
      for oneDindex, innerArray in my2dArray.iter:
        for value in innerArray:
          inc count
          if oneDindex > 50 and value < 200:
            otherContainer.add count

  innerTestTotalMem()

proc main =
  let closureMem = testTotalMem(myPairsClosure) #1052672
  let inlineMem = testTotalMem(myPairsInline) #20328448

  when defined(echoFootprint):
    echo "Closure memory footprint: " & $closureMem
    echo "Inline memory footprint: " & $inlineMem

  # check that mem footprint is relatively similar b/t each method
  doAssert (closureMem - inlineMem).abs < (closureMem div 10)

main()

Nim Version

Nim Compiler Version 2.2.0 [Linux: amd64]

Current Output

/path/to/mainmodule.nim(49) mainmodule
/path/to/mainmodule.nim(47) main
/path/to/.choosenim/toolchains/nim-2.2.0/lib/std/assertions.nim(41) failedAssertImpl
/path/to/.choosenim/toolchains/nim-2.2.0/lib/std/assertions.nim(36) raiseAssert
/path/to/.choosenim/toolchains/nim-2.2.0/lib/system/fatal.nim(53) sysFatal
Error: unhandled exception: /path/to/mainmodule.nim(47, 3) `(closureMem - inlineMem).abs < (closureMem div 10)`  [AssertionDefect]
Error: execution of an external program failed: '/path/to/mainmodule'

Expected Output

No response

Known Workarounds

No response

Additional Information

The regions gc/mm scheme is the only other gc that fails this example.

@lilkeet
Copy link
Author

lilkeet commented Nov 4, 2024

Here's valgrind's output on above example when nim is passed -d:useMalloc and the mm scheme is orc.

 HEAP SUMMARY:
     in use at exit: 16,161,616 bytes in 1,010,101 blocks
   total heap usage: 5,055,657 allocs, 4,045,556 frees, 187,586,304 bytes allocated
 
 Searching for pointers to 1,010,101 not-freed blocks
 Checked 108,560 bytes
 
 16 bytes in 1 blocks are still reachable in loss record 1 of 2
    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    by 0x10AFC8: allocImpl__system_u1742 (in /path/to/mainmodule)
    by 0x10AFEE: allocSharedImpl (in /path/to/mainmodule)
    by 0x10DD04: alignedAlloc__system_u1882 (in /path/to/mainmodule)
    by 0x10E014: newSeqPayloadUninit (in /path/to/mainmodule)
    by 0x10E28C: prepareSeqAddUninit (in /path/to/mainmodule)
    by 0x1125A9: setLen__mainmodule_u129 (in /path/to/mainmodule)
    by 0x11369A: eqdup___mainmodule_u115 (in /path/to/mainmodule)
    by 0x114E11: innerTestTotalMemmyPairsInline__mainmodule_u451 (in /path/to/mainmodule)
    by 0x11534F: main__mainmodule_u20 (in /path/to/mainmodule)
    by 0x115592: NimMainModule (in /path/to/mainmodule)
    by 0x1154CA: NimMainInner (in /path/to/mainmodule)
 
 16,161,600 bytes in 1,010,100 blocks are definitely lost in loss record 2 of 2
    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    by 0x10AFC8: allocImpl__system_u1742 (in /path/to/mainmodule)
    by 0x10AFEE: allocSharedImpl (in /path/to/mainmodule)
    by 0x10DD04: alignedAlloc__system_u1882 (in /path/to/mainmodule)
    by 0x10E014: newSeqPayloadUninit (in /path/to/mainmodule)
    by 0x10E28C: prepareSeqAddUninit (in /path/to/mainmodule)
    by 0x1125A9: setLen__mainmodule_u129 (in /path/to/mainmodule)
    by 0x11369A: eqdup___mainmodule_u115 (in /path/to/mainmodule)
    by 0x114E11: innerTestTotalMemmyPairsInline__mainmodule_u451 (in /path/to/mainmodule)
    by 0x11534F: main__mainmodule_u20 (in /path/to/mainmodule)
    by 0x115592: NimMainModule (in /path/to/mainmodule)
    by 0x1154CA: NimMainInner (in /path/to/mainmodule)
 
 LEAK SUMMARY:
    definitely lost: 16,161,600 bytes in 1,010,100 blocks
    indirectly lost: 0 bytes in 0 blocks
      possibly lost: 0 bytes in 0 blocks
    still reachable: 16 bytes in 1 blocks
                       of which reachable via heuristic:
                         newarray           : 16 bytes in 1 blocks
         suppressed: 0 bytes in 0 blocks
 
 ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

@ringabout
Copy link
Member

!nim c --gc:orc

iterator myPairsInline*[T](twoDarray: seq[seq[T]]): (int, seq[T]) {.inline.} =
  for indexValuePair in twoDarray.pairs:
    yield indexValuePair

iterator myPairsClosure*[T](twoDarray: seq[seq[T]]): (int, seq[T]) {.closure.} =
  for indexValuePair in twoDarray.pairs:
    yield indexValuePair

template testTotalMem(iter: untyped): int =
  proc innerTestTotalMem(): int {.gensym.} =
    result = 0

    # do the same operation 100 times, which should have similar mem footprint
    # as doing it once.
    for iterNum in 0..100:
      result = max(result, getTotalMem()) # record current mem footprint

      # initialize nested sequence
      var my2dArray: seq[seq[int32]] = @[]

      # fill with some data...
      for i in 0'i32..10_000:
        var z = @[i, i+1]
        my2dArray.add z

      # use that data somehow...
      var otherContainer: seq[int32] = @[]
      var count = 0'i32
      for oneDindex, innerArray in my2dArray.iter:
        for value in innerArray:
          inc count
          if oneDindex > 50 and value < 200:
            otherContainer.add count

  innerTestTotalMem()

proc main =
  let closureMem = testTotalMem(myPairsClosure) #1052672
  let inlineMem = testTotalMem(myPairsInline) #20328448

  when defined(echoFootprint):
    echo "Closure memory footprint: " & $closureMem
    echo "Inline memory footprint: " & $inlineMem

  # check that mem footprint is relatively similar b/t each method
  doAssert (closureMem - inlineMem).abs < (closureMem div 10)

main()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants