Строковые сравнительные тесты в C# - осуществляющий рефакторинг для скорости/Пригодности для обслуживания

Я думаю, вы только что пропустили скобку

CREATE TABLE schedules(
    shID INT,
    openTime TIME,
    closeTime TIME,
    FOREIGN KEY (shID) REFERENCES Shops (ShopID) ON DELETE CASCADE
);
6
задан 10 revs, 2 users 100% 31 October 2009 в 16:51
поделиться

10 ответов

1) Используйте StringBuilder, предпочтительно установите с разумной начальной способностью (например, длина строки * 5/4, для предоставления одного дополнительного пространства на четыре символа).

2) Попытайтесь использовать цикл foreach вместо для цикла - это может быть более просто

3) Вы не должны преобразовывать строку в массив символов сначала - foreach уже, будет работать по строке или использовать индексатор.

4) Не делайте дополнительных преобразований строк везде - вызов Преобразовывает. ToString (символ) и затем добавление, что строка бессмысленна; нет никакой потребности в односимвольной строке

5) Для второй опции просто создайте regex однажды вне метода. Попробуйте его RegexOptions. Скомпилированный также.

Править: Хорошо, полные результаты сравнительного теста. Я попробовал еще несколько вещей и также выполнил код со скорее большим количеством повторений для получения более точного результата. Это только работает на ПК Eee, поэтому несомненно он будет работать быстрее на "реальных" ПК, но я подозреваю, что широкие результаты являются соответствующими. Сначала код:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

class Benchmark
{
    const string TestData = "ThisIsAUpperCaseString";
    const string ValidResult = "This Is A Upper Case String";
    const int Iterations = 1000000;

    static void Main(string[] args)
    {
        Test(BenchmarkOverhead);
        Test(MakeNiceString);
        Test(ImprovedMakeNiceString);
        Test(RefactoredMakeNiceString);
        Test(MakeNiceStringWithStringIndexer);
        Test(MakeNiceStringWithForeach);
        Test(MakeNiceStringWithForeachAndLinqSkip);
        Test(MakeNiceStringWithForeachAndCustomSkip);
        Test(SplitCamelCase);
        Test(SplitCamelCaseCachedRegex);
        Test(SplitCamelCaseCompiledRegex);        
    }

    static void Test(Func<string,string> function)
    {
        Console.Write("{0}... ", function.Method.Name);
        Stopwatch sw = Stopwatch.StartNew();
        for (int i=0; i < Iterations; i++)
        {
            string result = function(TestData);
            if (result.Length != ValidResult.Length)
            {
                throw new Exception("Bad result: " + result);
            }
        }
        sw.Stop();
        Console.WriteLine(" {0}ms", sw.ElapsedMilliseconds);
        GC.Collect();
    }

    private static string BenchmarkOverhead(string str)
    {
        return ValidResult;
    }

    private static string MakeNiceString(string str)
    {
        char[] ca = str.ToCharArray();
        string result = null;
        int i = 0;
        result += System.Convert.ToString(ca[0]);
        for (i = 1; i <= ca.Length - 1; i++)
        {
            if (!(char.IsLower(ca[i])))
            {
                result += " ";
            }
            result += System.Convert.ToString(ca[i]);
        }
        return result;
    }

    private static string ImprovedMakeNiceString(string str)
    { //Removed Convert.ToString()
        char[] ca = str.ToCharArray();
        string result = null;
        int i = 0;
        result += ca[0];
        for (i = 1; i <= ca.Length - 1; i++)
        {
            if (!(char.IsLower(ca[i])))
            {
                result += " ";
            }
            result += ca[i];
        }
        return result;
    }

    private static string RefactoredMakeNiceString(string str)
    {
        char[] ca = str.ToCharArray();
        StringBuilder sb = new StringBuilder((str.Length * 5 / 4));
        int i = 0;
        sb.Append(ca[0]);
        for (i = 1; i <= ca.Length - 1; i++)
        {
            if (!(char.IsLower(ca[i])))
            {
                sb.Append(" ");
            }
            sb.Append(ca[i]);
        }
        return sb.ToString();
    }

    private static string MakeNiceStringWithStringIndexer(string str)
    {
        StringBuilder sb = new StringBuilder((str.Length * 5 / 4));
        sb.Append(str[0]);
        for (int i = 1; i < str.Length; i++)
        {
            char c = str[i];
            if (!(char.IsLower(c)))
            {
                sb.Append(" ");
            }
            sb.Append(c);
        }
        return sb.ToString();
    }

    private static string MakeNiceStringWithForeach(string str)
    {
        StringBuilder sb = new StringBuilder(str.Length * 5 / 4);
        bool first = true;      
        foreach (char c in str)
        {
            if (!first && char.IsUpper(c))
            {
                sb.Append(" ");
            }
            sb.Append(c);
            first = false;
        }
        return sb.ToString();
    }

    private static string MakeNiceStringWithForeachAndLinqSkip(string str)
    {
        StringBuilder sb = new StringBuilder(str.Length * 5 / 4);
        sb.Append(str[0]);
        foreach (char c in str.Skip(1))
        {
            if (char.IsUpper(c))
            {
                sb.Append(" ");
            }
            sb.Append(c);
        }
        return sb.ToString();
    }

    private static string MakeNiceStringWithForeachAndCustomSkip(string str)
    {
        StringBuilder sb = new StringBuilder(str.Length * 5 / 4);
        sb.Append(str[0]);
        foreach (char c in new SkipEnumerable<char>(str, 1))
        {
            if (char.IsUpper(c))
            {
                sb.Append(" ");
            }
            sb.Append(c);
        }
        return sb.ToString();
    }

    private static string SplitCamelCase(string str)
    {
        string[] temp = Regex.Split(str, @"(?<!^)(?=[A-Z])");
        string result = String.Join(" ", temp);
        return result;
    }

    private static readonly Regex CachedRegex = new Regex("(?<!^)(?=[A-Z])");    
    private static string SplitCamelCaseCachedRegex(string str)
    {
        string[] temp = CachedRegex.Split(str);
        string result = String.Join(" ", temp);
        return result;
    }

    private static readonly Regex CompiledRegex =
        new Regex("(?<!^)(?=[A-Z])", RegexOptions.Compiled);    
    private static string SplitCamelCaseCompiledRegex(string str)
    {
        string[] temp = CompiledRegex.Split(str);
        string result = String.Join(" ", temp);
        return result;
    }

    private class SkipEnumerable<T> : IEnumerable<T>
    {
        private readonly IEnumerable<T> original;
        private readonly int skip;

        public SkipEnumerable(IEnumerable<T> original, int skip)
        {
            this.original = original;
            this.skip = skip;
        }

        public IEnumerator<T> GetEnumerator()
        {
            IEnumerator<T> ret = original.GetEnumerator();
            for (int i=0; i < skip; i++)
            {
                ret.MoveNext();
            }
            return ret;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

Теперь результаты:

BenchmarkOverhead...  22ms
MakeNiceString...  10062ms
ImprovedMakeNiceString...  12367ms
RefactoredMakeNiceString...  3489ms
MakeNiceStringWithStringIndexer...  3115ms
MakeNiceStringWithForeach...  3292ms
MakeNiceStringWithForeachAndLinqSkip...  5702ms
MakeNiceStringWithForeachAndCustomSkip...  4490ms
SplitCamelCase...  68267ms
SplitCamelCaseCachedRegex...  52529ms
SplitCamelCaseCompiledRegex...  26806ms

Как Вы видите, строковая версия индексатора является победителем - это - также довольно простой код.

Надеюсь, это поможет... и не забывайте, там обязаны быть другими опциями, о которых я не думал!

17
ответ дан 8 December 2019 в 03:28
поделиться

Вы могли бы хотеть попытаться инстанцировать a Regex возразите как участник класса и использование RegexOptions.Compiled опция, когда Вы создаете его.

В настоящее время Вы используете помехи Split член Regex, и это не кэширует регулярное выражение. Используя инстанцированный членский объект вместо статического метода должен улучшить Вашу производительность еще больше (за длительный период).

3
ответ дан 8 December 2019 в 03:28
поделиться

Попытайтесь осуществить рефакторинг так, чтобы регулярное выражение, которое Вы используете для разделения строки во втором методе, было сохранено в статическом методе и было создано с помощью RegexOptions. Опция Compiled. Больше информации об этом здесь: http://msdn.microsoft.com/en-us/library/8zbs0h2f.aspx.

Я не протестировал теорию, но я предположу, что, имея необходимость воссоздать regex каждый раз был бы трудоемким.

2
ответ дан 8 December 2019 в 03:28
поделиться

Я знаю то, что они говорят о RegEx, используйте его для решения проблемы, и теперь у Вас есть две проблемы, но я остаюсь поклонником, Только для усмешек, вот версия RegEx. RegEx, с небольшим инициированием легко считать, меньше кода, и позволяет Вам легко привязаться в дополнительных разделителях (как я сделал с запятой).

   s1 = MakeNiceString( "LookOut,Momma,There'sAWhiteBoatComingUpTheRiver" ) );

   private string MakeNiceString( string input )
   {
       StringBuilder sb = new StringBuilder( input );
       int Incrementer = 0;
       MatchCollection mc;
       const string SPACE = " ";

       mc = Regex.Matches( input, "[A-Z|,]" );

       foreach ( Match m in mc )
       {
           if ( m.Index > 0 )
           {
               sb.Insert( m.Index + Incrementer, SPACE );
               Incrementer++;
           }
       }

       return sb.ToString().TrimEnd();
   }
2
ответ дан 8 December 2019 в 03:28
поделиться

Мой первый рефакторинг должен был бы изменить имя метода к чему-то более описательному. MakeNiceString imo не является именем, которое указало бы мне, что делает этот метод.

Как насчет PascalCaseToSentence? Не любя то имя, но это лучше, чем MakeNiceString.

2
ответ дан 8 December 2019 в 03:28
поделиться

Используйте a StringBuilder вместо конкатенации. Каждая конкатенация создает новый строковый экземпляр и выбрасывает старое.

2
ответ дан 8 December 2019 в 03:28
поделиться

Это в ответ на комментарий ctacke к ответу Jon Skeet (Это не, жаждут комментария),

Я всегда думал, что foreach был довольно известен, чтобы быть медленнее, чем для цикла, так как он должен использовать итератор.

На самом деле, нет, в этом случае foreach был бы быстрее. Индексный доступ является проверенными границами (т.е. я - проверка, чтобы быть в диапазоне три раза в цикле: однажды в для () и однажды каждый для двух приблизительно [я] s), который делает для цикла медленнее, чем foreach.

Если компилятор C# обнаруживает определенный синтаксис:

 for(i = 0; i < ca.Length; i++)

затем это выполнит специальную оптимизацию, удаляя внутренние проверки принадлежности к диапазону, делая для () цикл быстрее. Однако, так как здесь мы должны рассматривать CA [0] как особый случай (для предотвращения ведущего пространства на выводе), мы не можем инициировать ту оптимизацию.

2
ответ дан 8 December 2019 в 03:28
поделиться

Версии Regex Вашего решения не эквивалентны в результатах исходному коду. Возможно, больший контекст кода избегает областей, где они отличаются. Исходный код добавит пространство для чего-либо, что не является символом нижнего регистра. Например, "This\tIsATest" стал бы "This \t Is A Test" в оригинале, но "This\t Is A Test" с версиями Regex.

(?<!^)(?=[^a-z])

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

(?<!^)(?=\P{Ll})
0
ответ дан 8 December 2019 в 03:28
поделиться

В C# (.NET, действительно) при добавлении строки, существует несколько вещей, продолжающихся в фоновом режиме. Теперь, я забываю специфические особенности, но это - что-то как:

представьте в виде строки = B + C;

+ = D; + = E;

//... повторение промывки для 10 000 повторений

Для каждой строки выше, будет.NET: 1) Выделите некоторую новую память для A. 2) Скопируйте строку B в новую память. 3) Расширьте память для содержания C. 4) Добавьте строку C к A.

Чем дольше строка A, тем больше времени это занимает. Добавьте к этому, чем больше раз Вы делаете это, тем дольше A добирается, экспоненциально дольше это берет.

Однако с StringBuilder Вы не выделяете новую память, таким образом Вы пропускаете ту проблему.

Если Вы говорите:

StringBuilder A = new StringBuilder();
A.Append(B);
A.Append(C);
// .. rinse/repeat for 10,000 times...

string sA = A.ToString();

StringBuilder (Редактирование: фиксированное описание), имеет строку в памяти. Это не должно перераспределять всю строку для каждой добавленной подстроки. При издании ToString (), строка уже добавляется в соответствующем формате.

Один выстрел вместо цикла, который занимает все больше более длительный период.

Я надеюсь, что это помогает ответить, ПОЧЕМУ потребовалось настолько меньше времени.

-2
ответ дан 8 December 2019 в 03:28
поделиться

Вот немного более оптимальная версия. Я взял предложения из предыдущих постеров, но также добавил их к конструктору строк по блокам. Это может позволить построителю строк копировать 4 байта за раз, в зависимости от размера слова. Я также удалил выделение строки и просто заменил его на str.length.

    static string RefactoredMakeNiceString2(string str)
    {
        char[] ca = str.ToCharArray();
        StringBuilder sb = new StringBuilder(str.Length);
        int start = 0;
        for (int i = 0; i < ca.Length; i++)
        {
            if (char.IsUpper(ca[i]) && i != 0)
            {
                sb.Append(ca, start, i - start);
                sb.Append(' ');
                start = i;
            }
        }
        sb.Append(ca, start, ca.Length - start);
        return sb.ToString();
    }
1
ответ дан 8 December 2019 в 03:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: