Question Performance of calling a function from class

cynerboy

New member
Joined
Jan 2, 2024
Messages
2
Programming Experience
1-3
Does calling a function from a class with hundreds of functions run slower than calling a function from a class with one function?
 
Solution
Sounds like an interview question. :)

No, it does not run slower.

On the other hand, calling a function with one parameter vs calling a function with hundreds of parameters will be slower because more stuff needs to be pushed and popped off the stack versus being able to just pass parameters via registers.
Sounds like an interview question. :)

No, it does not run slower.

On the other hand, calling a function with one parameter vs calling a function with hundreds of parameters will be slower because more stuff needs to be pushed and popped off the stack versus being able to just pass parameters via registers.
 
Solution
Ah.... That changes a few things. If you have C# code directly calling other C# code, then the number of methods in either the calling class or the callee class doesn't matter.

I don't know enough about Godot, but if Godot uses any kind of reflection to make that initial call into the C# class, then the more methods there are in a class, the longer it will take to make the initial call. This is because when using reflection the method that needs to be called needs to be found at runtime. (This is as opposed to the compiler finding the called method at compile time.) If the engine is any good, it should cache this information and succeeding calls should be cheap, or at least the cost of the calls will be amortized and become cheap eventually.

Anyway, if you are following good object oriented programming practices, you would be following the Single Responsibility Principle. That would pretty inherently make you write classes that just have a few methods. Once you start running into hundreds of methods (or do I daresay even over 20 methods), you would need to start questioning yourself about whether your are following SRP or not, or whether you have the correct architecture or not.
 
Depends on the code using reflection. Some code will cache. Some code will not. It depends on the expected usage pattern.
 
That's not what I'm talking about though. Obviously you can cache reflection discovery information but what happens if you don't? Consider this code:
ReflectionPerformance:
using System.Diagnostics;

namespace ReflectionPerformance
{
    public class Program
    {
        public static void Main()
        {
            for (var i = 1; i < 10; i++)
            {
                AddNormally();
            }

            for (var i = 1; i < 10; i++)
            {
                AddReflectively();
            }
        }

        private static void AddNormally()
        {
            var sw = new Stopwatch();
            sw.Start();

            var o = new ReflectionTarget();
            var i = o.Add(1, 2);

            sw.Stop();

            Console.WriteLine($"Add normally: found {i} in {sw.ElapsedTicks} ticks");
        }

        private static void AddReflectively()
        {
            var sw = new Stopwatch();
            sw.Start();

            var methodInfo = typeof(ReflectionTarget).GetMethod("Add");
            var instance = Activator.CreateInstance(typeof(ReflectionTarget));
            var result = methodInfo.Invoke(instance, new object[] { 1, 2 });
            var i = (int)result;

            sw.Stop();

            Console.WriteLine($"Add reflectively: found {i} in {sw.ElapsedTicks} ticks");
        }
    }

    public class ReflectionTarget
    {
        public int Add(int x, int y)
        {
            return x + y;
        }
    }
}

The results I get by running without debugging:
Results of run:
Add normally: found 3 in 570 ticks
Add normally: found 3 in 2 ticks
Add normally: found 3 in 1 ticks
Add normally: found 3 in 1 ticks
Add normally: found 3 in 1 ticks
Add normally: found 3 in 0 ticks
Add normally: found 3 in 1 ticks
Add normally: found 3 in 1 ticks
Add normally: found 3 in 1 ticks
Add reflectively: found 3 in 734 ticks
Add reflectively: found 3 in 1933 ticks
Add reflectively: found 3 in 28 ticks
Add reflectively: found 3 in 14 ticks
Add reflectively: found 3 in 10 ticks
Add reflectively: found 3 in 10 ticks
Add reflectively: found 3 in 13 ticks
Add reflectively: found 3 in 10 ticks
Add reflectively: found 3 in 10 ticks

The first ten results I understand: first JIT and run (takes some time), then just run (quick).
The last ten results I understand only if there is some level of optimization performed by the runtime. Thoughts?
 
Last edited:
Probably some kind of optimization performed by the current runtime. When I did a similar type of experiment back in the early 2000's there was no tapering off of the reflection code. I was hoping at that time that there would be so that I didn't have to write my own caching code.
 
Right, there seems to be internal .Net runtime optimization around reflection. Dug a tad deeper.

ReflectionPerformance:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;

namespace ReflectionPerformance
{
    public class Program
    {

        public static void Main(string[] args)
        {
            AddNormally();

            var summary = BenchmarkRunner.Run(typeof(BenchmarkReflection).Assembly);
        }

        private static void AddNormally()
        {
            var o = new ReflectionTarget();
            var i = o.Add(1, 2);
        }

    }

    public class BenchmarkReflection
    {
        private static readonly MethodInfo? MethodCall = typeof(ReflectionTarget).GetMethod("Add");

        [Benchmark]
        public void AddReflectivelyWithCachedMethodInfo()
        {
            var instance = Activator.CreateInstance(typeof(ReflectionTarget));
            var result = MethodCall.Invoke(instance, new object[] { 1, 2 });
            var i = (int)result;
        }

        [Benchmark]
        public void AddReflectively()
        {
            var methodInfo = typeof(ReflectionTarget).GetMethod("Add");
            var instance = Activator.CreateInstance(typeof(ReflectionTarget));
            var result = methodInfo.Invoke(instance, new object[] { 1, 2 });
            var i = (int)result;
        }
    }

    public class ReflectionTarget
    {
        public int Add(int x, int y)
        {
            return x + y;
        }
    }
}

Results of that:
BenchmarkDotNet results:
BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.3803/22H2/2022Update)
Intel Core i9-9900K CPU 3.60GHz (Coffee Lake), 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.100
  [Host]     : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 [AttachedDebugger]
  DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2


| Method                              | Mean     | Error    | StdDev   |
|------------------------------------ |---------:|---------:|---------:|
| AddReflectivelyWithCachedMethodInfo | 39.15 ns | 0.757 ns | 0.743 ns |
| AddReflectively                     | 60.63 ns | 0.703 ns | 0.623 ns |

Which proves what you said was right: caching will speed up reflection calls. Depending on the scenario this might make considerable impact and, judging by the original poster's reference to a gaming engine, I guess it will make sense in this case.
 
Back
Top Bottom