2016-07-28 13 views
1

Мы находимся в обработке интеграции связи с внешним API. До сих пор это была небольшая головная боль из-за несогласованного наименования, плохой документации и ненадежных сообщений об ошибках/ошибках.Почему Regex и StringBuilder замедляются при удалении пробела?

Одна из вещей, с которыми мы имеем дело, состоит в том, что определенные Запросы, которые мы отправляем им, имеют ограничения на длину строк. Ничего революционного, но любой запрос, который содержит любую строку, которая превышает требования к длине, просто отрицается и терпит неудачу.

Наше решение было создать метод расширения для строки, которая только принимает в максимальной длины и возвращает подстроку той длины, начиная с индекса 0.

Я младший DEV и это моя первая работа , поэтому я знаю, что мои решения, возможно, не самые элегантные или эффективные. В любом случае, я поднял вопрос, что с нашим текущим расширением мы могли бы завершить удаление соответствующей информации, включая потенциально бесполезное пустое пространство, так как мы не обрезали или ничего не делали, чтобы проверять наличие двойных пробелов и т. Д. Мое руководство помогло мне сделать перегрузка расширения, которое позволит вам также исключить пустое пространство.

У меня есть 3 решения, которые полностью удаляют любые двойные пробелы. Я знаю, что метод Regex является единственным, который действительно удаляет все пустое пространство, где, как и другие два, удаляет любое появление двух пространств назад. Однако этот сайт будет использоваться исключительно в США, поэтому я не уверен, что дополнительное время для Regex оправдано.

Мой главный интерес в публикации этого вопроса Я задаюсь вопросом, может ли кто-нибудь объяснить, почему мой метод с использованием StringBuilder настолько неэффективен по сравнению с двумя другими, он даже медленнее, чем Regex, я ожидал, что он будет самым быстрым из трех. Любое понимание здесь ценится, а также намек на то, что может быть лучшим способом, чем любой из них, который я придумал.

Вот мои три расширения:

public static string SafeSubstringSomehowTheQuickest(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 

     stringToShorten = stringToShorten.Trim(); 
     int stringOriginalLength = stringToShorten.Length; 
     int extraWhitespaceCount = 0; 
     for (int i = 0; i < stringOriginalLength - extraWhitespaceCount; i++) 
     { 
      int stringLengthBeforeReplace = stringToShorten.Length; 
      stringToShorten = stringToShorten.Replace(" ", " "); 
      if(stringLengthBeforeReplace < stringToShorten.Length) { extraWhitespaceCount += stringToShorten.Length - stringLengthBeforeReplace; } 
     } 

     return stringToShorten.Length > maxLength ? stringToShorten.Substring(0, maxLength) : stringToShorten; 
    } 

    public static string SafeSubstringWithRegex(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 
     stringToShorten = System.Text.RegularExpressions.Regex.Replace(stringToShorten, @"\s{2,}", " ").Trim(); 

     return stringToShorten.Length > maxLength ? stringToShorten.Substring(0, maxLength) : stringToShorten; 
    } 

    public static string SafeSubstringFromBuilder(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 

     StringBuilder bob = new StringBuilder(); 
     bool lastCharWasWhitespace = false; 

     foreach (char c in stringToShorten) 
     { 
      if (c == ' ' && !lastCharWasWhitespace) { bob.Append(c); } 
      lastCharWasWhitespace = c == ' '; 
      if (!lastCharWasWhitespace) { bob.Append(c); } 
     } 
     stringToShorten = bob.ToString().Trim(); 

     return stringToShorten.Length < maxLength ? stringToShorten : stringToShorten.Substring(0, maxLength); 
    } 

Вот мой быстрый тест, я использую, чтобы сравнить время, необходимое для каждого расширения для запуска:

static void Main(string[] args) 
    { 
     var stopwatch = new System.Diagnostics.Stopwatch(); 

     string test = 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      "; 

     int stringStartingLength = test.Length; 
     int stringMaxLength = 30; 

     stopwatch.Start(); 
     string somehowTheQuickestResult = test.SafeSubstringSomehowTheQuickest(stringMaxLength); 
     stopwatch.Stop(); 
     var somehowTheQuickestResultTicks = stopwatch.ElapsedTicks; 

     stopwatch.Start(); 
     string regexResult = test.SafeSubstringWithRegex(stringMaxLength); 
     stopwatch.Stop(); 
     var regexResultTicks = stopwatch.ElapsedTicks; 

     stopwatch.Start(); 
     string stringBuilderResult = test.SafeSubstringFromBuilder(stringMaxLength); 
     stopwatch.Stop(); 
     var stringBuilderResultTicks = stopwatch.ElapsedTicks; 
    } 

Наконец эти результаты , тики различаются по каждому прогону, но разница между тремя методами довольно непротиворечива:

Все три возвращают одну и ту же строку из: "Foo бар Foobar е оо бар Foobar"

somehowTheQuickestResult (метод 1): 12840 клещи

regexResult (метод 2): 14889 клещей

stringBuilderResult (метод 3): 15798 тиков

+0

Возможно, было бы еще быстрее, если бы вы работали с массивом символов и перемещали небелые пробелы. Для больших сравнений строк вы, вероятно, получите совсем другие результаты с вашими текущими методами. – BugFinder

+0

В качестве примечания стороны методы возвратят строку из множества пространств без изменений, хотя она будет слишком длинной. – GSerg

+1

Для регулярного выражения вы, вероятно, хотите исключить время компиляции, создав явный экземпляр «static readonly» из него с обязательными [flags] (https://msdn.microsoft.com/en-us/library/h5845fdz (v = vs 0,110) .aspx). Для stringbuilder вы, вероятно, хотите передать 'maxLength' как [' capacity'] (https://msdn.microsoft.com/en-us/library/h1h0a5sy (v = vs.110) .aspx). – GSerg

ответ

5

Вы делаете свой бенчмаркинг немного неправильно.

Во-первых, вам нужно «разогреться» и позволить JIT выполнять свою работу. В принципе, просто вызовите три метода и отбросьте результаты.

Далее, одна попытка не является представительной. Попробуйте среднее (или среднее время) более 100 или более итераций.

В-третьих, неправильное использование Stopwatch. Start() после Stop() просмотр промежуток измерения. Restart() - это путь. С этим, мои тесты показывают следующие результаты:

9569 
314 
58 

Итак, StringBuilder путь является на самом деле является самым быстрым.

+0

Отличный улов! Извините, это первый раз, когда я использовал секундомер. Приступая к работе над повторением этого и получением лучшего среднего за несколько тысяч циклов. – WRP