C ++ 'da Sistematik Hata İşlemeyi izliyordum - Andrei Alexandrescu C ++' daki İstisnaların çok çok yavaş olduğunu iddia ediyor.
Bu hala C ++ 98 için geçerli mi?
C ++ 'da Sistematik Hata İşlemeyi izliyordum - Andrei Alexandrescu C ++' daki İstisnaların çok çok yavaş olduğunu iddia ediyor.
Bu hala C ++ 98 için geçerli mi?
Yanıtlar:
Bugün istisnalar için kullanılan ana model (Itanium ABI, VC ++ 64 bit) Sıfır Maliyetli model istisnalarıdır.
Buradaki fikir, bir koruma kurarak ve her yerde istisnaların varlığını açıkça kontrol ederek zaman kaybetmek yerine, derleyicinin bir istisna (Program Sayacı) atabilecek herhangi bir noktayı bir işleyici listesine eşleyen bir yan tablo oluşturmasıdır. Bir istisna atıldığında, doğru işleyiciyi (varsa) seçmek için bu listeye başvurulur ve yığın çözülür.
Tipik if (error)stratejiyle karşılaştırıldığında :
ifbir istisna meydana geldiğinde yaklaşık 10x / 20x maliyetiBununla birlikte, maliyeti ölçmek önemsiz değildir:
dynamic_casther işleyici için bir test)Dolayısıyla, çoğunlukla önbellek ıskalıyor ve bu nedenle saf CPU koduyla karşılaştırıldığında önemsiz değil.
Not: Daha fazla ayrıntı için TR18015 raporu, bölüm 5.4 İstisnaların Ele Alınması (pdf) bölümünü okuyun
Bu nedenle, evet, istisnalar istisnai yolda yavaştır , ancak bunun dışında ifgenel olarak açık kontrollerden ( strateji) daha hızlıdırlar .
Not: Andrei Alexandrescu bunu "daha hızlı" sorguluyor gibi görünüyor. Ben şahsen olayların her iki yönde değiştiğini gördüm, bazı programların istisnalarla daha hızlı ve diğerlerinin dallarda daha hızlı olduğunu, bu yüzden gerçekten de belirli koşullarda optimizasyon kaybı var gibi görünüyor.
Önemli mi ?
Ben öyle olmadığını iddia ediyorum. Bir program , performansı değil (en azından ilk kriter olarak değil) okunabilirliği dikkate alınarak yazılmalıdır . İstisnalar, arayanın arızayı yerinde halledemeyeceği veya istemeyeceği beklendiğinde ve yığından geçirildiğinde kullanılacaktır. Bonus: C ++ 11'de istisnalar, Standart Kitaplık kullanılarak iş parçacıkları arasında sıralanabilir.
Bu ince olsa da, bunun map::findatılmaması gerektiğini iddia ediyorum, ancak map::findgeri gönderme checked_ptrgirişimi başarısız olduğu için başarısız olursa bir atışı geri göndermekte sorun yok : ikinci durumda, Alexandrescu'nun tanıttığı sınıf örneğinde olduğu gibi , arayan kişi seçer açık kontrol ve istisnalara güvenme arasında. Arayan kişiyi daha fazla sorumluluk vermeden güçlendirmek genellikle iyi bir tasarımın işaretidir.
abort, ikili boyuttaki ayak izini ölçmenize ve yükleme süresi / i-önbelleğin benzer şekilde davrandığını kontrol etmenize olanak sağlar. Elbette, hiçbirini abort
Soru yayınlandığında, doktora giderken bir taksi beklemekteydim, bu yüzden kısa bir yorum için sadece vaktim vardı. Ama şimdi yorum yaptıktan, olumlu oy verdikten ve olumsuz oy kullandığım için kendi cevabımı eklemem daha iyi olur. Matthieu'nun cevabı zaten oldukça iyi olsa bile .
İddia yeniden
" C ++ 'da Sistematik Hata İşlemeyi izliyordum - Andrei Alexandrescu , C ++' daki İstisnaların çok çok yavaş olduğunu iddia ediyor."
Andrei'nin iddia ettiği gibi kelimenin tam anlamıyla buysa, o zaman düpedüz yanlış değilse de bir kez olsun çok yanıltıcıdır. Çünkü yükseltilen / atılan bir istisna, programlama dilinden bağımsız olarak, dildeki diğer temel işlemlerle karşılaştırıldığında her zaman yavaştır . İddia edilen iddianın da belirttiği gibi, sadece C ++ 'da değil, C ++' da diğer dillerden daha fazla.
Genel olarak, çoğunlukla dilden bağımsız olarak, diğerlerinden daha yavaş olan iki temel dil özelliği, çünkü karmaşık veri yapılarını işleyen rutin çağrılarına tercüme ederler.
istisna atma ve
dinamik bellek tahsisi.
C ++ 'da mutlu bir şekilde, zaman açısından kritik kodlarda her ikisinden de kaçınabilir.
Ne yazık ki , C ++ 'ın varsayılan verimliliği oldukça yakın gelse bile, Bedava Öğle Yemeği Gibi Bir Şey Yok . :-) İstisna atmadan ve dinamik bellek tahsisinden kaçınarak kazanılan verimlilik genellikle daha düşük bir soyutlama seviyesinde kodlama ile elde edilir, C ++ 'ı sadece "daha iyi bir C" olarak kullanır. Ve daha düşük soyutlama daha fazla "karmaşıklık" anlamına gelir.
Daha fazla karmaşıklık, bakım için daha fazla zaman harcanması ve kodun yeniden kullanımından çok az fayda sağlanması veya hiç fayda görmemesi anlamına gelir; bu, tahmin edilmesi veya ölçülmesi zor olsa bile gerçek parasal maliyetlerdir. Yani, C ++ ile, istenirse, yürütme verimliliği için bazı programcı verimliliğinin ticareti yapılabilir. Bunu yapıp yapmamak büyük ölçüde mühendislik ve içgüdüsel bir karardır, çünkü pratikte sadece kazanç, maliyet değil, kolayca tahmin edilebilir ve ölçülebilir.
Evet, uluslararası C ++ standardizasyon komitesi C ++ performansı hakkında TR18015 Teknik Rapor yayınladı .
Esas olarak , işleyici araması nedeniyle, throwörneğin bir intatama ile karşılaştırıldığında Çok Uzun Süre ™ alabileceği anlamına gelir .
TR18015'in 5.4 "İstisnalar" bölümünde tartıştığı gibi, iki temel istisna işleme uygulama stratejisi vardır:
Her bir trybloğun istisna yakalamayı dinamik olarak kurduğu yaklaşım , böylece dinamik işleyiciler zincirinin aranması, bir istisna atıldığında gerçekleştirilir ve
Derleyicinin, atılan bir özel durum için işleyiciyi belirlemek için kullanılan statik arama tabloları oluşturduğu yaklaşım.
İlk çok esnek ve genel yaklaşım neredeyse 32-bit Windows'ta zorlanırken, 64-bit arazi ve * nix-land'de ikinci çok daha verimli yaklaşım yaygın olarak kullanılır.
Ayrıca, bu raporun da tartıştığı gibi, her yaklaşım için, istisna işlemenin verimliliği etkilediği üç ana alan vardır:
try-bloklar,
düzenli işlevler (optimizasyon fırsatları) ve
throw-ifade.
Temel olarak, dinamik işleyici yaklaşımıyla (32 bit Windows) istisna işleme, trybloklar üzerinde , çoğunlukla dilden bağımsız olarak bir etkiye sahiptir (çünkü bu, Windows'un Yapılandırılmış İstisna İşleme şeması tarafından zorlanır ), statik tablo yaklaşımı ise try- bloklar. Bunu tartışmak, SO cevabı için pratik olandan çok daha fazla alan ve araştırma gerektirir. Bu nedenle, ayrıntılar için rapora bakın.
Maalesef 2006 tarihli rapor, 2012'nin sonlarından itibaren biraz tarihli ve bildiğim kadarıyla daha yeni olan karşılaştırılabilir bir şey yok.
Diğer bir önemli bakış açısı, istisnaların kullanımının performans üzerindeki etkisinin , destekleyici dil özelliklerinin izole edilmiş verimliliğinden çok farklı olmasıdır, çünkü raporun da belirttiği gibi,
"İstisna işlemeyi değerlendirirken, hatalarla başa çıkmanın alternatif yollarıyla karşılaştırılmalıdır."
Örneğin:
Farklı programlama stilleri nedeniyle bakım maliyetleri (doğruluk)
Yedekli arama sitesi ifarıza kontrolü ile merkezileştirilmiştry
Önbelleğe alma sorunları (örneğin, daha kısa kod önbelleğe sığabilir)
Raporda dikkate alınması gereken farklı yönler listesi vardır, ancak yine de yürütme verimliliği hakkında somut gerçekleri elde etmenin tek pratik yolu, muhtemelen aynı programı istisnaları kullanarak ve istisnaları kullanmadan, geliştirme süresinde belirli bir sınır içinde ve geliştiricilerle uygulamaktır her yola aşina ve ardından ÖLÇÜM .
Doğruluk neredeyse her zaman verimliliğin önüne geçer.
İstisnalar olmaksızın aşağıdakiler kolaylıkla gerçekleşebilir:
Bazı P kodları, bir kaynak elde etmek veya bazı bilgileri hesaplamak içindir.
Çağıran kod C, başarı / başarısızlık için kontrol edilmiş olmalı, ancak yapmıyor.
Var olmayan bir kaynak veya geçersiz bilgi C'yi izleyen kodda kullanılır ve genel kargaşaya neden olur.
Asıl sorun nokta (2) 'dir, burada olağan dönüş kodu şeması ile çağıran kod C kontrol etmeye zorlanmaz.
Bu tür bir denetimi zorlayan iki ana yaklaşım vardır:
P başarısız olduğunda doğrudan bir istisna atar.
P, C'nin ana değerini kullanmadan önce incelemesi gereken bir nesne döndürdüğünde (aksi takdirde bir istisna veya sonlandırma).
İkinci yaklaşım, ilk olarak Barton ve Nackman tarafından * Bilimsel ve Mühendislik C ++: Gelişmiş Teknikler ve Örneklerle Giriş adlı kitaplarında tanımlanan AFAIK idi ve burada Fallow"olası" bir fonksiyon sonucu için çağrılan bir sınıfı tanıttılar . Benzer bir sınıf adıoptionalBoost kitaplığında şimdi sunulmaktadır. Ve POD dışı sonuç durumunda Optionalbir std::vectordeğer taşıyıcısı kullanarak bir sınıfı kendiniz kolayca uygulayabilirsiniz .
İlk yaklaşımla çağıran kod C'nin istisna işleme tekniklerini kullanmaktan başka seçeneği yoktur. İkinci yaklaşımla, bununla birlikte, çağıran kod C'nin kendisi,if , esaslı kontrolün yoksa genel istisna işlemenin . Bu nedenle, ikinci yaklaşım programcı ile yürütme süresi verimliliği arasında denge kurmayı destekler.
"Bunun C ++ 98 için hala geçerli olup olmadığını bilmek istiyorum"
C ++ 98, ilk C ++ standardıydı. İstisnalar için, standart bir istisna sınıfları hiyerarşisi getirmiştir (maalesef oldukça kusurlu). Performans üzerindeki ana etki, istisna belirtimlerinin olasılığıydı (C ++ 11'de kaldırıldı), ancak bunlar ana Windows C ++ derleyicisi Visual C ++ tarafından hiçbir zaman tam olarak uygulanmadı: Visual C ++, C ++ 98 özel durum belirtim sözdizimini kabul eder, ancak yalnızca yok sayar istisna özellikleri.
C ++ 03, C ++ 98'in teknik bir düzeltmesiydi. C ++ 03'te gerçekten yeni olan tek şey değer başlatma idi . Bunun istisnalarla ilgisi yok.
C ++ 11 standart genel istisna özellikleri kaldırıldı ve noexcept anahtar kelimeyle .
C ++ 11 standardı ayrıca, istisnaların depolanması ve yeniden atılması için destek ekledi; bu, C ++ istisnalarını C dili geri aramalarında yaymak için harika. Bu destek, geçerli istisnanın nasıl saklanabileceğini etkili bir şekilde kısıtlar. Bununla birlikte, bildiğim kadarıyla, yeni kodda istisna işlemenin bir C dili geri aramasının her iki tarafında daha kolay kullanılabilmesi dışında, performansı etkilemiyor.
longjmpişleyiciye kalırsınız .
try..finallyYapı, yığın çözülmesi olmadan uygulanabilir. F #, C # ve Java'nın tümü try..finallyyığın çözme kullanmadan uygulanır . Sen sadece longjmpişleyiciye (daha önce açıkladığım gibi).
Kodu montaja dönüştürmedikçe veya karşılaştırmadıkça performans hakkında asla iddiada bulunamazsınız.
İşte gördükleriniz: (hızlı tezgah)
Hata kodu, oluşma yüzdesine duyarlı değildir. İstisnaların, asla atılmadıkları sürece biraz yükü vardır. Onları fırlattığınızda sefalet başlar. Bu örnekte, vakaların% 0,% 1,% 10,% 50 ve% 90'ı için atılmıştır. İstisnalar% 90 oranında atıldığında kod, istisnaların% 10 oranında atıldığı durumdan 8 kat daha yavaştır. Gördüğünüz gibi, istisnalar gerçekten yavaştır. Sık sık atılırlarsa kullanmayın. Uygulamanızın gerçek zamanlı gereksinimi yoksa, çok nadiren ortaya çıkarsa bunları atmaktan çekinmeyin.
Onlar hakkında birçok çelişkili görüş görüyorsunuz. Ama son olarak, istisnalar yavaş mı? Ben yargılamıyorum. Sadece kıyaslamayı izle.
Derleyiciye bağlıdır.
Örneğin GCC, istisnaları ele alırken çok zayıf performans göstermesiyle biliniyordu, ancak bu son birkaç yılda önemli ölçüde iyileşti.
Ancak, istisnaların ele alınmasının - adından da anlaşılacağı gibi - yazılım tasarımınızdaki kuraldan ziyade istisna olması gerektiğini unutmayın. Saniyede performansı etkileyecek kadar çok istisna atan bir uygulamanız varsa ve bu hala normal çalışma olarak kabul ediliyorsa, işleri farklı şekilde yapmayı düşünmelisiniz.
İstisnalar, tüm bu hantal hata işleme kodunu ortadan kaldırarak kodu daha okunaklı hale getirmenin harika bir yoludur, ancak bunlar normal program akışının bir parçası olur olmaz takip edilmesi gerçekten zor hale gelir. Unutmayın, a throwhemen hemen goto catchkılık değiştirmiş bir durumdur .
throw new Exceptionbir Java-ism'dir. bir kural olarak asla işaret atmamalı .
Evet, ama bu önemli değil. Neden?
Bunu okuyun:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
Temelde Alexandrescu'nun gibi kullanarak istisnalar (kullandıkları çünkü 50x yavaşlama tarif söylüyor, catcholarak else) çok yanlış. Bunu yapmaktan hoşlanan ppl için C ++ 22 :) 'nin şöyle bir şey ekleyeceği söyleniyor:
(temelde mevcut olandan kod üreten derleyici olduğu için bunun çekirdek dil olması gerektiğini unutmayın)
result = attempt<lexical_cast<int>>("12345"); //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
int x = result.get(); // or result.result;
}
else
{
// even possible to see what is the exception that would have happened in original function
switch (result.exception_type())
//...
}
Not: İstisnalar bu kadar yavaş olsa bile ... yürütme sırasında kodun bu bölümünde çok fazla zaman harcamamanız bir sorun değildir ... Örneğin, kayan bölme yavaşsa ve bunu 4x yaparsanız Zamanınızın% 0,3'ünü FP bölümü için harcarsanız, bu önemli değil ...
In silico'nun dediği gibi, uygulamasına bağımlıdır, ancak genel olarak istisnalar herhangi bir uygulama için yavaş kabul edilir ve performans yoğun kodda kullanılmamalıdır.
DÜZENLEME: Bunları hiç kullanmayın demiyorum ama yoğun performans gerektiren kodlar için onlardan kaçınmak en iyisidir.