• 2 Posts
  • 165 Comments
Joined 2 years ago
cake
Cake day: July 2nd, 2023

help-circle

  • My wife runs a large online D&D guild, with games 4-5 nights a week (not that you play in all of them, they just have that many people). They’re no strangers to kicking people that cause drama, and they have several trans and non-binary members already.

    I will say that they might not be able to bring their character in, as-is, if it’s heavily based on “official” D&D lore and sourcebooks. The group has spent 8 years building all their own lore and tuning some homebrew rules and classes. It’s mostly built on the 5e foundation, though.

    I believe this is where they’d go to apply.











  • I’m gonna hazard a guess, just cause I’m curious, that you’re coming from JavaScript.

    Regardless, the answer’s basically the same across all similar languages where this question makes sense. That is, languages that are largely, if not completely, object-oriented, where memory is managed for you.

    Bottom line, object allocation is VERY expensive. Generally, objects are allocated on a heap, so the allocation process itself, in its most basic form, involves walking some portion of a linked list to find an available heap block, updating a header or other info block to track that the block is now in use, maybe sub-dividing the block to avoid wasting space, any making any updates that might be necessary to nodes of the linked list that we traversed.

    THEN, we have to run similar operations later for de-allocation. And if we’re talking about a memory-managed language, well, that means running a garbage collector algorithm, periodically, that needs to somehow inspect blocks that are in use to see if they’re still in use, or can be automatically de-allocated. The most common garbage-collector I know of involves tagging all references within other objects, so that the GC can start at the “root” objects and walk the entire tree of references within references, in order to find any that are orphaned, and identify them as collectable.

    My bread and butter is C#, so let’s look at an actual example.

    public class MyMutableObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    }
    
    public record MyImmutableObject
    {
        public required ulong Id { get; init; }
    
        public required string Name { get; init; }
    }
    
    _immutableInstance = new()
    {
        Id      = 1,
        Name    = "First"
    };
    
    _mutableInstance = new()
    {
        Id      = 1,
        Name    = "First"
    };
    
    [Benchmark(Baseline = true)]
    public MyMutableObject MutableEdit()
    {
        _mutableInstance.Name = "Second";
    
        return _mutableInstance;
    }
    
    [Benchmark]
    public MyImmutableObject ImmutableEdit()
        => _immutableInstance with
        {
            Name = "Second"
        };
    
    Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
    MutableEdit 1.080 ns 0.0876 ns 0.1439 ns 1.02 0.19 - - NA
    ImmutableEdit 8.282 ns 0.2287 ns 0.3353 ns 7.79 1.03 0.0076 32 B NA

    Even for the most basic edit operation, immutable copying is slower by more than 7 times, and (obviously) allocates more memory, which translates to more cost to be spent on garbage collection later.

    Let’s scale it up to a slightly-more realistic immutable data structure.

    public class MyMutableParentObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyMutableChildObject Child { get; set; }
    }
    
    public class MyMutableChildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyMutableGrandchildObject FirstGrandchild { get; set; }
                
        public required MyMutableGrandchildObject SecondGrandchild { get; set; }
                
        public required MyMutableGrandchildObject ThirdGrandchild { get; set; }
    }
    
    public class MyMutableGrandchildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    }
    
    public record MyImmutableParentObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyImmutableChildObject Child { get; set; }
    }
    
    public record MyImmutableChildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    
        public required MyImmutableGrandchildObject FirstGrandchild { get; set; }
                
        public required MyImmutableGrandchildObject SecondGrandchild { get; set; }
                
        public required MyImmutableGrandchildObject ThirdGrandchild { get; set; }
    }
    
    public record MyImmutableGrandchildObject
    {
        public required ulong Id { get; set; }
    
        public required string Name { get; set; }
    }
    
    _immutableTree = new()
    {
        Id      = 1,
        Name    = "Parent",
        Child   = new()
        {
            Id                  = 2,
            Name                = "Child",
            FirstGrandchild     = new()
            {
                Id      = 3,
                Name    = "First Grandchild"
            },
            SecondGrandchild    = new()
            {
                Id      = 4,
                Name    = "Second Grandchild"
            },
            ThirdGrandchild     = new()
            {
                Id      = 5,
                Name    = "Third Grandchild"
            },
        }
    };
    
    _mutableTree = new()
    {
        Id      = 1,
        Name    = "Parent",
        Child   = new()
        {
            Id                  = 2,
            Name                = "Child",
            FirstGrandchild     = new()
            {
                Id      = 3,
                Name    = "First Grandchild"
            },
            SecondGrandchild    = new()
            {
                Id      = 4,
                Name    = "Second Grandchild"
            },
            ThirdGrandchild     = new()
            {
                Id      = 5,
                Name    = "Third Grandchild"
            },
        }
    };
    
    [Benchmark(Baseline = true)]
    public MyMutableParentObject MutableEdit()
    {
        _mutableTree.Child.SecondGrandchild.Name = "Second Grandchild Edited";
    
        return _mutableTree;
    }
    
    [Benchmark]
    public MyImmutableParentObject ImmutableEdit()
        => _immutableTree with
        {
            Child = _immutableTree.Child with
            {
                SecondGrandchild = _immutableTree.Child.SecondGrandchild with
                {
                    Name = "Second Grandchild Edited"
                }
            }
        };
    
    Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
    MutableEdit 1.129 ns 0.0840 ns 0.0825 ns 1.00 0.10 - - NA
    ImmutableEdit 32.685 ns 0.8503 ns 2.4534 ns 29.09 2.95 0.0306 128 B NA

    Not only is performance worse, but it drops off exponentially, as you scale out the size of your immutable structures.


    Now, all this being said, I myself use the immutable object pattern FREQUENTLY, in both C# and JavaScript. There’s a lot of problems you encounter in business logic that it solves really well, and it’s basically the ideal type of data structure for use in reactive programming, which is extremely effective for building GUIs. In other words, I use immutable objects a ton when I’m building out the business layer of a UI, where data is king. If I were writing code within any of the frameworks I use to BUILD those UIs (.NET, WPF, ReactiveExtensions) you can bet I’d be using immutable objects way more sparingly.








  • I’m not sure which ones he was into at 4yo specifically, but my son’s Switch favorites include…

    Super Mario Odyssey Mario + Rabbids: Kingdom Battle Celeste Minecraft Yoshi’s Crafted World Letterquest Big Brain Academy NES Arcade SNES Arcade

    Of those, the ones I would say mught meet your super-chill criteria are…

    Super Mario Odyssey (yeah, you can die, but you just respawn and can spend tons of time just running around aimlessly) Celeste (normally not, but there’s a lovely Assist Mode) Yoshi’s Crafted World (there’s a no-fail mode) Big Brain Academy (if they can handle being scored on things, without taking it too seriously).