C# Substring()似乎是这段代码的瓶颈
导言 我有一个我很喜欢的算法,它是我很久以前做的,我一直在用新的编程语言、平台等编写和重新编写,作为某种基准。虽然我的主要编程语言是C#,但我只是简单地复制粘贴了代码并稍微修改了语法,用Java构建了它,发现它的运行速度快了1000倍 代码 这里有很多代码,但我只想介绍这段代码,这似乎是主要问题:C# Substring()似乎是这段代码的瓶颈,c#,performance,substring,C#,Performance,Substring,导言 我有一个我很喜欢的算法,它是我很久以前做的,我一直在用新的编程语言、平台等编写和重新编写,作为某种基准。虽然我的主要编程语言是C#,但我只是简单地复制粘贴了代码并稍微修改了语法,用Java构建了它,发现它的运行速度快了1000倍 代码 这里有很多代码,但我只想介绍这段代码,这似乎是主要问题: for (int i = 0; i <= s1.Length; i++) { for (int j = i + 1; j <= s1.Length - i; j++) {
for (int i = 0; i <= s1.Length; i++)
{
for (int j = i + 1; j <= s1.Length - i; j++)
{
string _s1 = s1.Substring(i, j);
if (tree.hasLeaf(_s1))
...
探查器的屏幕截图片段
注意:这是用大小为300.000个字符的字符串s1测试的。由于某种原因,一百万个字符在C#中永远不会结束,而在Java中只需要0.75秒。。消耗的内存和垃圾收集的数量似乎并不表示内存问题。峰值约为400 MB,但考虑到巨大的后缀树,这似乎是正常的。也没有发现奇怪的垃圾收集模式
发行来源 在经历了一场持续两天三夜的光荣战斗(以及评论中的惊人想法和想法)之后,我终于解决了这个问题 我想为任何遇到类似问题的人发布一个答案,其中
string.Substring(I,j)
函数不是获取字符串子字符串的可接受解决方案,因为字符串太大,您无法负担string.Substring(I,j)
(它必须复制,因为C#字符串是不可变的,无法绕过它)或字符串。子字符串(i,j)
在同一个字符串上被调用了大量次(就像在我的嵌套for循环中),这给垃圾收集器带来了困难,或者在我的例子中两者都是
尝试
我尝试了许多建议,例如StringBuilder,流,在不安全{}中使用Intptr和封送进行非托管内存分配
阻塞,甚至创建IEnumerable,并通过引用返回给定位置内的字符。所有这些尝试最终都失败了,因为必须进行某种形式的数据连接,因为我无法轻松地逐个字符遍历我的树而不损及性能。如果有waY跨越一个数组内的多个内存地址,就像你将能够在C++中用一些指针算法……除了…
(归功于@Ivan Stoev的评论)
解决方案
解决方案是使用System.ReadOnlySpan
(由于字符串是不可变的,所以不能是System.Span
),它允许我们读取现有数组中内存地址的子数组,而无需创建副本
发布的这段代码:
string _s1 = s1.Substring(i, j);
if (stree.has(_s1))
{
score += j - i;
longest = j - i;
}
已更改为以下内容:
if (stree.has(i, j))
{
score += j - i;
longest = j - i;
}
其中stree.has()
现在接受两个整数(子字符串的位置和长度),并执行以下操作:
ReadOnlySpan<char> substr = s1.AsSpan(i, j);
ReadOnlySpan substr=s1.AsSpan(i,j);
请注意,substr
变量实际上是对初始s1
数组的字符子集的引用,而不是副本!(可以通过此函数访问s1
变量)
请注意,在撰写本文时,我使用的是C#7.2和.NET Framework 4.6.1,这意味着要获得Span功能,我必须进入Project>Manage NuGet Packages,勾选“Include prerelease”复选框,浏览System.Memory并安装它
重新运行初始测试(在长度为100万字符的字符串上,即1MB)速度从2分钟以上提高(2分钟后我放弃等待)大约86毫秒!!
String
在Java中也是不可变的。你尝试过StringBuilder
吗?我猜你有内存问题。你看过了吗?Java中的八个内核中有七个可能用于垃圾收集你的子字符串:)哈哈,可能就是它了……)。你知道如何在C#中不需要一直复制就可以得到我的子字符串吗?我不能只使用C++中的指针算法,直到C语言得到<代码> Strue/Cube >,因为其他评论者指出,在<代码> Stre等方法中,只使用<代码>(String,StistCurnand,EndiDeX) >。在方法内部使用字符串索引器(s[i]
),它返回char
w/o分配。可以作为创建Span的一部分进行切片:s1。AsSpan(i,j)
,应该快一点?可能是因为我不知道Span是如何实现的。它看起来并不是更快,但直觉上认为它是。。至少我这么认为。我将编辑我的帖子并使用您的建议,因为如果您感兴趣,这可能是使用span@benadam有关span的更多信息的预期方式。(仅为完整起见)
ReadOnlySpan<char> substr = s1.AsSpan(i, j);