улучшение преобразования в двоичную и обратно в C # - PullRequest
1 голос
/ 03 апреля 2012

Я пытаюсь написать сервер сокетов общего назначения для игры, над которой я работаю. Я знаю, что могу очень хорошо использовать уже построенные серверы, такие как SmartFox и Photon, но я не хочу испытывать трудности, связанные с его созданием в учебных целях.

Я придумал протокол, основанный на BSON, для преобразования основных типов данных, их массивов и специального GSObject в двоичный файл и упорядочения их таким образом, чтобы их можно было собрать обратно в форму объекта на клиенте конец. По сути, методы преобразования используют класс .Net BitConverter для преобразования основных типов данных в двоичные. В любом случае, проблема заключается в производительности, если я зациклюсь 50000 раз и преобразую свой GSObject в двоичный файл каждый раз, когда для этого требуется около 5500 мс (результирующий байт [] составляет всего 192 байта на преобразование). Я думаю, что это будет слишком медленно для MMO, который отправляет 5-10 обновлений позиции в секунду с 1000 одновременно работающих пользователей. Да, я знаю, что маловероятно, что в игре одновременно будет 1000 пользователей, но, как я уже говорил ранее, это должно быть процессом обучения для меня, я хочу пойти дальше и создать что-то, что хорошо масштабируется и может обслуживать не менее нескольких тысяч пользователей.

Так что, если кто-то знает о других методах преобразования или видит, где я теряю производительность, я был бы признателен за помощь.

GSBitConverter.cs

Это основной класс преобразования, он добавляет методы расширения к основным типам данных для преобразования в двоичный формат. Он использует класс BitConverter для преобразования базовых типов. Я показал только код для преобразования целочисленных и целочисленных массивов, но остальная часть метода в значительной степени является копией этих двух, они просто перегружают тип.

public static class GSBitConverter
{
    public static byte[] ToGSBinary(this short value)
    {
        return BitConverter.GetBytes(value);
    }

    public static byte[] ToGSBinary(this IEnumerable<short> value)
    {
        List<byte> bytes = new List<byte>();
        short length = (short)value.Count();

        bytes.AddRange(length.ToGSBinary());
        for (int i = 0; i < length; i++)
            bytes.AddRange(value.ElementAt(i).ToGSBinary());

        return bytes.ToArray();
    }

    public static byte[] ToGSBinary(this bool value);
    public static byte[] ToGSBinary(this IEnumerable<bool> value);

    public static byte[] ToGSBinary(this IEnumerable<byte> value);

    public static byte[] ToGSBinary(this int value);
    public static byte[] ToGSBinary(this IEnumerable<int> value);

    public static byte[] ToGSBinary(this long value);
    public static byte[] ToGSBinary(this IEnumerable<long> value);

    public static byte[] ToGSBinary(this float value);
    public static byte[] ToGSBinary(this IEnumerable<float> value);

    public static byte[] ToGSBinary(this double value);
    public static byte[] ToGSBinary(this IEnumerable<double> value);

    public static byte[] ToGSBinary(this string value);
    public static byte[] ToGSBinary(this IEnumerable<string> value);

    public static string GetHexDump(this IEnumerable<byte> value);
}

Program.cs Вот объект, который я преобразовываю в двоичный файл в цикле.

class Program
{
    static void Main(string[] args)
    {
        GSObject obj = new GSObject();
        obj.AttachShort("smallInt", 15);
        obj.AttachInt("medInt", 120700);
        obj.AttachLong("bigInt", 10900800700);
        obj.AttachDouble("doubleVal", Math.PI);
        obj.AttachStringArray("muppetNames", new string[] { "Kermit", "Fozzy", "Piggy", "Animal", "Gonzo" });

        GSObject apple = new GSObject();
        apple.AttachString("name", "Apple");
        apple.AttachString("color", "red");
        apple.AttachBool("inStock", true);
        apple.AttachFloat("price", (float)1.5);

        GSObject lemon = new GSObject();
        apple.AttachString("name", "Lemon");
        apple.AttachString("color", "yellow");
        apple.AttachBool("inStock", false);
        apple.AttachFloat("price", (float)0.8);

        GSObject apricoat = new GSObject();
        apple.AttachString("name", "Apricoat");
        apple.AttachString("color", "orange");
        apple.AttachBool("inStock", true);
        apple.AttachFloat("price", (float)1.9);

        GSObject kiwi = new GSObject();
        apple.AttachString("name", "Kiwi");
        apple.AttachString("color", "green");
        apple.AttachBool("inStock", true);
        apple.AttachFloat("price", (float)2.3);

        GSArray fruits = new GSArray();
        fruits.AddGSObject(apple);
        fruits.AddGSObject(lemon);
        fruits.AddGSObject(apricoat);
        fruits.AddGSObject(kiwi);

        obj.AttachGSArray("fruits", fruits);

        Stopwatch w1 = Stopwatch.StartNew();
        for (int i = 0; i < 50000; i++)
        {
            byte[] b = obj.ToGSBinary();
        }
        w1.Stop();

        Console.WriteLine(BitConverter.IsLittleEndian ? "Little Endian" : "Big Endian");
        Console.WriteLine(w1.ElapsedMilliseconds + "ms");

    }

Вот код для некоторых других моих классов, которые используются в коде выше. Большая часть повторяется.

GSObject

GSArray

GSWrappedObject

Ответы [ 2 ]

2 голосов
/ 03 апреля 2012

Моей первой догадкой, без особой надобности, было бы то, что большая часть вашего времени тратится на постоянное воссоздание массивов и списков.

Я был бы склонен перейти к потоковому подходу, а не пытаться создавать массивы постоянно. Таким образом, заставьте все методы GSBinary принимать поток, затем записывать в него, а не создавать свои собственные массивы, затем, если вы хотите, чтобы он находился в локальной памяти, используйте MemoryStream в базе, а затем выведите свой массив из него в конце ( Или еще лучше, если вы планируете, чтобы это было сетевое приложение, пишите прямо в сетевой поток).

В соответствии с комментарием Криса ранее, однако, лучший способ начать - запустить профилировщик, такой как dotTrace, или профилировщик производительности redgate ANTS, чтобы фактически выяснить, какой шаг занимает больше всего времени, прежде чем тратить время на рефакторинг чего-то, что, хотя и неэффективно, может только быть малой долей фактического времени.

1 голос
/ 03 апреля 2012

1) Элемент очень дорогой. Используйте foreach (var v in value) вместо for (int i = 0; i < length; i++) .. .ElementAt(i) ..

2) Методы ToGsBinary дороги, потому что они часто копируют массивы. Используйте подпись void WriteToGsBinary(Stream stream) вместо byte[] ToGsBinary()

3) Добавить перегрузки для массивов: void WriteToGsBinary(Stream stream, byte[] values), void WriteToGsBinary(Stream stream, short[] values) и т. Д.

...