Najszybszy sposób na usunięcie pierwszego znaku z ciągu

207

Powiedzmy, że mamy następujący ciąg

string data= "/temp string";

Jeśli chcemy usunąć pierwszą postać, /możemy to zrobić na wiele sposobów, takich jak:

data.Remove(0,1);
data.TrimStart('/');
data.Substring(1);

Ale tak naprawdę nie wiem, który ma najlepszy algorytm i robi to szybciej.
Czy jest taki, który jest najlepszy, czy wszystkie są takie same?

Amr Badawy
źródło
Czy mimo to chcesz usunąć pierwszą postać, czy też musisz sprawdzić, czy ta postać rzeczywiście jest /?
SRKX
5
TrimStartnie usunie pierwszego znaku, usunie nznaki od początku. Substringjest najszybszy.
Jaroslav Jandek
Muszę tylko usunąć pierwszą postać
Amr Badawy,
6
Jeśli usuwasz jakikolwiek pierwszy znak, TrimStart()jest całkowicie wykluczone.
BoltClock
@BoltClock: tak, właśnie to powiedziałem (wpisałem).
Jaroslav Jandek

Odpowiedzi:

147

Druga opcja tak naprawdę nie jest taka sama jak inne - jeśli ciąg ma postać „/// foo”, zmieni się w „foo” zamiast „// foo”.

Pierwsza opcja wymaga nieco więcej pracy, aby zrozumieć niż trzecia - uznałbym tę Substringopcję za najbardziej powszechną i czytelną.

(Oczywiście każde z nich jako osobna instrukcja nie przyniesie niczego pożytecznego - musisz przypisać wynik do zmiennej, być może datasamej).

Nie wziąłbym tutaj pod uwagę wydajności, chyba że stałoby się to dla ciebie problemem - w takim przypadku jedynym sposobem, aby wiedzieć, było posiadanie przypadków testowych, a następnie łatwo jest uruchomić te przypadki testowe dla każdej opcji i porównaj wyniki. Spodziewałbym się, że będę Substringprawdopodobnie najszybszy tutaj, po prostu dlatego, że Substringzawsze tworzy ciąg z pojedynczego fragmentu oryginalnego wejścia, podczas gdy Removemusi przynajmniej potencjalnie skleić fragment początkowy i końcowy.

Jon Skeet
źródło
36
Sprawdzam teraz, dzwoniąc do każdego o 90000000 i idę następujący wynik: Usuń: 06.63 - TrimStart: 04.71 - subString: 03.09 więc z podciągu wyników jest najlepszy
Amr Badawy
5
Pamiętaj tylko, że podczas testowania wydajności w ten sposób wpływa na ciebie buforowanie procesora, więc musisz to zrobić na losowych ciągach, które wcześniej wypełniłeś tablicę (listę) i losowo wybierasz element tej tablicy ( lista).
ajeh
12

Wiem, że to kraina hiperoptymalizacyjna, ale wydawało się, że to dobra wymówka, by kopać koła BenchmarkDotNet. Wynik tego testu (nawet w .NET Core) jest Substringnawet nieco szybszy niż Removew tym przykładowym teście: 19,37ns vs 22,52ns dla Remove. Więc jakieś ~ 16% szybciej.

using System;
using BenchmarkDotNet.Attributes;

namespace BenchmarkFun
{
    public class StringSubstringVsRemove
    {
        public readonly string SampleString = " My name is Daffy Duck.";

        [Benchmark]
        public string StringSubstring() => SampleString.Substring(1);

        [Benchmark]
        public string StringRemove() => SampleString.Remove(0, 1);

        public void AssertTestIsValid()
        {
            string subsRes = StringSubstring();
            string remvRes = StringRemove();

            if (subsRes == null
                || subsRes.Length != SampleString.Length - 1
                || subsRes != remvRes) {
                throw new Exception("INVALID TEST!");
            }
        }
    }

    class Program
    {
        static void Main()
        {
            // let's make sure test results are really equal / valid
            new StringSubstringVsRemove().AssertTestIsValid();

            var summary = BenchmarkRunner.Run<StringSubstringVsRemove>();
        }
    }
}

Wyniki:

BenchmarkDotNet=v0.11.4, OS=Windows 10.0.17763.253 (1809/October2018Update/Redstone5)
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100-preview-010184
  [Host]     : .NET Core 3.0.0-preview-27324-5 (CoreCLR 4.6.27322.0, CoreFX 4.7.19.7311), 64bit RyuJIT
  DefaultJob : .NET Core 3.0.0-preview-27324-5 (CoreCLR 4.6.27322.0, CoreFX 4.7.19.7311), 64bit RyuJIT

|          Method |     Mean |     Error |    StdDev |
|---------------- |---------:|----------:|----------:|
| StringSubstring | 19.37 ns | 0.3940 ns | 0.3493 ns |
|    StringRemove | 22.52 ns | 0.4062 ns | 0.3601 ns |
Nicholas Petersen
źródło
9

Sądzę, że Removei Substringremis na pierwszym miejscu, ponieważ obaj zasłaniają część łańcucha o stałej wielkości, podczas TrimStartgdy skanuje od lewej z testem na każdym znaku, a następnie musi wykonać dokładnie taką samą pracę jak pozostałe dwie metody. Poważnie, to jednak dzieli włosy.

Marcelo Cantos
źródło
1
W rzeczywistości Substringjest szybszy niż Remove, ponieważ Removepołączenia Substring.
Jaroslav Jandek
@Jaroslav: To nie jest prawda. Zarówno Substringi Removepolegać na prywatnej metodzie FillSubstring.
Marcelo Cantos,
Nie zweryfikowałem tego, ale brzmi to bardzo realistycznie:string Remove(this string source, int from, int to) { return source.SubString(0, from) + source.SubString(to); }
Dykam,
1
@Jaroslav: Patrzę na demontaż Reflectora dwóch metod w mscorlib.dll w dość konwencjonalnym środowisku programistycznym Windows. Obie wywołują, System.PInvoke.EE.AllocateStringaby przydzielić docelowy ciąg znaków, a następnie wywołują, FillSubstringaby skopiować znaki w poprzek. Czy patrzę na niewłaściwą rzecz?
Marcelo Cantos,
1
@Marcelo: W każdym razie, twój pierwszy komentarz początkowo mówił zupełnie inną rzecz. Prawdopodobnie powinienem był użyć lepszego sformułowania, ale to prawda ( Substring> Remove). Nie zamierzam komentować dalej, ponieważ dyskusja zajęła mi wystarczająco dużo czasu.
Jaroslav Jandek
6

Możesz to profilować, jeśli naprawdę ci na tym zależy. Napisz pętlę wielu iteracji i zobacz, co się stanie. Są jednak szanse, że nie jest to wąskim gardłem w twojej aplikacji, a TrimStart wydaje się najbardziej semantycznie poprawny. Staraj się pisać kod czytelnie przed optymalizacją.

Stefan Kendall
źródło
6
TrimStartjest najmniej poprawne, ponieważ "//temp string".TrimStart('/')będzie nie tylko usunąć pierwszy '/'.
Marcelo Cantos,
Funkcja jest wtedy źle nazwana. Nie jestem facetem z C #.
Stefan Kendall,
@StefanKendall: Spójrz na tagi
Vijay Singh Rana