介绍
我有一个很喜欢的算法,我很早以前就做了,我一直在用新的编程语言,平台等来编写和重新编写某种基准。尽管我的主要编程语言是C#,但是我只是从字面上复制粘贴了代码,并稍稍更改了语法,使用Java进行了构建,并发现其运行速度快了1000倍。
编码
有很多代码,但是我仅要介绍这个片段,这似乎是主要问题:
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))
...
数据
重要的是要指出,在此特定测试中,字符串s1的长度为1百万个字符(1MB)。
测量
我之所以在Visual Studio中介绍代码执行情况,是因为我认为构造树或遍历树的方法不是最佳方法。检查结果后,该行似乎占据string _s1 = s1.Substring(i, j);
了执行时间的90%以上!
其他观察
我注意到的另一个区别是,尽管我的代码是单线程的Java还是设法使用所有8个内核(100%CPU使用率)执行它,而即使使用Parallel.For()和多线程技术,我的C#代码也设法使用了35-最多40%。由于该算法随内核数(和频率)线性扩展,因此我对此进行了补偿,而Java中的代码段执行速度却快了100-1000倍。
推理
我认为发生这种情况的原因与以下事实有关:C#中的字符串是不可变的,因此String.Substring()必须创建一个副本,并且由于它位于具有多次迭代的嵌套for循环中,所以我想很多复制和正在进行垃圾收集,但是,我不知道Substring是如何在Java中实现的。
题
目前我有什么选择?没有办法解决子字符串的数量和长度(已经对它进行了最大程度的优化)。是否有我不知道的方法(或数据结构)可以为我解决此问题?
要求的最低限度实施(来自评论)
我省略了后缀树的实现,后缀树的构造为O(n),遍历为O(log(n))
public static double compute(string s1, string s2)
{
double score = 0.00;
suffixTree stree = new suffixTree(s2);
for (int i = 0; i <= s1.Length; i++)
{
int longest = 0;
for (int j = i + 1; j <= s1.Length - i; j++)
{
string _s1 = s1.Substring(i, j);
if (stree.has(_s1))
{
score += j - i;
longest = j - i;
}
else break;
};
i += longest;
};
return score;
}
探查器的屏幕截图
请注意,这是使用大小为300.000个字符的字符串s1进行测试的。出于某种原因,一百万个字符从未在C#中完成,而在Java中仅花费了0.75秒。峰值约为400 MB,但是考虑到巨大的后缀树,这似乎是正常的。也没有发现任何奇怪的垃圾收集模式。
String
Java中的变量也是不可变的。您尝试过StringBuilder
吗?