DotRecast icon indicating copy to clipboard operation
DotRecast copied to clipboard

SOH allocation issues

Open Danil0v3s opened this issue 1 year ago • 59 comments

Rider keeps telling me that Small Object Heap allocation is high for the DtCrowd.Update method.

image image

This is my usage image

Where the Update method is called every 0.3... image

On the pictures above the compiler is complaining about 200mb-ish, but I've seen it complaining about almost 800mb. Do you have any idea of what could it be?

Danil0v3s avatar Jan 19 '24 23:01 Danil0v3s

image image image

Danil0v3s avatar Jan 20 '24 12:01 Danil0v3s

I'll check and mention you!!

ikpil avatar Jan 20 '24 13:01 ikpil

@Danil0v3s

Using stack memory introduces some potential risks that need careful consideration. Could you please review the following changes and confirm their appropriateness?

before

            // Build sampling pattern aligned to desired velocity.
            float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];

after

            // Build sampling pattern aligned to desired velocity.
            int patSize = (DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2;
            RcStackArray512<float> pat = RcStackArray512<float>.Empty;
            ThrowHelper.ThrowExceptionIfIndexOutOfRange(patSize - 1, pat.Length);

ikpil avatar Jan 21 '24 04:01 ikpil

@Danil0v3s

Using stack memory introduces some potential risks that need careful consideration.

Could you please review the following changes and confirm their appropriateness?

before


            // Build sampling pattern aligned to desired velocity.

            float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];

after


            // Build sampling pattern aligned to desired velocity.

            int patSize = (DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2;

            RcStackArray512<float> pat = RcStackArray512<float>.Empty;

            ThrowHelper.ThrowExceptionIfIndexOutOfRange(patSize - 1, pat.Length);

Hey, thanks for taking the time to look at the issue. The solution is a bit above my league so I can't really say anything. But if you wish to release a preview version I can see if the SOH warnings are gone

Danil0v3s avatar Jan 21 '24 09:01 Danil0v3s

I added RcRentedArray, which internally utilizes the functionality of ArrayPool<T>. If everything seems fine with its features and usage, I will release it and mention you.

Thank you for reporting the issue.

    [Test]
    public void Test()
    {
        var rand = new RcRand();
        for (int loop = 0; loop < 1024; ++loop)
        {
            int length = (int)(rand.Next() * 2048);
            var values = RandomValues(length);
            using var array = RcRentedArray.RentDisposableArray<float>(length);

            for (int i = 0; i < array.Length; ++i)
            {
                array[i] = values[i];
            }

            for (int i = 0; i < array.Length; ++i)
            {
                Assert.That(array[i], Is.EqualTo(values[i]));
            }
            
            Assert.That(array[^1], Is.EqualTo(values[^1]));
            
            Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
            Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
            Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
            Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
        }
    }

ikpil avatar Jan 21 '24 10:01 ikpil

step0 step1 step2 step3

ikpil avatar Jan 22 '24 16:01 ikpil

next !!

ikpil avatar Jan 24 '24 15:01 ikpil

in DtNodePool.GetNode() image

ikpil avatar Jan 24 '24 15:01 ikpil

in DtNavMesh.GetPolyHeight()

image

ikpil avatar Jan 24 '24 15:01 ikpil

in DtPathUtils.MergeCorridorStartMoved() image

ikpil avatar Jan 24 '24 15:01 ikpil

in RcRentedArray()

image

ikpil avatar Jan 24 '24 15:01 ikpil

in DtNavMeshQuery.Raycast()

image

ikpil avatar Jan 24 '24 15:01 ikpil

new Vector3f[3] in DtNavMesh.GetPolyHeight() image

ikpil avatar Jan 25 '24 16:01 ikpil

DtNodePool.GetNode() image

ikpil avatar Jan 25 '24 16:01 ikpil

new DtRaycastHit() in DtNavMeshQuery.Raycast() image

ikpil avatar Jan 25 '24 16:01 ikpil

result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath)) in DtPathUtils.MergeCorridorStartMoved() image

ikpil avatar Jan 25 '24 16:01 ikpil

  1. use stackalloc keywork
  2. use NativeMemory

https://github.com/bepu/bepuphysics2

Sarofc avatar Jan 29 '24 08:01 Sarofc

  1. use stackalloc keywork
  2. use NativeMemory

https://github.com/bepu/bepuphysics2

Thanks for your input! However, to support various compatibility/platforms/debugging, we cannot use unmanaged code.

Is there any other way? I'm currently testing using ArrayPool<T>, there's a slight slowdown in speed.

ikpil avatar Jan 29 '24 15:01 ikpil

  1. use stackalloc keywork
  2. use NativeMemory

https://github.com/bepu/bepuphysics2

Thanks for your input! However, to support various compatibility/platforms/debugging, we cannot use unmanaged code.

Is there any other way? I'm currently testing using ArrayPool, there's a slight slowdown in speed.

If you mark the assembly or whatever it's called as unsafe, on the user side we would need to allow usage of unsafe code, no? So I guess it's possible?

Or is there more to it than enabling the unsafe code usage?

sky-danilomenezes avatar Jan 29 '24 15:01 sky-danilomenezes

  1. use stackalloc keywork
  2. use NativeMemory

https://github.com/bepu/bepuphysics2

Thanks for your input! However, to support various compatibility/platforms/debugging, we cannot use unmanaged code. Is there any other way? I'm currently testing using ArrayPool, there's a slight slowdown in speed.

If you mark the assembly or whatever it's called as unsafe, on the user side we would need to allow usage of unsafe code, no? So I guess it's possible?

Or is there more to it than enabling the unsafe code usage?

From a library perspective, I'm trying to develop only safe code. First, let’s fix the SOH that occurs structurally.

ikpil avatar Jan 29 '24 17:01 ikpil

  1. use stackalloc keywork
  2. use NativeMemory

https://github.com/bepu/bepuphysics2

Thanks for your input! However, to support various compatibility/platforms/debugging, we cannot use unmanaged code.

Is there any other way? I'm currently testing using ArrayPool, there's a slight slowdown in speed.

Unsafe code is cross platform, and anywhere in bcl and unity (UnsafeUtility/Unity, NativeMemory/DotNet), but maybe cause memory leak.

Use unsafe code in lib(gc free), expose Safe/Unsafe api for user code is a good way.

for example

unsafe int Physics.Raycast(Span<HitResult> outHits){
    for(int i = 0, i < outHits.Length; i++)
        outHits[i] = new HitResult();
    return outHits.Length;
}

// use managed collection
var list = new List<HitResult>(4);
CollectionsMarshal.SetCount(list, 4);
var span = CollectionsMarshal.AsSpan(list);
// Span<T> span = stackalloc HitResult[4]; // if temp
var hitCount = Physics.Raycast(span);

// use unsafe collection // need unsafe blocks
unsafe{
    var ptr = NativeMemory.Alloc(sizeof(HitReuslt) * 4);
    Span<HitReuslt> span = new Span<HitReuslt>(ptr, 4);
    var hitCount = Physics.Raycast(span);
    NativeMemory.Free(ptr);
}

If dont want to use unsafe code

  1. Span<T> temp = stackalloc T[Size] for small temp array
  2. ArrayPool<T>.Return(array, clearArray), pass clearArray by false if T is UnmanagedStruct(RuntimeHelpers.IsReferenceOrContainsReferences)
  3. Use Span and Unsafe.Add/MemoryMarshal/CollectionsMarshal api to improve performance

Sarofc avatar Jan 30 '24 03:01 Sarofc

@Sarofc I will check if there is any performance improvement through benchmarking.

ikpil avatar Jan 30 '24 13:01 ikpil

benchmark source map https://github.com/ikpil/DotRecast/issues/10#issuecomment-1925555592

ikpil avatar Feb 04 '24 02:02 ikpil

before image

ikpil avatar Feb 04 '24 03:02 ikpil

Nice!! It seems we're down to 12 issues in other places.

Screenshot 2024-02-04 at 10 31 50

Here's my project my project so you can play around since I don't discard the possiblity I'm doing things in a wrong way. If you change the postgres address to point to local and user the docker-compose inside deploy it should work just fine. A manual migration of EF still needs to be triggered

Danil0v3s avatar Feb 04 '24 10:02 Danil0v3s

version 2024.1.2

  • Ryzen 5600X

Ryzen5600X 2024-02-07 134409

ikpil avatar Feb 07 '24 04:02 ikpil

version 2024.1.2

  • Rayzen 5625U

image

ikpil avatar Feb 11 '24 05:02 ikpil

Hey @ikpil, what are we looking for exactly in those comparison images? Would you write the comparison numbers down? I've tried looking for the same data in the pictures but it seems they change place

Danil0v3s avatar Feb 11 '24 18:02 Danil0v3s

The value currently being compared is the Avg Update Time

If SOH issues are improperly addressed, there tends to be a decline in performance. We will compare 2024.1.2 with the upcoming 2024.1.3.

ikpil avatar Feb 12 '24 04:02 ikpil

2024.1.3

  • Ryzen 5600X
  • reuse DtNode

DOTRECAST-2024-1-3

ikpil avatar Feb 13 '24 04:02 ikpil