Мне было поручено ускорить обработку текста / нормализацию нашего кода, и было несколько разделов, которые имели несколько настраиваемых списков «если увидишь, замени на это», и они были реализованы с большими стеками регулярные выражения. Это выглядело как хорошее место для начала - и это было.
Я реализовал простой Tr ie, загруженный с записями конфигурации, а затем имел функцию
Match (string raw, int idx = 0)
, которая просматривала необработанные вход, просматривая Tr ie на совпадения.
Мой первый черновик функции соответствия использовал a для l oop и индексатор (т.е.
TrieNode node = Root;
for (; idx < raw.Length; idx++)
{
TrieNode next;
if (node.TryGetValue(raw[idx], out next))
...
В нем и в нем был на несколько порядков быстрее, чем куча регулярных выражений.
Я хотел очистить и обобщить Tr ie, возможно, сделать его настраиваемым для символов или слов в качестве токенов, и после всех обобщений я заменил выше с
foreach (var c in idx > 0 ? raw.Skip(idx) : raw)
{
...
и был удивлен, увидев, сколько накладных расходов вызвало изменение в итерации. Я ожидал, что будут некоторые издержки, но метод foreach был примерно в 100 раз медленнее (4300 мс на цикл из 100 статей). против 40 мс с for для l oop) - только это изменение само по себе.
Я видел много статей из разных периодов времени, в которых говорилось: «Конечно, Linq и перечислители сосут!» для «alwa». Вы используете foreach, потому что производительность достаточно близка, а foreach круче ".
Ни одна из нижеперечисленных статей не была очень актуальной, поэтому я решил добавить эту заметку в bottle.
Я понимаю, что выделение перечислителя добавит немного накладных расходов, а Skip () никогда не будет столь же быстрым, как прыжок вперед с помощью индексатора, но это был довольно резкий контраст.
Я нашел дискуссия о том, должен ли String реализовывать IReadOnlyList или нет, и кажется, что он мог бы быть лучшим из обоих миров, но его не существует.
Кто-нибудь еще удивляется, что такое количество накладных расходов?