ikkentim / SampSharp

A framework for writing game modes for SA-MP in C#. SA-MP is a free Massively Multiplayer Online game mod for the PC version of Rockstar Games Grand Theft Auto: San Andreas.
https://sampsharp.net
Apache License 2.0
210 stars 41 forks source link

Improve performance of callback handling #399

Closed ikkentim closed 2 years ago

ikkentim commented 2 years ago

Benchmark:

public class BenchmarkNatives
{
    [NativeMethod] 
    public virtual void CallRemoteFunction(string func, string format, params object[] args)
    {
        throw new NativeNotImplementedException();
    }
}

public class GameMode : BaseMode
{
    private (string, int, float)? _benchStore;

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        // (...)

        Console.WriteLine("Bench start");
        RunCallbackBenchmark();
    }

    private void RunCallbackBenchmark()
    {
        var natives = NativeObjectProxyFactory.CreateInstance<BenchmarkNatives>();
        var args = new object[] { "stringValue", 4321, 23.665f };
        var sw = new Stopwatch();

        const int maxRuns = 10;
        const int runCallCount = 1_000_000;

        var totalElapsed = TimeSpan.Zero;
        for (var j = 0; j <= maxRuns; j++)
        {
            Console.WriteLine($"Bench run {j}/{maxRuns}");
            if (j == 0) Console.WriteLine("(warmup)");

            sw.Restart();

            for (var i = 0; i < runCallCount; i++)
            {
                natives.CallRemoteFunction("BenchmarkCallback", "sdf", args);

                // validate

                if (!_benchStore.HasValue || _benchStore.Value.Item1 != "stringValue" ||
                    _benchStore.Value.Item2 != 4321 || Math.Abs(_benchStore.Value.Item3 - 23.665f) > 0.001f)
                {
                    throw new InvalidOperationException("native call failed!");
                }

                _benchStore = null;
            }

            sw.Stop();

            if (j > 0)
                totalElapsed += sw.Elapsed;
            Console.WriteLine($"{runCallCount} calls took {sw.Elapsed}");
        }

        Console.WriteLine($"AVG of {totalElapsed / maxRuns}");
    }

    [Callback]
    public void BenchmarkCallback(string stringValue, int intValue, float floatValue)
    {
        _benchStore = (stringValue, intValue, floatValue);
    }
}
ikkentim commented 2 years ago

a53825777286d8b2196406bb77a229cdf680f7f0 performance. compiled plugin and .net libraries in release mode:

ikkentim commented 2 years ago

changed benchmark to perform 1_000_000 instead of 100_000 in 9649ad685dd393182fbcb080d756a0e7313c518b

benchmark times are:

AVG of 00:00:02.0727976
AVG of 00:00:02.0378712
AVG of 00:00:02.0661319
ikkentim commented 2 years ago

1179c631324ddf73a8fdf5142aff30d5dca71208

benchmark times are:

AVG of 00:00:01.5029470
AVG of 00:00:01.4856173
AVG of 00:00:01.5092219

So that looks like a (1 - 150 ÷ 205 =) ~27% performance improvement already

ikkentim commented 2 years ago

Also tested this on an Ubuntu VM @ 4a55771aa96454cfd9e6ef939c7328ad784efd88

AVG of 00:00:01.7994443
AVG of 00:00:01.7907788
AVG of 00:00:01.7601093

When tested on master branch @ 9649ad685dd393182fbcb080d756a0e7313c518b

AVG of 00:00:02.5606534
AVG of 00:00:02.5192036
AVG of 00:00:02.5845466

So that also looks like a (1 - 1,78344 / 2,5548 =) ~30% performance improvement

ikkentim commented 2 years ago

Good enough for now, merged.