Введение
У меня есть этот любимый алгоритм, который я сделал довольно давно, и я всегда пишу и переписываю на новых языках программирования, платформах и т.д. В качестве своего рода эталона. Хотя мой основной язык программирования - С#, я просто буквально скопировал код и немного изменил синтаксис, построил его на 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 миллион символов (1 МБ).
измерения
Я профилировал выполнение моего кода в Visual Studio, потому что думал, что я строю свое дерево или способ, которым я пересекаю его, не является оптимальным. После изучения результатов появляется строка string _s1 = s1.Substring(i, j);
вмещает более 90% времени исполнения!
Дополнительные наблюдения
Еще одно отличие, которое я заметил, заключается в том, что хотя мой код является однопоточным Java, ему удается выполнить его с использованием всех 8 ядер (100% загрузка процессора), хотя даже с методами Parallel.For() и многопоточности мой код С# позволяет использовать 35- Максимум 40%. Поскольку алгоритм масштабируется линейно с количеством ядер (и частоты), я компенсировал это, и все же фрагмент в Java выполняет порядок на 100-1000 раз быстрее.
аргументация
Я предполагаю, что причина, по которой это происходит, связана с тем, что строки в С# неизменяемы, поэтому String.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;
}
Снимок экрана профайлера
Обратите внимание, что это было проверено со строкой s1 размером 300 000 символов. По какой-то причине 1 миллионный персонаж просто не заканчивается на С#, в то время как в Java он занимает всего 0,75 секунды. Потребляемая память и количество сборок мусора, похоже, не указывают на проблему с памятью. Пик составлял около 400 МБ, но, учитывая огромное дерево суффикса, это кажется нормальным. Никаких странных сборок мусора не обнаружено.