Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: [2.6]C# bindings and Text allocation  (Read 1485 times)

0 Members and 2 Guests are viewing this topic.

ThePersonWithAQuestion

  • Newbie
  • *
  • Posts: 4
    • View Profile
[2.6]C# bindings and Text allocation
« on: January 11, 2025, 07:55:04 pm »
Hello there, first I'd like to congratulate to the team behind SFML for their continuous effort into supporting and improving the library after all these years. I have known about the existence of the lib for more than 10 years but only recently I decided to employ it, and I'm glad it is so easy to use and well documented.

Now, I have some concerns with the Text class. For a game I'm making, I reuse one Text instance for rendering strings, but I noticed there are some copying under the hood every time I assign the DisplayedString property. This means that, in a couple of seconds rendering at 60FPS, it can easily produce more than 1000 instances.
For someone with more experience than me, is there a way to work with them while keeping the amount of allocation low? Thanks

ThePersonWithAQuestion

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: [2.6]C# bindings and Text allocation
« Reply #1 on: January 12, 2025, 11:27:43 am »
Looks like there is some sort of leak in DisplayedText. I fixed it by overriding the class and providing a different implementation. However, because it is not virtual, you must always use the inherited class!
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using SFML.System;

namespace FooBar;

public sealed partial class Text2: SFML.Graphics.Text
{
    [ThreadStatic]
    private static int[]? _utf32;
    private string _value = "";

    public new string DisplayedString
    {
        get => _value;
        set
        {
            _value = value;
            var len = Encoding.UTF32.GetMaxByteCount(value.Length + 1);
            if (_utf32 == null || len > _utf32.Length) _utf32 = new int[len];

            var i = 0;
            foreach (var rune in value.EnumerateRunes()) _utf32[i++] = rune.Value;
            _utf32[i] = 0;
            unsafe { fixed (int* ptr = _utf32) { sfText_setUnicodeString(CPointer, (IntPtr)ptr); } }
        }
    }
   
    [LibraryImport(CSFML.graphics), SuppressUnmanagedCodeSecurity]
    [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
    private static partial void sfText_setUnicodeString(IntPtr CPointer, IntPtr Text);
}
 
« Last Edit: January 13, 2025, 01:10:05 pm by ThePersonWithAQuestion »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11116
    • View Profile
    • development blog
    • Email
Re: [2.6]C# bindings and Text allocation
« Reply #2 on: January 13, 2025, 09:26:50 am »
Hello there, first I'd like to congratulate to the team behind SFML for their continuous effort into supporting and improving the library after all these years. I have known about the existence of the lib for more than 10 years but only recently I decided to employ it, and I'm glad it is so easy to use and well documented.
Thanks! :)

every time I assign the DisplayedString property. This means that, in a couple of seconds rendering at 60FPS, it can easily produce more than 1000 instances.
Are you actually changing the text or are you also updating it even if nothing has changed?

We're marshalling the string between C and C# for every get and set, which does some additional allocations.

I guess it would make sense to add some caching, similar to how you did it, and only get the text anew, if we detect some reconstruction or CPointer change
Official FAQ: https://www.sfml-dev.org/faq/
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

ThePersonWithAQuestion

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: [2.6]C# bindings and Text allocation
« Reply #3 on: January 13, 2025, 01:01:56 pm »
I'm not acquainted with C# language, this is my very first project in a long long time (ten years). I called it a leak but it may be possible that, due to the small size of the strings, the GC never triggers. I had an issue where I had allocated more than 300.000 instances of my point struct because it was actually a record, and at first I thought they were value types too.

Indeed I'm assigning a new value even when there is no change because I was using one Text instance for everything. Assigning a new value allocates a temporal byte array and string too when it concatenates with the null character.

I'm not sure how laggy the GC can get from collecting these but the change was simple to implement, and I think having a large shared buffer (_utf32 could be made static to further reduce the cost) is more beneficial than many short lived small objects that the GC has to collect every now and then.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11116
    • View Profile
    • development blog
    • Email
Re: [2.6]C# bindings and Text allocation
« Reply #4 on: January 13, 2025, 01:42:13 pm »
It all depends a bit on the usage pattern. Since humans can only read so fast, the more common case will certainly be, that text is only changed every few seconds, rather than 60+ times a second.

A static buffer would require decisions around how large or small it would need to be and you'll either be wasting memory or end up having to reallocate again anyways.

I've opened an issue to introduce some caching, as I do think it's overly wasteful to do full marshalling all the time.
Official FAQ: https://www.sfml-dev.org/faq/
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

ThePersonWithAQuestion

  • Newbie
  • *
  • Posts: 4
    • View Profile
Re: [2.6]C# bindings and Text allocation
« Reply #5 on: January 13, 2025, 02:10:57 pm »
Lol, my use case is not as naive as it sounds. I want to display text one character at a time, for that initial animation I have no choice than to set a new slice of text every time.

kojack

  • Sr. Member
  • ****
  • Posts: 358
  • C++/C# game dev teacher.
    • View Profile
Re: [2.6]C# bindings and Text allocation
« Reply #6 on: January 13, 2025, 04:09:48 pm »
I called it a leak but it may be possible that, due to the small size of the strings, the GC never triggers.
I did a memory profile (using a time limited trial of .Net Memory Profiler) of a C# ray tracer I wrote.
What was interesting was that it showed the garbage collector was only triggering when deleted but not collected memory reached around 120MB. Then it would do a garbage collection, and start again. The garbage collector memory usage looked like a saw tooth wave.

So the collection is delayed until the collector thinks it needs to, it will look like a memory leak in between collections.

I was able to double the performance of the raytracer by changing the ray intersect function to use an out parameter instead of a return, which made the garbage collector activate less often. This is because the out parameter overwrote an existing variable while the return meant a temporary object.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11116
    • View Profile
    • development blog
    • Email
Re: [2.6]C# bindings and Text allocation
« Reply #7 on: January 13, 2025, 04:39:43 pm »
In case it's unclear, I agree with you that we should be optimizing this function. :)
I merely tried to point out that in the realm of all possibilities, updating the string every frame isn't the case with the highest usage percentage.

my use case is not as naive as it sounds.
That's not what I wanted to say, sorry if it came across like that.

Even with your use case the ratio between "animated" text and "static" text will weigh in favor of static texts (i.e. you render more frames with text that hasn't changed between frames, than text that has changed between frames).
As such it's best to optimize for the "static" text rendering and accept some performance penalty when "animating" text.

Hope that was a bit clearer to what I meant.  :)

So the collection is delayed until the collector thinks it needs to, it will look like a memory leak in between collections.
GC actions are unpredictable, which is one of the major downsides of GC
Official FAQ: https://www.sfml-dev.org/faq/
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

 

anything