1

我正在尝试为我正在开发的游戏编写通用套接字服务器。我知道我可以很好地使用 SmartFox 和 Photon 等已经构建的服务器,但我不想经历为学习目的而自己创建一个服务器的痛苦。

我提出了一个受 BSON 启发的协议,将基本数据类型、它们的数组和特殊的 GSObject 转换为二进制,并以某种方式排列它们,以便可以在客户端将它们重新组合成对象形式。在核心,转换方法利用 .Net BitConverter 类将基本数据类型转换为二进制。无论如何,问题在于性能,如果我循环 50,000 次并将我的 GSObject 转换为二进制每次大约需要 5500 毫秒(每次转换的结果 byte[] 仅为 192 个字节)。我认为这对于一个每秒发送 5-10 个位置更新和 1000 个并发用户的 MMO 来说太慢了。是的,我知道一款游戏不太可能同时拥有 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");

    }

这是上面代码中使用的一些其他类的代码。大部分都是重复的。

对象

GSArray

GSWrappedObject

4

2 回答 2

2

我的第一个预感是,你的很多时间都花在了不断地重新创建数组和列表上。

我倾向于转向基于流的方法,而不是尝试不断地创建数组。也就是说,让所有 GSBinary 方法接受一个 Stream 然后写入它而不是创建自己的数组,然后如果你想在本地内存中使用 aMemoryStream在基础上,然后在最后取出你的数组(或者甚至更好如果您计划将其作为网络应用程序,请直接写入网络流)。

根据 Chris 之前的评论,最好的开始方法是在 dotTrace 或 redgate 的 ANTS 性能分析器上运行分析器,以实际找出哪个步骤花费的时间最多,然后再投入时间重构某些东西,虽然效率低下,但可能只是一个小问题实际时间的一小部分。

于 2012-04-02T23:24:44.813 回答
1

1) ElementAt 非常昂贵。使用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), 等

于 2012-04-02T23:21:22.947 回答