gpt4 book ai didi

c# - 提高字符串解析性能

转载 作者:太空狗 更新时间:2023-10-29 21:54:22 25 4
gpt4 key购买 nike

在我们开始之前,我知道“过早优化”这个词。然而,以下片段已被证明是可以进行改进的领域。

好的。我们目前有一些网络代码可以处理基于字符串的数据包。我知道对数据包使用字符串是愚蠢、疯狂和缓慢的。遗憾的是,我们无法控制客户端,因此必须使用字符串。

每个数据包都由 \0\r\n 终止,我们目前使用 StreamReader/Writer 从流中读取单个数据包。我们的主要瓶颈来自两个地方。

首先:我们需要从字符串的末尾删除那个讨厌的小空字节。我们目前使用如下代码:

line = await reader.ReadLineAsync();
line = line.Replace("\0", ""); // PERF this allocates a new string
if (string.IsNullOrWhiteSpace(line))
return null;
var packet = ClientPacket.Parse(line, cl.Client.RemoteEndPoint);

正如您从那个可爱的小评论中看到的那样,我们在修剪“\0”时遇到了 GC 性能问题。有许多不同的方法可以从字符串的末尾删除 '\0',但所有方法都会导致我们得到的相同 GC 锤击。因为所有字符串操作都是不可变的,所以它们会导致创建一个新的字符串对象。由于我们的服务器处理 1000 多个连接,所有连接都以每秒 25-40 个数据包的速度进行通信(它是一个游戏服务器),这个 GC 问题正在成为一个问题。所以我的第一个问题来了:什么是更有效地从我们的字符串末尾修剪'\0'的方法?我所说的高效不仅意味着速度,还意味着 GC 明智(最终我想要一种无需创建新字符串对象即可摆脱它的方法!)。

我们的第二期也是源于GC land。我们的代码看起来有点像下面这样:

private static string[] emptyStringArray = new string[] { }; // so we dont need to allocate this
public static ClientPacket Parse(string line, EndPoint from)
{
const char seperator = '|';

var first_seperator_pos = line.IndexOf(seperator);
if (first_seperator_pos < 1)
{
return new ClientPacket(NetworkStringToClientPacketType(line), emptyStringArray, from);
}
var name = line.Substring(0, first_seperator_pos);
var type = NetworkStringToClientPacketType(name);
if (line.IndexOf(seperator, first_seperator_pos + 1) < 1)
return new ClientPacket(type, new string[] { line.Substring(first_seperator_pos + 1) }, from);
return new ClientPacket(type, line.Substring(first_seperator_pos + 1).Split(seperator), from);
}

(其中 NetworkStringToClientPacketType 只是一个大的 switch-case block )

如您所见,我们已经做了一些事情来处理 GC。我们重复使用静态“空”字符串,并检查没有参数的数据包。我这里唯一的问题是我们经常使用 Substring,甚至在 Substring 的末尾链接一个 Split。这导致(对于一个平均数据包)将近 20 个新的字符串对象被创建并且 12 个被处理为每个数据包。当负载增加超过 400 个用户时,这会导致很多性能问题(我们得到了快速 ram :3)

有没有人以前有过这类事情的经验,或者可以给我们一些关于下一步要研究什么的建议?也许是一些魔法类或一些漂亮的指针魔法?

(PS。StringBuilder 没有帮助,因为我们不是在构建字符串,我们通常是拆分它们。)

我们目前有一些基于索引系统的想法,我们存储每个参数的索引和长度而不是拆分它们。想法?

一些其他的事情。反编译 mscorlib 并浏览字符串类代码,在我看来 IndexOf 调用是通过 P/Invoke 完成的,这意味着它们为每次调用增加了开销,如果我错了请纠正我?使用 char[] 数组手动实现 IndexOf 不是更快吗?

public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
{
...
return TextInfo.IndexOfStringOrdinalIgnoreCase(this, value, startIndex, count);
...
}

internal static int IndexOfStringOrdinalIgnoreCase(string source, string value, int startIndex, int count)
{
...
if (TextInfo.TryFastFindStringOrdinalIgnoreCase(4194304, source, startIndex, value, count, ref result))
{
return result;
}
...
}

...

[DllImport("QCall", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool InternalTryFindStringOrdinalIgnoreCase(int searchFlags, string source, int sourceCount, int startIndex, string target, int targetCount, ref int foundIndex);

然后我们到达 String.Split,它最终调用 Substring 本身(沿线的某处):

// string
private string[] InternalSplitOmitEmptyEntries(int[] sepList, int[] lengthList, int numReplaces, int count)
{
int num = (numReplaces < count) ? (numReplaces + 1) : count;
string[] array = new string[num];
int num2 = 0;
int num3 = 0;
int i = 0;
while (i < numReplaces && num2 < this.Length)
{
if (sepList[i] - num2 > 0)
{
array[num3++] = this.Substring(num2, sepList[i] - num2);
}
num2 = sepList[i] + ((lengthList == null) ? 1 : lengthList[i]);
if (num3 == count - 1)
{
while (i < numReplaces - 1)
{
if (num2 != sepList[++i])
{
break;
}
num2 += ((lengthList == null) ? 1 : lengthList[i]);
}
break;
}
i++;
}
if (num2 < this.Length)
{
array[num3++] = this.Substring(num2);
}
string[] array2 = array;
if (num3 != num)
{
array2 = new string[num3];
for (int j = 0; j < num3; j++)
{
array2[j] = array[j];
}
}
return array2;
}

谢天谢地,子字符串看起来很快(而且高效!):

private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
if (startIndex == 0 && length == this.Length && !fAlwaysCopy)
{
return this;
}
string text = string.FastAllocateString(length);
fixed (char* ptr = &text.m_firstChar)
{
fixed (char* ptr2 = &this.m_firstChar)
{
string.wstrcpy(ptr, ptr2 + (IntPtr)startIndex, length);
}
}
return text;
}

看完这个答案后here ,我认为可以找到基于指针的解决方案...想法?

谢谢。

最佳答案

您可以“作弊”并在 Encoder 级别工作...

public class UTF8NoZero : UTF8Encoding
{
public override Decoder GetDecoder()
{
return new MyDecoder();
}
}

public class MyDecoder : Decoder
{
public Encoding UTF8 = new UTF8Encoding();

public override int GetCharCount(byte[] bytes, int index, int count)
{
return UTF8.GetCharCount(bytes, index, count);
}

public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
{
int count2 = UTF8.GetChars(bytes, byteIndex, byteCount, chars, charIndex);
int i, j;

for (i = charIndex, j = charIndex; i < charIndex + count2; i++)
{
if (chars[i] != '\0')
{
chars[j] = chars[i];
j++;
}
}

for (int k = j; k < charIndex + count2; k++)
{
chars[k] = '\0';
}

return count2 + (i - j);
}
}

请注意,此作弊是基于 StreamReader.ReadLineAsync 仅使用 GetChars() 这一事实。我们删除 StreamReader.ReadLineAsync 使用的临时缓冲区 char[] 缓冲区中的 '\0'。

关于c# - 提高字符串解析性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18692899/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com