Bir Dizedeki ilk karakteri kaldırmanın en hızlı yolu


207

Diyelim ki şu dizeye sahibiz

string data= "/temp string";

İlk karakteri kaldırmak istiyorsak, /aşağıdaki gibi birçok yolla yapabiliriz:

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

Ama, gerçekten hangisinin en iyi algoritmaya sahip olduğunu bilmiyorum ve bunu daha hızlı yapıyorum ..
En iyisi veya hepsi aynı olan bir tane var mı?


Yine de ilk karakteri kaldırmak istiyor musunuz yoksa bu karakterin gerçekten bir olup olmadığını kontrol etmeniz mi gerekiyor /?
SRKX

5
TrimStartİlk karakteri kaldırmaz, nkarakterleri en baştan kaldırır . Substringen hızlısıdır.
Jaroslav Jandek

Ben sadece herhangi bir ilk karakteri kaldırmak gerekir
Amr Badawy

6
Herhangi bir ilk karakteri kaldırıyorsanız TrimStart(), tamamen söz konusu değildir.
BoltClock

@BoltClock: evet, ben de öyle söyledim (yazdım).
Jaroslav Jandek

Yanıtlar:


147

İkinci seçenek gerçekten diğerleriyle aynı değildir - dize "/// foo" ise, "// foo" yerine "foo" olur.

İlk seçenek, üçüncüsünden biraz daha fazla çalışmaya ihtiyaç duyar - Substringseçeneği en yaygın ve okunabilir olarak görürüm.

(Açıkçası her biri bireysel bir ifade olarak yararlı bir şey yapmaz - sonucu muhtemelen bir değişkene atamanız gerekir data.)

Aslında sizin için bir sorun haline gelmedikçe burada performansı dikkate almam - bu durumda bilmenin tek yolu test senaryolarına sahip olmak olacaktır ve daha sonra her test için bu test senaryolarını çalıştırmak kolaydır ve sonuçları karşılaştırır. SubstringMuhtemelen burada en hızlı olmasını beklerdim , çünkü Substringher zaman orijinal girdinin tek bir yığınından bir dize oluştururken, Removeen azından potansiyel olarak bir başlangıç ​​yığınını ve bir son yığınını birbirine yapıştırmak zorunda kalırız.


36
Şimdi 90000000 hakkında her birini arayarak kontrol ediyorum ve aşağıdaki sonucu gidiyorum: Kaldır: 06.63 - TrimStart: 04.71 - subString: 03.09 böylece sonuç alt dize en iyisi
Amr Badawy 11:10

5
Performansı bu şekilde test ederken, CPU önbelleklemesinden etkilendiğinizi unutmayın, bu yüzden bunu rastgele dizelerde yapmanız, bir diziyi (listeyi) önceden doldurmanız ve bu dizinin öğesini rastgele seçmeniz ( liste).
ajeh

12

Bunun hiper optimizasyon ülkesi olduğunu biliyorum, ama çarkları tekmelemek için iyi bir bahane gibi görünüyordu BenchmarkDotNet. Bu testin sonucu (.NET Core'da bile) şu örnek testte Substringolduğundan çok daha hızlıdır Remove: 19.37ns vs 22.52ns for Remove. Bazı ~% 16 daha hızlı.

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>();
        }
    }
}

Sonuçlar:

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 |

9

Ben her ikisi de dize sabit boyutlu bir kısmı yukarı slurp, çünkü her karakter için bir test ile sol tarama yapar ve daha sonra tam olarak aynı işi yapmak zorunda çünkü, tahmin Removeve Substringilk yer için kravat olurdu TrimStartdiğer iki yöntem. Cidden, bu, saçları bölüyor.


1
Aslında, Substringdaha hızlı olduğu Removeiçin, Removeçağrılar Substring.
Jaroslav Jandek

@Jaroslav: Bu doğru değil. Hem Substringve Remove, özel bir yönteme dayanır FillSubstring.
Marcelo Cantos

Doğrulamadım, ama çok mantıklı geliyor:string Remove(this string source, int from, int to) { return source.SubString(0, from) + source.SubString(to); }
Dykam

1
@Jaroslav: Oldukça geleneksel bir Windows geliştirme ortamında mscorlib.dll'de iki yöntemin Reflektör sökme bakıyorum. Her ikisi System.PInvoke.EE.AllocateStringde hedef dize nesnesini ayırmak için çağırır ve sonra FillSubstringkarakterleri kopyalamak için çağırır . Yanlış şeye mi bakıyorum?
Marcelo Cantos

1
@Marcelo: Her neyse, ilk yorumunuz aslında tamamen farklı bir şey söyledi. Muhtemelen daha iyi bir ifade kullanmalıydım, nokta olsa da geçerlidir ( Substring> Remove). Daha fazla yorum yapmayacağım çünkü tartışma yeterince zamanımı aldı.
Jaroslav Jandek

6

Gerçekten önemsediyseniz, profil oluşturabilirsiniz. Birçok yinelemeden oluşan bir döngü yazın ve ne olduğunu görün. Ancak, bunun uygulamanızdaki tıkanıklık olmaması ve TrimStart en semantik olarak doğru görünüyor. Optimizasyondan önce kodu okunabilir şekilde yazmaya çalışın.


6
TrimStartçünkü doğru azından "//temp string".TrimStart('/')olacak değil sadece ilk kaldırın '/'.
Marcelo Cantos

İşlevin adı kötü. Ben bir C # adamı değilim.
Stefan Kendall

@StefanKendall: Etikete bak
Vijay Singh Rana
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.