Dizeleri birer birer birleştirmek verimsiz mi?


11

C'de programlama günlerimden hatırlıyorum, iki dize birleştirildiğinde, işletim sisteminin birleştirilmiş dize için bellek ayırması gerekiyor, sonra program tüm dize metnini bellekteki yeni alana kopyalayabilir, daha sonra eski bellek manuel olarak serbest bırakılmak. Dolayısıyla, bu, bir listeye katılma durumunda olduğu gibi birden çok kez yapılırsa, işletim sisteminin bir sonraki birleştirmeden sonra serbest bırakılması için sürekli olarak daha fazla bellek ayırması gerekir. C'de bunu yapmanın çok daha iyi bir yolu, birleştirilmiş dizelerin toplam boyutunu belirlemek ve birleştirilmiş tüm dizeler listesi için gerekli belleği ayırmak olacaktır.

Şimdi modern programlama dillerinde (örneğin C #), koleksiyonların içeriğini, koleksiyon boyunca yineleme yaparak ve tüm dizeleri tek tek bir dizgi referansına ekleyerek bir araya getirildiğini görüyorum. Modern bilgi işlem gücü ile bile bu verimsiz değil mi?


derleyici ve profiler için bırakın, onlar ilgilenecek, zaman dizeleri birleştirme için zamandan çok daha pahalı.
OZ_

7
Uygulamaya bağlıdır - özel dize kitaplığınızın belgelerini gerçekten kontrol etmelisiniz. O (1) zamanda referans ile bir araya getirilen dizeler uygulamak mümkündür. Her durumda, keyfi olarak uzun bir dize listesini birleştirmeniz gerekiyorsa, bu tür şeyler için tasarlanmış sınıfları veya işlevleri kullanmalısınız.
comingstorm

Dize birleştirme gibi şeylerin genellikle işletim sistemi tarafından değil, bir kütüphane işlevi tarafından işlendiğini unutmayın. İşletim sistemi bellek ayırmaya dahil olabilir, ancak muhtemelen dizeler gibi nispeten küçük nesneler için olmayabilir.
Caleb

@Caleb İşletim sistemi TÜM bellek tahsisinde yer almaktadır. Bu kurala uyulmaması bir tür bellek sızıntısıdır. Bunun istisnası, uygulamada sabit kodlu dizeleriniz olduğunda; bunlar oluşturulan derleme içinde ikili veri olarak yazılır. Ancak, bir dizeyi manipüle ettiğinizde (veya belki de atadığınızda) bellekte depolanması gerekir (yani, bellek tahsis edilmelidir).
JSideris

4
@Bizorke Tipik bir senaryoda, OS tarafından işleme önceden tahsis edilmiş bellekten çeşitli bellek parçalarını ayırmak için malloc () (OS'nin değil C standart kütüphanesinin bir parçası olan) gibi bir bellek ayırıcı kullanılır. İşlemin belleği azalmadıkça ve daha fazlasını istemeye gerek duymadıkça işletim sisteminin dahil olması gerekmez. Ayrıca, bir ayırma sayfa hatasına neden olursa daha düşük bir düzeyde yer alabilir. Bu nedenle evet, işletim sistemi nihayetinde belleği sağlar, ancak işlemin içinde dizelerin ve diğer nesnelerin parçalı olarak tahsis edilmesi gerekmez.
Caleb

Yanıtlar:


21

En azından aşina olduğum dillerde (C, Java, C #) neden verimsiz olduğunun açıklanması doğru olsa da, büyük miktarda dize birleştirme yapmanın evrensel olarak yaygın olduğunu kabul etmiyorum. Ben üzerinde çalışmak C # kodunda, bol kullanımı vardır StringBuilder, String.Formataşırı yeniden tahsisini önlemek için yöntemlerle en tasarruflu tüm hafıza vardır, vb.

Sorunuzun cevabına ulaşmak için başka bir soru sormalıyız: Eğer dizeleri birleştirmek gerçekten bir sorun değilse, sınıflar neden ister StringBuilderve StringBuffervar olur ? Bu tür sınıfların kullanımı neden yarı yeni başlayan programlama kitaplarına ve sınıflarına dahil edilir? Önceden olgunlaşmış optimizasyon önerileri neden bu kadar belirgindir?

Çoğu dizeyi birleştiren geliştirici, yanıtlarını tamamen deneyime dayandıracak olsaydı, çoğu bunun asla bir fark yaratmadığını ve "daha okunabilir" lehine bu tür araçların kullanılmasından vazgeçeceğini söylerdi for (int i=0; i<1000; i++) { strA += strB; }. Ama bunu hiç ölçmediler.

Bu sorunun gerçek cevabı , bir örnekte, 50.000 dizeyi (uygulamanıza bağlı olarak yaygın bir olay olabilir) birleştirirken, küçük olanlar bile 1000x performans isabetiyle sonuçlanan bu SO cevabında bulunabilir .

Performans tam anlamıyla bir şey ifade etmiyorsa, elbette bitiştirin. Ancak alternatifleri (StringBuilder) kullanmanın zor veya daha az okunabilir olduğunu ve bu nedenle "erken optimizasyon" savunmasını başlatmaması gereken makul bir programlama uygulaması olacağını kabul etmem .

GÜNCELLEME:

Bunun ne olduğunu düşünüyorum , platformunuzu tanıyor ve ne yazık ki evrensel olmayan en iyi uygulamalarını takip ediyor . İki farklı "modern dilden" iki örnek:

  1. Başka bir SO yanıtında , tam ters performans özelliklerinin (array.join vs + =) bazen JavaScript'te doğru olduğu bulunmuştur . Bazı tarayıcılarda, dize birleştirme otomatik olarak optimize edilmiş gibi görünür, diğer durumlarda ise bu şekilde optimize edilmez. Yani öneri (en azından SO sorusunda), sadece bitiştirmek ve endişelenmemek.
  2. Başka bir durumda, bir Java derleyici olabilir otomatik gibi StringBuilder olarak daha verimli bir yapı ile birleştirme değiştirin. Ancak, diğerlerinin de belirttiği gibi, bu belirsizdir, garanti edilmez ve StringBuilder kullanmak okunabilirliğe zarar vermez. Bu özel durumda, büyük koleksiyonlar için birleştirme kullanımına veya belirsiz bir Java derleyici davranışına güvenmeye karşı önerme eğilimindeyim. Benzer şekilde, .NET'te, sıralamada hiçbir optimizasyon yapılmaz .

Her platformun her nüansını hemen bilmemek tam bir kardinal günah değildir, ancak bunun gibi önemli platform sorunlarını görmezden gelmek neredeyse Java'dan C ++ 'a geçmek ve hafızaya alma konusuna önem vermemek gibi olacaktır.


-1: büyük BS içerir. strA + strBolan tam bir StringBuilder kullanarak aynı. 1x performans isabeti var. Veya 0x, nasıl ölçtüğünüze bağlı olarak. Daha fazla ayrıntı için codinghorror.com/blog/2009/01/…
amara

5
@sparkleshy: Tahminimce SO yanıtı Java kullanıyor ve bağlantılı makaleniz C # kullanıyor. "Uygulamaya bağlı" ve "ortamınız için ölçün" diyenlere katılıyorum.
Kai Chan

1
@KaiChan: dize birleştirme temelde java ve c # ile aynıdır
amara

3
@sparkleshy - Alınan nokta, ancak tam olarak iki dizeyi birleştirmek için StringBuilder, String.Join, vb. kullanmak nadiren bir öneridir. Ayrıca, OP'nin sorusu özellikle " koleksiyonların bir araya getirildiği koleksiyonların içeriği " ile ilgilidir, ki durum böyle değildir (StringBuilder vb. Çok uygulanabilir). Ne olursa olsun, örneğimi daha fazla olacak şekilde güncelleyeceğim.
Kevin McCormick

3
Bu sorunun amacı için dil umrumda değil. Bazı dillerde sahnelerin arkasında stringbuilder kullanımı, sorumu cevaplayan dizelerin bir listesini birleştirmenin neden yetersiz olabileceğini açıklıyor. Ancak bu cevap, bir listeye katılmanın potansiyel olarak tehlikeli olabileceğini ve alternatif olarak stringbuilder'ı önerebileceğini açıkladı. Muhtemel itibar kaybını veya yanlış yorumlamayı önlemek için derleyicinin perde arkasındaki stringbuilder kullanımını yanıtınıza eklemenizi tavsiye ederim.
JSideris

2

Kabaca tarif ettiğiniz nedenlerden ötürü verimli değildir. C # ve Java dizeleri değiştirilemez. Dizelerdeki işlemler, C'de olduğu gibi, orijinal olanı değiştirmek yerine ayrı bir örnek döndürür. Birden çok dizeyi birleştirirken, her adımda ayrı bir örnek oluşturulur. Kullanılmayan bu örnekleri tahsis etmek ve daha sonra çöpleri toplamak performansa neden olabilir. Çöp toplayıcı tarafından yalnızca bu sefer bellek yönetimi sizin için yapılır.

C # ve Java, özellikle bu tür görevler için değiştirilebilir bir dize olarak bir StringBuilder sınıfı sunar. C'deki bir eşdeğer, bir dizide birleştirmek yerine birleştirilmiş dizelerin bağlantılı bir listesini kullanır. C # ayrıca bir dize koleksiyonuna katılmak için dize üzerinde uygun bir birleştirme yöntemi sunar.


1

Açıkçası, CPU döngülerinin daha az verimli kullanılmasıdır, bu yüzden haklısınız. Ancak, geliştirici süresi, bakım maliyetleri vb.
"Program Optimizasyonunun İlk Kuralı: Yapmayın. İkinci Program Optimizasyon Kuralı (yalnızca uzmanlar için!): Henüz yapma."


3
çok etkili kurallar değil bence.
OZ_

@OZ_: Bu, Donald Knuth'un beğenileri tarafından yaygın olarak kullanılan bir teklif (Michael A. Jackson) ve diğeri ... O zaman genellikle "Daha fazla bilgi işlem günahı verimlilik adına kararlıdır ( kör aptallık da dahil olmak üzere başka herhangi bir nedenden daha başarılı olamaz. "
mattnz

2
Ben işaret olmalıdır Michael A. Jackson bir BRIT, böylece 's oldu Optimizasyon değil Optimizasyon . Bir noktada gerçekten wikipedia sayfasını düzeltmeliyim . * 8 ')
Mark Booth

Tamamen katılıyorum, yazım hatalarını düzeltmelisiniz. Ana dilim Queens İngilizce olmasına rağmen, web üzerinden ABD ile konuşmayı daha kolay buluyorum .......
mattnz

birileri kullanıcıları düşünmeyecek. Geliştiricinin oluşturmasını biraz daha hızlı hale getirebilirsiniz, ancak daha sonra müşterilerinizden her biri bunun için acı çeker. Kodunuzu onlar için yazın, sizin için değil.
gbjbaanb

1

Pratik bir test olmadan performans hakkında bir şey söylemek çok zor. Son zamanlarda JavaScript'te naif bir dize birleştirme genellikle önerilen "yapmak listesi ve katılmak" çözümü daha hızlı olduğunu bulmak için çok şaşırdım ( burada test , t1 t4 ile karşılaştırın). Bunun neden olduğu konusunda hala şaşkınım.

Performans hakkında akıl yürütürken (özellikle bellek kullanımı ile ilgili) sorabileceğiniz birkaç soru şunlardır: 1) girdim ne kadar büyük? 2) Derleyicim ne kadar akıllı? 3) Çalışma zamanım belleği nasıl yönetir? Bu kapsamlı değil, ama bir başlangıç ​​noktası.

  1. Girişim ne kadar büyük?

    Karmaşık bir çözüm genellikle sabit bir ek yüke, belki de yapılacak ek işlemler biçiminde veya belki de fazladan belleğe ihtiyaç duyacaktır. Bu çözümler büyük vakaları ele alacak şekilde tasarlandığından, uygulayıcılar genellikle ek maliyet getirmekte sorun yaşamazlar, çünkü net kazanç, kodu mikro-optimize etmekten daha önemlidir. Dolayısıyla, girdiniz yeterince küçükse, naif bir çözüm, sadece bu ek yükten kaçınmak için karmaşık olandan daha iyi bir performansa sahip olabilir. ("yeterince küçük" olanı belirlemek zor kısmıdır)

  2. Derleyicim ne kadar akıllı?

    Birçok derleyici, yazılan ancak hiç okunmayan değişkenleri "optimize etmek" için yeterince akıllıdır. Benzer şekilde, iyi bir derleyici, saf bir dizgi birleştirmesini (çekirdek) kitaplık kullanımına dönüştürebilir ve birçoğu okuma yapılmadan yapılırsa, bu işlemler arasında bir dizeye dönüştürmeye gerek yoktur ( kaynak kodunuz bunu yapıyor gibi görünüyor). Orada herhangi bir derleyici olup olmadığını veya ne ölçüde yapıldığını söyleyemem (AFAIK Java en azından aynı ifadede birkaç concat StringBuffer işlemleri bir dizi yerine), ama bu bir olasılık.

  3. Çalışma zamanım belleği nasıl yönetir?

    Modern CPU'larda darboğaz genellikle işlemci değil, önbellektir; kodunuz kısa bir süre içinde birçok "uzak" bellek adresine erişiyorsa, tüm bu belleği önbellek düzeyleri arasında taşımak için kullanılan süre, kullanılan talimatlardaki çoğu optimizasyondan ağır basar. En yeni oluşturulan değişkenler (örneğin, aynı işlev kapsamı içinde) genellikle bitişik bellek adreslerinde olacağından, kuşak çöp toplayıcıları ile çalışma zamanlarında bu özellikle önemlidir. Bu çalışma zamanları, yöntemi çağrılar arasında düzenli olarak hafızayı ileri geri taşır.

    Dize birleştirme etkileyebilir bir yolu (feragat: Bu vahşi bir tahmin, emin söylemek için yeterince bilgili değilim) saf biri için bellek onu kullanan kodun geri kalanına yakın tahsis eğer olurdu (hatta birden çok kez ayırır ve serbest bırakırsa), kitaplık nesnesinin belleği ondan uzak olarak ayrılırken (böylece kodunuz hesaplanırken birçok bağlam değişir, kitaplık tüketir, kodunuz daha fazla hesaplar, vb. Tabii ki büyük giriş OTOH için önbellek özledim zaten olacak, bu nedenle çoklu tahsis sorunu daha belirgin hale gelir.

Bununla birlikte, şu ya da bu yöntemin kullanımını savunmuyorum, sadece test ve profilleme ve kıyaslama performansla ilgili herhangi bir teorik analizden önce gelmelidir, çünkü günümüzde çoğu sistem bu konuda derin bir uzmanlık olmadan tam olarak anlaşılamayacak kadar karmaşıktır.


Evet, bu kesinlikle bir derleyicinin teorik olarak bir dizi dizeyi eklemeye çalıştığınızı ve daha sonra bir dize oluşturucu kullanıyormuşsunuz gibi optimize ettiğiniz bir alan olduğunu kabul ediyorum. Ancak bu pek de önemsiz bir şey değil ve modern derleyicilerde uygulandığını düşünmüyorum. Bana bir lisans araştırma projesi için harika bir fikir verdin: D.
JSideris

Bu yanıtı kontrol edin , Java derleyicisi zaten StringBuilderkaputun altında kullanıyor , tek yapmanız toStringgereken değişken gerçekten gerekli olana kadar aramamaktır. Doğru hatırlıyorsam, tek bir ifade için bunu yapar , tek şüphe, aynı yöntemde birden çok ifade için geçerli olup olmadığıdır. .NET internals hakkında hiçbir şey bilmiyorum, ancak benzer bir stratejinin C # derleyicisi tarafından da kullanılabileceğine inanıyorum.
mgibsonbr

0

Joel bir süre önce bu konuda harika bir makale yazdı . Bazılarının işaret ettiği gibi, büyük ölçüde dile bağlıdır. Dizelerin C'de uygulanma şekli nedeniyle (uzunluk alanı olmadan sıfır sonlandırılmış), standart strcat kütüphane rutini çok verimsizdir. Joel, çok daha verimli olan küçük bir değişiklikle bir alternatif sunuyor.


-1

Dizeleri birer birer birleştirmek verimsiz mi?

Hayır.

'Mikro-Optimizasyon Tiyatrosunun Üzücü Trajedisi'ni okudunuz mu?


4
"Erken optimizasyon tüm kötülüklerin köküdür." - Knuth
Scott C Wilson

4
Optimizasyondaki tüm kötülüklerin kökü, bu ifadeyi bağlamsız almaktır.
OZ_

Bazı destekleyici nedenler olmadan bir şeyin doğru olduğunu söylemek böyle bir forumda yararlı değildir.
Edward Strange

@Crazy Eddie: Jeff Atwood'un neden söylemek zorunda olduğunu okudun mu?
Jim G.10
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.