2013-06-10 7 views
3

Ниже быстрый код, который я мог бы создать для реверсирования строкиУскоренный способ обратной строки?

public static void ReverseFast(string x) 
{ 
    string text = x; 
    StringBuilder reverse = new StringBuilder(); 

    for (int i = text.Length - 1; i >= 0; i--) 
    { 
     reverse.Append(text[i]); 
    } 
     Console.WriteLine(reverse); 
} 

Я хочу, чтобы охватить все узкие места в этом уравнении, чтобы сделать это как можно быстрее. Единственное, что я могу найти до сих пор, это проверка границ массива, которую я лишь частично понимаю. Есть ли вообще отключить это, как я понимаю, если вы используете .Length, компилятор решает не проверять границы, но если вы уменьшаетесь, поскольку я нахожусь в цикле for, он все еще выполняет проверку границы? Может ли кто-то преобразовать это, чтобы использовать указатели для меня, что бы избежать пограничной проверки, я хотел бы проверить разницу в скорости для строк в диапазоне 100 кбайт + символов.

Основываясь на комментариях и сообщениях ниже, это то, к чему я пришел.

public static void ReverseFast(string x) 
{ 
    StringBuilder reverse = new StringBuilder(x.Length); 
    for (int i = x.Length - 1; i >= 0; i--) 
    { 
     reverse.Append(x[i]); 
    } 
    Console.WriteLine(reverse); 
} 

Это решение выше, чем предлагаемый дублирующий вопрос. Этот вопрос действительно обращается к развороту в диапазоне 5000 * 26 символов +. Я все еще хотел бы проверить это, используя указатели, чтобы действительно увидеть, нет ли узкого места, особенно с таким большим количеством символов.

+4

Это ** не ** будет вашим узким местом. Шутки в сторону. –

+0

Инициализировать StringBuilder с правильной длиной (text.Length) - это предотвратит изменение размера буфера. – JeffRSon

+0

Мой вопрос - это попрошайничество, чтобы исследовать альтернативы указателя в C# и критиковать мою текущую идею. – CodeCamper

ответ

2

Вот указатель решение на основе:

unsafe String Reverse(String s) 
     { 
      char[] sarr = new char[s.Length]; 
      int idx = s.Length; 
      fixed (char* c = s) 
      { 
       char* c1 = c; 
       while (idx != 0) 
       { 
        sarr[--idx] = *c1++; 
       } 
      } 

      return new String(sarr); 
     } 

Избавление от индекса массива (Сарры [- IDX]) следующий может быть быстрее:

unsafe String Reverse(String s) 
     { 
      char[] sarr = new char[s.Length]; 
      fixed (char* c = s) 
      fixed (char* d = sarr) 
      { 
       char* c1 = c; 
       char* d1 = d + s.Length; 
       while (d1 > d) 
       { 
        *--d1 = *c1++; 
       } 
      } 

      return new String(sarr); 
     } 
+0

Хороший подход; Я сравнивал это, и он все еще немного медленнее, чем плоский «Array.Reverse». Я не утверждаю, что знаю почему; но очень приятно –

+0

Редактировать: разъяснение для этого - похоже, зависит от длины строки; для небольших строк «Array.Reverse» кажется * чуть-чуть * быстрее; для больших строк код указателя кажется * немного * быстрее - недостаточно разницы, что я бы сказал, либо явный победитель –

+0

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

11
var arr = x.ToCharArray(); 
Array.Reverse(arr); 
return new string(arr); 

Обратите внимание, что это изменит любые символы модификатора юникода (акценты и т. Д.).

Benchmark:

Array.Reverse: 179ms 
StringBuilder: 475ms 

С:

static void Main() 
{ 
    string text = new string('x', 100000); 
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    var watch = Stopwatch.StartNew(); 
    const int LOOP = 1000; 
    for (int i = 0; i < LOOP; i++) 
    { 
     var arr = text.ToCharArray(); 
     Array.Reverse(arr); 
     string y = new string(arr); 
    } 
    watch.Stop(); 
    Console.WriteLine("Array.Reverse: {0}ms", watch.ElapsedMilliseconds); 

    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    watch = Stopwatch.StartNew(); 
    for (int i = 0; i < LOOP; i++) 
    { 
     var reverse = new StringBuilder(text.Length); 
     for (int j = text.Length - 1; j >= 0; j--) 
     { 
      reverse.Append(text[j]); 
     } 
     string y = reverse.ToString(); 
    } 
    watch.Stop(); 
    Console.WriteLine("StringBuilder: {0}ms", watch.ElapsedMilliseconds); 
} 

Если попытаться строку длиной 500 и петля 500000 раз:

Array.Reverse: 480ms 
StringBuilder: 1176ms 

Я также попытался добавить unsafe в том, т.е.

fixed (char* c = text) 
{ 
    for (int j = text.Length - 1; j >= 0; j--) 
    { 
     reverse.Append(c[j]); 
    } 
} 

это не имело значения.

И я добавил также в ответ Джеффриона; Я получаю:

Array.Reverse: 459ms 
StringBuilder: 1092ms 
Pointer: 513ms 

(для 500 й длиной 5000 итераций теста)

+0

это определенно быстрее? Любопытно, сколько ... –

+1

@ Хорошо, я не вижу способа, которым это может быть медленнее; «StringBuilder» должен выполнять * как минимум * столько же распределений, что и недостаток: возможно, слишком большой, и b: возможно, потребуется изменить размер; цикл - всего лишь базовый цикл. Я думаю, это зависит от того, счастлив ли 'StringBuilder' предоставить вам свою приватную' string' (если это точный размер). Это для OP, чтобы проверить, я слишком занят. –

+3

Извините, я подумал, что вопрос был «Более быстрый способ перевернуть строку?» Разве мы обычно не говорим людям о контроле? ;) –

2

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

public static void ReverseFast(string text) { 
    StringBuilder reverse = new StringBuilder(text.Length); 
    for (int i = text.Length - 1; i >= 0; i--) { 
    reverse.Append(text[i]); 
    } 
} 

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