Почему мой код ColdFusion в 100 раз быстрее при обработке строк?

Итак, как гласит мое название, у меня есть программа ColdFusion, которая использовала 10 минут для запуска на нашем сервере, но теперь работает в < 15 секунд. Смутившись, почему такая простая программа занимала 10 минут, и мой босс осмотрел ее, чтобы выяснить раздел culprit кода, вызывающий замедление. Мы закончили тем, что переходим от 10 минут к 5-10 секунд для запуска.

Теперь мы не знаем, почему исправление является исправлением, поэтому мы хотели узнать, может ли кто-нибудь объяснить нам, почему/как это работает, чтобы мы могли понять исправление, чтобы мы могли использовать ускорение в рамках других программ.

Начало этой программы - это запрос, который захватывает ~ 4800 записей (ничего не возмутительно). Затем мы перебираем эти записи, которые мы выяснили, это медленный раздел. Вот пример того, что у нас было, и что мы сделали, чтобы исправить это. TextString устанавливается вверху запроса рядом с заголовками полей, которые мы возвращаем.

Старый код:

<cfloop>
       <CFSET TextString = TextString & DriverID & TabChar & LocalSSN & TabChar & FirstName & CarriageReturn & LineFeed>        
</cfloop>

Фиксированный код:

<cfloop>
    <CFSET LocalTextString = "">
    <CFSET LocalTextString = LocalTextString & DriverID & TabChar & LocalSSN & TabChar & FirstName & CarriageReturn & LineFeed>
    <CFSET TextString = TextString & LocalTextString>
</cfloop>

Ответ 1

Ваш код почти наверняка быстрее из-за того, что строки связаны в CF. Хотя я не знаю точную внутренность CF, я подозреваю, что Strings является неизменным. Это означает, что каждый раз, когда вы присоединяете дополнительную переменную к String с помощью &, она создаст новую String, содержащую как старую строку, так и новую строку в конце. Для этого ему придется выделять память, а по мере роста строки - все больше и больше памяти.

В строку добавляется 8 переменных, каждая из которых проходит цикл, включая предыдущую версию String, поэтому вы выделяете строки ~ 4800 * 8 во время цикла.

Предполагая, что каждая строка имеет длину 35 символов, конечный размер строки будет 168k. Это означает, что его средний размер во время пробега составляет половину этого: 84k. Теперь, учитывая, что вы назначаете строку 4800 * 8 раз, вы используете целых 3 GIG (4800 * 8 * 8400) памяти для создания 168k вывода. Это будет означать, что Java должна делать целую кучу коллекции мусора и дополнительную работу для обслуживания вашего кода.

Ваш обновленный код работает над LocalTextString 7 раз из 8, что будет крошечным по сравнению, поэтому вы получите значительное улучшение скорости.

Попробуйте эту версию:

<cfset buffer=ArrayNew()>
<cfset crlf=CarriageReturn & LineFeed />
<cfloop>
  <cfset ArrayAppend(buffer,DriverID)>
  <cfset ArrayAppend(buffer,TabChar)>
  <cfset ArrayAppend(buffer,LocalSSN)>
  <cfset ArrayAppend(buffer,TabChar)> 
  <cfset ArrayAppend(buffer,FirstName)>
  <cfset ArrayAppend(buffer,crlf)> 
</cfloop>
<cfoutput arrayToList(buffer, "")/>

Он создает массив строк, а затем превращает их в одну строку в конце. Вы также можете посмотреть StringBuffer с Java, что делает то же самое. Когда я в последний раз смотрел на него, метод Array/List выше был самым быстрым, но это было несколько версий CF назад.

Update

Я не был доволен количеством догадок в своем ответе, поэтому у меня была популярность при воспроизведении проблемы. Я использовал 5-летний Mac с CF10 для тестирования.

  • Оригинальный код: 8100мс
  • Улучшенный исходный код: 750 мс
  • Метод ArrayAppend: ~ 15 мс

Если я подключу VisualVM к процессу ColdFusion, пока он запускает тесты, я вижу, что исходный код пережевывается через несколько сотен мегабайт памяти во время прогона. Не так плохо, как предложили мои первоначальные математические соображения; Я подозреваю, что это только создание большой строки один раз за один проход по циклу, не один раз за отдельную конкатенацию. Он запускает 2-3 мелкие коллекции мусора во время каждого прогона, хотя это и приводит к снижению производительности. Он также использует 100% процессор на ядре, на котором он работает.

Код ArrayAppend не запускает никаких GC в среднем, и вы вряд ли сможете увидеть используемую память.

Я не понимаю, почему исходный код занял довольно много времени, но другие факторы - это скорость процессора, свободная память, доступ к базе данных (я использовал MySQL5), версию CF и т.д.