Tamam, problemi, iyileştirilmesi için fazla yer olmadığı anlaşılıyor. Bu benim deneyimime göre oldukça nadirdir. Bunu, Kasım 1993'teki Dr. Dobbs makalesinde, belirgin bir atık olmadan geleneksel olarak iyi tasarlanmış önemsiz bir programdan başlayarak ve duvar saati 48 saniyeden kısalıncaya kadar bir dizi optimizasyon yoluyla açıklamaya çalıştım. 1.1 saniyeye ve kaynak kodu boyutu 4. My tanı aracı kat azaldı bu oldu . Değişiklik sırası şuydu:
Bulunan ilk sorun, yarıdan uzun süredir hesaplanan liste kümelerinin (şimdi "yineleyiciler" ve "kapsayıcı sınıfları" olarak adlandırılır) kullanılmasıydı. Bunlar oldukça basit bir kodla değiştirildi ve zaman 20 saniyeye indirildi.
Şimdi en büyük zaman alıcı daha liste oluşturma. Yüzde olarak, daha önce çok büyük değildi, ama şimdi daha büyük sorunun giderilmesinden kaynaklanıyor. Hızlanmanın bir yolunu buluyorum ve zaman 17 saniyeye düşüyor.
Şimdi bariz suçlular bulmak daha zor, ama hakkında bir şeyler yapabileceğim birkaç küçük tane var ve zaman 13 saniyeye düşüyor.
Şimdi bir duvara çarpmış gibiyim. Örnekler bana tam olarak ne yaptığını söylüyor, ancak geliştirebileceğim bir şey bulamıyorum. Daha sonra programın temel tasarımına, işlem odaklı yapısına odaklanıyorum ve yaptığı tüm liste aramasının sorunun gereksinimlerine göre zorunlu olup olmadığını soruyorum.
Sonra, program kodunun daha küçük bir kaynaktan gerçekten oluşturulduğu (önişlemci makroları aracılığıyla) ve programın programcının oldukça tahmin edilebilir olduğunu bildiği şeyleri sürekli olarak çözmediği bir yeniden tasarıma çarptım. Başka bir deyişle, yapılacak şeylerin sırasını "yorumlamayın", "derleyin".
- Bu yeniden tasarım, kaynak kodunu 4 kat küçülterek yapılır ve süre 10 saniyeye düşürülür.
Şimdi, çok hızlı olduğu için, örneklemesi zor, bu yüzden yapmak için 10 kat daha fazla iş veriyorum, ancak aşağıdaki süreler orijinal iş yüküne dayanıyor.
Daha fazla tanı, kuyruk yönetiminde zaman harcadığını ortaya koymaktadır. Bu astarlar süreyi 7 saniyeye düşürür.
Şimdi büyük bir zaman alıcı, yaptığım teşhis baskısı. Yıkayın - 4 saniye.
Şimdi en büyük zaman alıcılar malloc ve ücretsiz çağrılardır . Nesneleri geri dönüştürme - 2,6 saniye.
Örneklemeye devam ederken, hala kesinlikle gerekli olmayan işlemleri buluyorum - 1.1 saniye.
Toplam hız faktörü: 43,6
Şimdi iki program birbirine benzemiyor, ancak oyuncak olmayan yazılımda her zaman böyle bir ilerleme gördüm. Önce azalan bir noktaya ulaşıncaya kadar kolay şeyleri ve sonra daha zor olanı elde edersiniz. Ardından, elde ettiğiniz içgörü, tekrar azalan getirilere ulaşana kadar yeni bir hızlanma turu başlatarak yeniden tasarıma yol açabilir. Şimdi bu o olsun merak mantıklı olabilir noktadır ++i
ya i++
ya for(;;)
ya while(1)
daha hızlı: Ben Yığın taşması bu yüzden sık görmek tür sorular.
Not: Neden profiler kullanmadım merak edilebilir. Cevap, bu "problemlerin" neredeyse her birinin, numunelerin yerini saptayan bir fonksiyon çağrı sitesi olmasıdır. Profilciler, bugün bile, ifadelerin ve çağrı talimatlarının bulunması, tüm işlevlerden daha önemli ve düzeltilmesi daha kolay olduğu fikrine pek yaklaşmıyor.
Aslında bunu yapmak için bir profil oluşturdum, ancak kodun ne yaptığına dair gerçek bir aşağılık yakınlığı için, parmaklarınızı doğru bir şekilde almanın yerini tutamaz. Örneklerin sayısının az olması bir sorun değildir, çünkü bulunan sorunların hiçbiri o kadar küçük değildir ki kolayca gözden kaçırılırlar.
ADDED: jerryjvl bazı örnekler istedi. İşte ilk sorun. Birlikte zamanın yarısını alan az sayıda ayrı kod satırından oluşur:
/* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)
Bunlar ILST liste kümesini kullanıyordu (liste sınıfına benzer). "Bilgi gizleme" ile olağan bir şekilde uygulanırlar, yani sınıf kullanıcılarının nasıl uygulandıklarına dikkat etmeleri gerekmezdi. Bu satırlar (yaklaşık 800 satır kod dışında) yazıldığında, bunların bir "darboğaz" olabileceği fikrine verilmedi (o kelimeden nefret ediyorum). Bunlar sadece bir şeyler yapmanın önerilen yoludur. Gezde bunlardan kaçınılması gerektiğini söylemek kolaydır , ancak benim deneyimime göre tüm performans sorunları böyledir. Genel olarak, performans problemleri yaratmaktan kaçınmak iyidir. Yaratılmış olanları bulmak ve düzeltmek daha da iyidir ("kaçınılması gereken" olsalar bile).
İkinci sorun, iki ayrı satırda:
/* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)
Bunlar, öğelerini sonuna ekleyerek listeler oluşturuyor. (Düzeltme, öğeleri diziler halinde toplamak ve listeleri bir kerede oluşturmaktı.) İlginç olan, bu ifadelerin yalnızca orijinal sürenin 3 / 48'ine mal olması (yani çağrı yığınında). Aslında başlangıçta büyük bir problem . Ancak, ilk sorunu çıkardıktan sonra, zamanın 3/20 maliyeti ve bu yüzden şimdi "büyük balık" idi. Genel olarak böyle devam eder.
Bu projenin yardım ettiğim gerçek bir projeden damıtıldığını ekleyebilirim. Bu projede, bir görevin bitip bitmediğini görmek için bir iç döngü içinde veritabanı erişim rutinini çağırmak gibi performans sorunları çok daha dramatikti (hızlandırmalar gibi).
REFERANS EKLENDİ: kaynak kodu, hem orijinal hem yeniden tasarlanmış, bulunabilir www.ddj.com dosyası 9311.zip, dosyalar slug.asc ve slug.zip içinde, 1993 için.
EDIT 2011/11/26: Şimdi Visual C ++ kaynak kodu ve nasıl ayarlanmış bir darbe açıklaması içeren bir SourceForge projesi var . Sadece yukarıda açıklanan senaryonun ilk yarısını geçer ve tam olarak aynı sırayı takip etmez, ancak yine de 2-3 derecelik bir hız artışı elde eder.