Daha iyi performans elde etmek için gömülü yazılım için işlev yazarken en iyi yaklaşım nedir? [kapalı]


13

Mikrodenetleyiciler için bazı kütüphaneleri gördüm ve işlevleri bir seferde bir şey yapıyor. Örneğin, böyle bir şey:

void setCLK()
{
    // Code to set the clock
}

void setConfig()
{
    // Code to set the config
}

void setSomethingElse()
{
   // 1 line code to write something to a register.
}

Daha sonra, başka amaçlara hizmet etmek için bir işlev içeren bu 1 satır kodunu kullanan diğer işlevler üzerine gelin. Örneğin:

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

Emin değilim, ancak bu şekilde atlamalar için daha fazla çağrı yaratacağı ve bir işlev her çağrıldığında veya çıkıldığında dönüş adreslerini istifleme yükü oluşturacağına inanıyorum. Ve bu programın yavaş çalışmasını sağlayacaktır, değil mi?

Araştırdım ve her yerde programlamanın temel kuralı, bir fonksiyonun sadece bir görevi yerine getirmesi gerektiğini söylüyorlar.

Bu yüzden doğrudan saati ayarlayan bir InitModule işlev modülü yazarsam, istenen yapılandırmayı ekler ve işlevleri çağırmadan başka bir şey yapar. Gömülü yazılım yazarken kötü bir yaklaşım mı?


DÜZENLEME 2:

  1. Pek çok insan bu soruyu bir programı optimize etmeye çalıştığımı anlamış görünüyor. Hayır, yapmaya niyetim yok . Derleyicinin yapmasına izin veriyorum, çünkü her zaman benden daha iyi olacak (umarım olmasın!).

  2. Bazı başlatma kodunu temsil eden bir örnek seçmek için üzerimdeki tüm suçlamalar . Sorunun, başlatma amacıyla yapılan işlev çağrıları ile ilgili bir niyeti yoktur. Benim sorum şudur: Belirli bir görevi sonsuz bir döngü içinde çalışan çok satırlı küçük işlevlere ( yani satır içi söz konusu değildir ) bölmek , iç içe geçmiş bir işlev olmadan uzun işlev yazma konusunda herhangi bir avantaj sağlar mı?

Lütfen @ Jonk cevabında tanımlanan okunabilirliği göz önünde bulundurun .


28
Makul bir derleyicinin kodları yazılı olarak ikiliye körü körüne çevireceğine inanıyorsanız çok naifsiniz (hakaret anlamına gelmez). Modern derleyicilerin çoğu, bir rutinin daha iyi satır içine alındığını ve bir değişkeni tutmak için bir kayıt yerine RAM konumunun kullanılması gerektiğini belirlemede oldukça iyidir. İki optimizasyon kuralına uyun: 1) optimizasyon yapmayın. 2) henüz optimize etmeyin . Kodunuzu okunabilir ve bakım yapılabilir hale getirin ve daha sonra yalnızca bir çalışma sistemi oluşturduktan sonra optimize etmeye bakın.
16:18

10
@akohlsmith IIRC Üç optimizasyon kuralı şunlardır: 1) Yapma! 2) Hayır gerçekten yapma! Eğer 3) optimize ancak o zaman o, ilk Profil ve mutlaka - Michael_A._Jackson
esoterik

3
Sadece "erken optimizasyon programlama tüm kötülüklerin (ya da en azından çoğunun)
kökü olduğunu unutmayın

1
@Mawg: Oradaki ameliyat kelimesi erken . (Bu makaledeki bir sonraki paragrafta açıklandığı gibi. Kelimenin tam anlamıyla bir sonraki cümle: "Yine de fırsatlarımızı bu kritik% 3'te geçmemeliyiz.") İhtiyacınız olana kadar optimize etmeyin - yavaş olanı bulamayacaksınız profil için bir şey olana kadar bitler - ama aynı zamanda, örneğin iş için bariz şekilde yanlış araçlar kullanarak, kötümserlik yapmayın.
cHao

1
@Mawg Optimizasyon ile ilgili neden cevaplar / geri bildirimler aldığımı bilmiyorum, çünkü kelimeden hiç bahsetmedim ve bunu yapmak niyetindeyim. Soru, daha iyi performans elde etmek için Gömülü Programlama'da işlevlerin nasıl yazılacağı hakkında daha fazladır.
MaNyYaCk

Yanıtlar:


28

Muhtemelen, örneğinizde, kod başlangıçta sadece bir kez çalıştırıldığından performans önemli olmaz.

Kullandığım temel kural: Kodunuzu olabildiğince okunabilir yazın ve yalnızca derleyicinizin sihrini düzgün bir şekilde yapmadığını fark ederseniz optimizasyona başlayın.

ISR'deki bir işlev çağrısının maliyeti, başlatma ve saklama sırasındaki bir işlev çağrısının maliyeti ile aynı olabilir. Ancak, bu ISR sırasındaki zamanlama gereksinimleri çok daha kritik olabilir.

Ayrıca, başkaları tarafından fark edildiği gibi, bir işlev çağrısının maliyeti (ve 'maliyetinin anlamı') platform, derleyici, derleyici optimizasyon ayarı ve uygulamanın gereksinimlerine göre değişir. Bir 8051 ve bir korteks-m7 ile bir kalp pili ve bir ışık anahtarı arasında büyük bir fark olacaktır.


6
IMO ikinci paragraf koyu ve üstte olmalıdır. Doğru algoritmaları ve veri yapılarını yarasadan seçmenin yanlış bir yanı yok, ancak gerçek bir darboğazın kesinlikle erken optimizasyon olduğunu ve kaçınılması gerektiğini keşfetmedikçe, işlev çağrısı yükü hakkında endişelenmek .
Monica'nın Davası

11

Düşünebileceğim bir avantaj yok (ancak alttaki JasonS notuna bakın), bir kod satırını bir işlev veya altyordam olarak tamamlıyor. Belki fonksiyonu "okunabilir" olarak adlandırabilirsiniz. Ancak bu satırı da yorumlayabilirsiniz. Ve bir kod satırını bir fonksiyona sarmak kod hafızasına, yığın alanına ve yürütme süresine mal olduğu için, bana çoğunlukla bunun karşı üretken olduğu görülüyor . Öğretme durumunda mı? Bir anlam ifade edebilir. Ancak bu, öğrencilerin sınıfına, önceden hazırlanmalarına, müfredata ve öğretmene bağlıdır. Çoğunlukla, bunun iyi bir fikir olmadığını düşünüyorum. Ama bu benim görüşüm.

Bu da bizi en alt düzeye getiriyor. Geniş soru alanınız onlarca yıldır tartışma konusu ve günümüzde tartışma konusu olmaya devam ediyor. Yani, en azından sorunuzu okuduğumda, bana fikir tabanlı bir soru gibi geliyor (sorduğunuz gibi).

Durum hakkında daha ayrıntılı olmanız ve birincil olarak tuttuğunuz hedefleri dikkatli bir şekilde tanımlamanız durumunda, görüşe dayalı olmaktan uzaklaşabilir. Ölçüm araçlarınızı ne kadar iyi tanımlarsanız, cevaplar o kadar objektif olabilir.


Genel olarak, herhangi bir kodlama için aşağıdakileri yapmak istersiniz . (Aşağıda, tüm hedeflere ulaşan farklı yaklaşımları karşılaştırdığımızı varsayacağım. Açıkçası, gerekli görevleri yerine getiremeyen herhangi bir kod, nasıl yazıldığından bağımsız olarak başarılı olan koddan daha kötüdür.)

  1. Yaklaşımınız hakkında tutarlı olun, böylece kodunuzu okuduğunuzda kodlama sürecinize nasıl yaklaştığınız konusunda bir anlayış geliştirilebilir. Tutarsız olmak, muhtemelen en kötü suçtur. Sadece başkaları için zorlaştırmakla kalmaz, aynı zamanda yıllar sonra koda geri dönmenizi de zorlaştırır.
  2. Mümkün olduğu ölçüde, çeşitli fonksiyonel bölümlerin başlatılmasını sipariş vermeden gerçekleştirilebilecek şekilde düzenlemeye çalışın. Siparişin gerekli olduğu durumlarda, yüksek derecede ilişkili iki alt fonksiyonun yakın kuplajından kaynaklanıyorsa, zarar vermeden yeniden sıralanabilmesi için her ikisi için tek bir başlatmayı düşünün. Bu mümkün değilse, başlatma siparişi gereksinimini belgeleyin.
  3. Mümkünse, bilgileri tek bir yerde kapsülleyin . Sabitler, kodun her yerinde çoğaltılmamalıdır. Bazı değişkenleri çözen denklemler tek bir yerde mevcut olmalıdır. Ve bunun gibi. Kendinizi çeşitli konumlarda gerekli davranışları gerçekleştiren bazı satırları kopyalayıp yapıştırırken bulursanız, bu bilgiyi tek bir yerde yakalamanın ve gerektiğinde kullanmanın bir yolunu düşünün. Belirli bir şekilde yürüdü gereken bir ağaç yapısı varsa Örneğin, do notağaç yürüme kodunu ağaç düğümleri arasında dolaşmanız gereken her yerde çoğaltın. Bunun yerine, ağaç yürüme yöntemini tek bir yerde yakalayın ve kullanın. Bu şekilde, ağaç değişirse ve yürüme yöntemi değişirse, endişelenecek tek bir yeriniz vardır ve kodun geri kalan kısmı "doğru çalışır."
  4. Tüm rutinlerinizi büyük, düz bir kağıda yayarsanız, okları diğer rutinler tarafından çağrıldığı gibi birbirine bağlarsanız, herhangi bir uygulamada çok sayıda ok içeren rutinlerin "kümeleri" olduğunu göreceksiniz kendi aralarında ama grup dışında sadece birkaç ok. Bu nedenle , yakından bağlı rutinlerin diğer grupları arasında sıkıca bağlı rutinlerin ve gevşek bağlı bağlantıların doğal sınırları olacaktır . Kodunuzu modüller halinde düzenlemek için bu gerçeği kullanın. Bu, kodunuzun görünür karmaşıklığını önemli ölçüde sınırlayacaktır.

Yukarıdakiler tüm kodlamalar için genellikle doğrudur. Parametrelerin, yerel veya statik küresel değişkenlerin vb. Kullanımını tartışmadım. Bunun nedeni, gömülü programlama için uygulama alanının genellikle aşırı ve çok önemli yeni kısıtlamalar koyması ve her gömülü uygulamayı tartışmadan hepsini tartışmak imkansızdır. Ve bu zaten burada değil.

Bu kısıtlamalar bunlardan herhangi biri (ve daha fazlası) olabilir:

  • Küçük RAM içeren ve neredeyse hiç G / Ç pin sayımı olmayan son derece ilkel MCU'lar gerektiren ciddi maliyet sınırlamaları. Bunlar için yepyeni kurallar uygulanır. Örneğin, çok fazla kod alanı olmadığından montaj kodunu yazmanız gerekebilir. YALNIZCA statik değişkenleri kullanmanız gerekebilir, çünkü yerel değişkenlerin kullanımı çok maliyetli ve zaman alıcıdır. Alt yordamların aşırı kullanımından kaçınmanız gerekebilir, çünkü (örneğin, bazı Microchip PIC parçaları) alt yordam dönüş adreslerinin saklanacağı yalnızca 4 donanım kaydı vardır. Bu nedenle, kodunuzu önemli ölçüde "düzleştirmek" zorunda kalabilirsiniz. Vb.
  • MCU'nun çoğunu başlatmak ve kapatmak için dikkatli bir şekilde hazırlanmış kod gerektiren ve tam hızda çalışırken kodun yürütme süresine ciddi sınırlamalar getiren ciddi güç sınırlamaları. Yine, bu bazen bir takım kodlama gerektirebilir.
  • Ciddi zamanlama gereksinimleri. Örneğin, açık bir drenaj 0'ın iletiminin 1'in iletimi ile tam olarak aynı sayıda devir alması gerektiğinden emin olmam gereken zamanlar var. bu zamanlamaya tam bir bağıl faz ile. Bu, C'nin burada KULLANILAMAZ anlamına geliyordu. Bu garantiyi vermenin SADECE yolu montaj kodunu dikkatlice hazırlamaktır. (Ve o zaman bile, her zaman tüm ALU tasarımlarında değil.)

Ve bunun gibi. (Yaşam için kritik tıbbi cihazların kablolama kodunun da kendi dünyası vardır.)

Buradaki sonuç, gömülü kodlamanın genellikle herkes için ücretsiz olmadığı ve bir iş istasyonunda olduğu gibi kod yazabileceğinizdir. Çok çeşitli çok zor kısıtlamalar için genellikle ciddi, rekabetçi nedenler vardır. Ve bunlar daha geleneksel ve stok yanıtlarına şiddetle karşı çıkabilir .


Okunabilirlik ile ilgili olarak, okuduğumda öğrenebileceğim tutarlı bir şekilde yazılmışsa kodun okunabilir olduğunu görüyorum. Ve kasıtlı olarak kasıtlı gizleme girişimi olmadığında. Gerçekten daha fazlası gerekmez.

Okunabilir kod oldukça verimli olabilir ve daha önce bahsettiğim tüm yukarıdaki gereksinimleri karşılayabilir. Önemli olan, yazdığınız her kod satırının kodlama sırasında montaj veya makine düzeyinde ne ürettiğini tam olarak anlamanızdır. C ++ birçok durum vardır çünkü burada programcı üzerinde ciddi bir yük getirmektedir özdeş C ++ kodunun parçacıkları aslında üretmek farklı çok farklı performanslar var makine kod parçacıkları. Ancak C, genel olarak, çoğunlukla "gördüğünüz şey ne elde edersiniz" dilidir. Yani bu konuda daha güvenli.


JasonS başına DÜZENLE:

1978'den beri C ve 1987'den beri C ++ kullanıyorum ve hem ana bilgisayarlar, mini bilgisayarlar hem de (çoğunlukla) gömülü uygulamalar için çok fazla deneyim yaşadım.

Jason, 'satır içi'yi değiştirici olarak kullanma hakkında bir yorum getiriyor. (Benim bakış açımdan, bu nispeten "yeni" bir özelliktir, çünkü hayatımın belki de yarısı veya C ve C ++ kullanarak daha fazlası için mevcut değildi.) Satır içi işlevlerin kullanımı aslında bu tür çağrılar yapabilir (hatta kod) oldukça pratik. Ve mümkün olduğunda, derleyicinin uygulayabileceği yazma nedeniyle bir makro kullanmaktan çok daha iyidir.

Ancak sınırlamalar da var. Birincisi "ipucunu almak" için derleyiciye güvenemezsiniz. Olabilir ya da olmayabilir. Ve ipucunu almamak için iyi nedenler var. (Fonksiyonun adresi alınırsa bariz Örneğin, bu gerektirdiği fonksiyonun örnekleme ve ... bir çağrı gerektirecektir arama yapmak için adres kullanımını. Kod, daha sonra satır içine yerleştirilmiş olamaz.) Vardır diğer nedenler de. Derleyiciler, ipucunu nasıl ele alacağına karar verdikleri çok çeşitli kriterlere sahip olabilirler. Ve bir programcı olarak bu ,Derleyicinin bu yönünü öğrenmek için biraz zaman harcayın yoksa başka türlü kusurlu fikirlere dayalı kararlar verebilirsiniz. Bu nedenle, hem kodun yazarına hem de herhangi bir okuyucuya ve kodu başka bir derleyiciye taşımayı planlayan herkese bir yük ekler.

Ayrıca, C ve C ++ derleyicileri ayrı derlemeyi destekler. Bu, proje için ilgili herhangi bir kodu derlemeden bir parça C veya C ++ kodunu derleyebilecekleri anlamına gelir. Satır içi kod oluşturmak için, derleyicinin başka türlü yapmayı seçebileceğini varsayarsak, yalnızca "kapsamda" bildirimine sahip olmakla kalmaz, aynı zamanda tanımına da sahip olmalıdır. Genellikle, programcılar 'satır içi' kullanıyorlarsa, durumun böyle olduğundan emin olmak için çalışırlar. Ancak hataların içine girmesi kolaydır.

Genel olarak, uygun olduğunu düşündüğüm yerde satır içi de kullanırken, buna güvenemeyeceğimi varsayıyorum. Performans önemli bir gereklilikse ve bence OP daha açık bir şekilde "işlevsel" bir rotaya gittiklerinde önemli bir performans artışı olduğunu açıkça yazmışsa, o zaman kesinlikle satır içi bir kodlama uygulaması olarak güvenmekten kaçınmayı ve bunun yerine, biraz farklı ama tamamen tutarlı bir kod yazma modeli izler.

Ayrı bir derleme adımı için 'satır içi' ve tanımların "kapsamda" olduğuna dair son bir not. Bağlama aşamasında yapılacak işin (her zaman güvenilir olmadığı) mümkündür. Bu, yalnızca bir C / C ++ derleyicisinin nesne dosyalarına yeterli miktarda bağlayıcının bir bağlayıcının 'satır içi' istekleri üzerinde işlem yapmasına izin vermesi durumunda oluşabilir. Şahsen bu özelliği destekleyen bir bağlayıcı sistem (Microsoft'un dışında) yaşamadım. Ancak ortaya çıkabilir. Yine, buna güvenilip güvenilmeyeceği koşullara bağlı olacaktır. Ancak, aksi takdirde iyi kanıtlara dayanmadığını bilmedikçe, genellikle bağlayıcıya kürek çekilmediğini varsayıyorum. Ve eğer ona güvenirsem, önemli bir yerde belgelenecek.


C ++

İlgilenenler için, bugün hazır kullanılabilirliğine rağmen, gömülü uygulamaları kodlarken neden C ++ 'a karşı oldukça ihtiyatlı davrandığımın bir örneği. Tüm gömülü C ++ programcılarının soğuk bilmeleri gerektiğini düşündüğüm bazı terimleri atacağım :

  • kısmi şablon uzmanlığı
  • vtables
  • sanal temel nesne
  • aktivasyon çerçevesi
  • aktivasyon çerçevesi çözme
  • kurucularda akıllı işaretçilerin kullanımı ve neden
  • dönüş değeri optimizasyonu

Bu sadece kısa bir liste. Bu terimler ve neden bunları listelediğimi (ve burada listelemediğim birçok şey) zaten bilmiyorsanız , proje için bir seçenek değilse, gömülü çalışma için C ++ kullanımına karşı tavsiyede bulunacağım. .

Sadece bir lezzet elde etmek için C ++ istisna semantiğine hızlı bir göz atalım.

Bir C ++ derleyicisi , ayrı derleme birimi ayrı olarak ve farklı bir zamanda derlenen ne tür bir istisna işlemesi gerekebileceğine dair hiçbir fikre sahip olmadığında derleme birimi için doğru kodu üretmelidir . BAB

Bazı derleme birimleri bazı işlevlerin bir parçası olarak bulunan bu kod sırasını alın :A

   .
   .
   foo ();
   String s;
   foo ();
   .
   .

Tartışma amacıyla, derleme birimi kaynağında hiçbir yerde 'try..catch' kullanmaz . 'Atmak' da kullanmaz. Aslında, C ++ kitaplık desteğini kullanması ve String gibi nesneleri işleyebilmesi dışında, bir C derleyicisi tarafından derlenemeyen herhangi bir kaynak kullanmadığını varsayalım. Bu kod, String sınıfı gibi birkaç C ++ özelliğinden yararlanmak için biraz değiştirilmiş bir C kaynak kodu dosyası bile olabilir.A

Ayrıca, foo () ' nun derleme birimi bulunan harici bir prosedür olduğunu ve derleyicinin bunun için bir bildirimi olduğunu, ancak tanımını bilmediğini varsayın .B

C ++ derleyicisi, foo () öğesine yapılan ilk çağrıyı görür ve foo () bir istisna atarsa, normal bir etkinleştirme çerçevesinin gevşemesine izin verebilir. Başka bir deyişle, C ++ derleyicisi, istisna işlemede yer alan çerçeve çözme işlemini desteklemek için bu noktada fazladan bir kod gerekmediğini bilir.

Ancak String s oluşturulduktan sonra, daha sonra bir istisna oluşursa, C ++ derleyicisi bir çerçeve çözülmesine izin verilebilmesi için düzgün şekilde yok edilmesi gerektiğini bilir. Yani ikinci foo () çağrısı anlamsal olarak ilk çağrıdan farklıdır. 2. foo () çağrısı bir istisna atarsa ​​(bunu yapabilir veya yapmayabilir), derleyici normal çerçevenin gevşemesine izin vermeden önce String'lerin imhasını işlemek için tasarlanmış kod yerleştirmiş olmalıdır. Bu, ilk foo () çağrısı için gereken koddan farklıdır .

( Bu sorunu sınırlamaya yardımcı olmak için C ++ 'a ek süslemeler eklemek mümkündür . Ancak gerçek şu ki, C ++ kullanan programcılar yazdıkları her kod satırının etkilerinden çok daha fazla haberdar olmalıdırlar.)

C'nin malloc'undan farklı olarak, C ++ 'ın yeni özellikleri ham bellek ayırma gerçekleştiremediğinde sinyal vermek için istisnalar kullanır. 'Dynamic_cast' de öyle. (C ++ 'daki standart istisnalar için Stroustrup'un 3. baskısı, C ++ Programlama Dili, sayfa 384 ve 385'e bakın.) Derleyiciler bu davranışın devre dışı bırakılmasına izin verebilir. Ancak genel olarak, istisnalar gerçekte gerçekleşmediğinde ve derlenen fonksiyonun herhangi bir istisna işleme bloğu olmasa bile, oluşturulan koddaki uygun şekilde oluşturulmuş istisna işleme prologları ve epilogları nedeniyle bir miktar ek yüke maruz kalacaksınız. (Stroustrup bunu açıkça ağıt yaktı.)

Kısmi şablon uzmanlığı olmadan (tüm C ++ derleyicileri bunu desteklemez), şablonların kullanımı gömülü programlama için felakete yol açabilir. Onsuz, kod patlaması, küçük belleğe gömülü bir projeyi bir flaşta öldürebilecek ciddi bir risktir.

Bir C ++ işlevi bir nesneyi döndürdüğünde, adlandırılmamış bir derleyici geçici oluşturulur ve yok edilir. Bazı C ++ derleyicileri, yerel bir nesne yerine return deyiminde bir nesne yapıcısı kullanılırsa, bir nesnenin inşaat ve imha ihtiyaçlarını azaltarak etkin kod sağlayabilir. Ancak her derleyici bunu yapmaz ve birçok C ++ programcısı bu "dönüş değeri optimizasyonunun" farkında bile değildir.

Bir nesne yapıcısına tek bir parametre türünün sağlanması, C ++ derleyicisinin programcıya tamamen beklenmedik yollarla iki tür arasında bir dönüşüm yolu bulmasına izin verebilir. Bu tür "akıllı" davranışlar C'nin bir parçası değildir.

Bir taban türünü belirten bir catch cümlesi, atılan nesne nesnenin "dinamik tipi" değil, catch deyiminin "statik türü" kullanılarak kopyalandığından, atılan türetilen bir nesneyi "dilimleyecektir". Nadir olmayan bir istisna sefaleti kaynağı (gömülü kodunuzda istisnalar bile alabileceğinizi düşündüğünüzde).

C ++ derleyicileri sizin için otomatik olarak yapıcıları, yıkıcıları, kopya oluşturucuları ve atama işleçlerini istenmeyen sonuçlarla oluşturabilir. Bunun detayları ile tesis kazanmak zaman alır.

Türetilmiş nesnelerin dizilerini temel nesnelerin dizilerini kabul eden bir işleve geçirmek, nadiren derleyici uyarıları oluşturur, ancak neredeyse her zaman yanlış davranış verir.

Nesne yapıcısında bir istisna meydana geldiğinde C ++ kısmen oluşturulmuş nesnelerin yıkıcısını çağırmadığından, bir istisna oluşursa yapıcıdaki yerleşik parçaların doğru şekilde yok edilmesini garanti etmek için kurucularda istisnaların işlenmesi genellikle "akıllı işaretçiler" i zorunlu kılar. . (Bkz. Stroustrup, sayfa 367 ve 368.) Bu, C ++ 'da iyi sınıflar yazmada yaygın bir konudur, ancak C'de inşaat ve yıkım anlambilimi bulunmadığından elbette C'de önlenir. Yapıyı işlemek için uygun kod yazma bir nesnenin içindeki alt nesneler, C ++ 'daki bu benzersiz semantik sorunla başa çıkması gereken kod yazmak anlamına gelir; diğer bir deyişle "yazarak" C ++ semantik davranışları.

C ++, nesne parametrelerine iletilen nesneleri kopyalayabilir. Örneğin, aşağıdaki parçalarda "rA (x);" C ++ derleyicisinin p parametresi için bir yapıcı çağırmasına neden olabilir, daha sonra kopya yapıcısını x nesnesini p parametresine aktarmak için çağırır, daha sonra rA işlevinin dönüş nesnesi (adsız geçici) için başka bir kurucu p parametresinden kopyalandı. Daha da kötüsü, eğer A sınıfı inşa edilmesi gereken kendi nesneleri varsa, bu disasterous teleskop olabilir. (AC programcısı, C programcılarının bu kadar kullanışlı bir sözdizimine sahip olmadıkları ve tüm ayrıntıları birer birer ifade etmeleri gerektiğinden, el optimizasyonundan kaçınacaktır.)

    class A {...};
    A rA (A p) { return p; }
    // .....
    { A x; rA(x); }

Son olarak, C programcıları için kısa bir not. longjmp (), C ++ 'da taşınabilir bir davranışa sahip değildir. (Bazı C programcıları bunu bir tür "istisna" mekanizması olarak kullanır.) Bazı C ++ derleyicileri aslında longjmp alındığında temizlenecek şeyleri ayarlamaya çalışır, ancak bu davranış C ++ 'da taşınabilir değildir. Derleyici yapılandırılmış nesneleri temizlerse, taşınabilir değildir. Derleyici bunları temizlemezse, kod longjmp'nin sonucu olarak oluşturulan nesnelerin kapsamını terk ederse ve davranış geçersizse nesneler bozulmaz. (Eğer foo () içinde longjmp kullanımı bir kapsam bırakmazsa, davranış iyi olabilir.) Bu, C gömülü programcılar tarafından çok sık kullanılmaz, ancak kullanmadan önce bu sorunlardan haberdar olmalıdırlar.


4
Sadece bir kez kullanılan bu tür fonksiyonlar asla fonksiyon çağrısı olarak derlenmez, kod herhangi bir çağrı yapılmadan basitçe oraya yerleştirilir.
Dorian

6
@Dorian - Yorumunuz belirli derleyiciler için belirli koşullar altında doğru olabilir. İşlev dosya içinde statikse, derleyici kodu satır içi yapma seçeneğine sahiptir . harici olarak görünürse, aslında hiç çağrılmasa bile, işlevin çağrılabilir olması için bir yol olmalıdır.
uɐɪ

1
@jonk - İyi bir yanıtta belirtmediğiniz başka bir hile, başlatma veya yapılandırmayı genişletilmiş satır içi kod olarak gerçekleştiren basit makro işlevleri yazmaktır. Bu özellikle RAM / yığın / fonksiyon çağrısı derinliğinin sınırlı olduğu çok küçük işlemcilerde kullanışlıdır.
uɐɪ

@ ɐɪouɐɪ Evet, C'deki makroları tartışmayı kaçırdım. Bunlar C ++ 'da kullanımdan kaldırıldı, ancak bu konuda bir tartışma yararlı olabilir. Bu konuda yazmak için yararlı bir şey bulabilirsem, bu konuyu ele alabilirim.
jonk

1
@jonk - İlk cümlene tamamen katılmıyorum. Çok inline static void turnOnFan(void) { PORTAbits &= ~(1<<8); }sayıda yerde denilen bir örnek mükemmel bir adaydır.
Jason S

8

1) Önce okunabilirlik ve bakım kolaylığı için kod. Herhangi bir kod tabanının en önemli özelliği iyi yapılandırılmış olmasıdır. Güzel yazılmış yazılımlar daha az hata yapma eğilimindedir. Birkaç hafta / ay / yıl içinde değişiklik yapmanız gerekebilir ve kodunuzun okunması güzelse çok yardımcı olur. Ya da belki bir başkasının değişiklik yapması gerekir.

2) Bir kez çalışan kodun performansı çok önemli değildir. Performans için değil stil için bakım

3) Sıkı döngülerdeki kodun her şeyden önce doğru olması gerekir. Performans sorunları yaşıyorsanız, kod doğru olduğunda optimizasyon yapın.

4) Optimize etmeniz gerekiyorsa, ölçmeniz gerekir! Düşünmek ya da birisi derleyici sadece bir tavsiye olduğunu söylemek önemli değil static inline. Derleyicinin ne yaptığına bir göz atmalısınız. Ayrıca, satır içi işlemin performansı iyileştirip iyileştirmediğini de ölçmeniz gerekir. Gömülü sistemlerde, kod belleği de oldukça sınırlıdır, çünkü kod boyutunu da ölçmeniz gerekir. Bu, mühendisliği varsayımdan ayıran en önemli kuraldır. Eğer ölçmediyseniz, yardımcı olmadı. Mühendislik ölçüyor. Bilim yazıyor;)


2
Aksi halde mükemmel görevinizle ilgili tek eleştiri nokta 2). Başlatma kodunun performansının alakasız olduğu doğrudur - ancak gömülü bir ortamda boyut önemli olabilir. (Ancak bu, 1. noktayı geçersiz kılmaz; daha önce değil - gerektiğinde boyut için optimize etmeye başlayın)
Martin Bonner, Monica'yı

2
Başlatma kodunun performansı ilk başta ilgisiz olabilir. Düşük güç modu eklediğinizde ve uyandırma olayını işlemek için hızlı bir şekilde kurtarmak istediğinizde, ilgili olur.
berendi -

5

Bir işlev yalnızca bir yerde (diğer işlevin içinde bile) çağrıldığında, derleyici her zaman işlevi gerçekten çağırmak yerine kodu o yere koyar. Eğer fonksiyon birçok yerde çağrılırsa, en azından kod boyutu açısından bir fonksiyon kullanmak mantıklıdır.

Kod derlendikten sonra birden fazla çağrı olmayacaktır, bunun yerine okunabilirlik büyük ölçüde geliştirilecektir.

Ayrıca, örneğin ana c dosyasında olmayan diğer ADC işlevleriyle aynı kütüphanede ADC başlangıç ​​koduna sahip olmak isteyeceksiniz.

Birçok derleyici, hız veya kod boyutu için farklı optimizasyon seviyeleri belirlemenize izin verir, bu nedenle birçok yerde çağrılan küçük bir işleviniz varsa, işlev "satır içi" olur, çağrı yapmak yerine oraya kopyalanır.

Hız optimizasyonu işlevleri olabildiğince fazla sayıda satır içinde sıraya koyar, kod boyutu için optimizasyon işlevi çağırır, ancak bir işlev yalnızca sizin durumunuzda olduğu gibi tek bir yerde çağrıldığında her zaman "satır içi" olur.

Bunun gibi kod:

function_used_just_once{
   code blah blah;
}
main{
  codeblah;
  function_used_just_once();
  code blah blah blah;
{

derlenecekler:

main{
 code blah;
 code blah blah;
 code blah blah blah;
}

herhangi bir arama yapmadan.

Ve sorunuzun cevabı, örneğin veya benzer bir şekilde, kodun okunabilirliği performansı etkilemez, hiçbir şey hız veya kod boyutunda çok fazla değildir. Kodu okunabilir hale getirmek için birden fazla çağrı kullanmak yaygındır, sonunda satır içi kod olarak kabul edilir.

Yukarıdaki ifadelerin Microchip XCxx ücretsiz sürümü gibi amaçlı sakat ücretsiz sürüm derleyicileri için geçerli olmadığını belirtmek için güncelleme. Bu tür bir işlev çağrısı, Microchip'in ücretli sürümün ne kadar daha iyi olduğunu göstermek için bir altın madeni ve bunu derlerseniz, ASM'de tam olarak C kodunda olduğu kadar arama bulacaksınız.

Ayrıca, eğik bir işleve işaretçi kullanmayı bekleyen aptal programcılar için değildir.

Bu, genel C C ++ veya programlama bölümü değil, elektronik bölümdür, soru, herhangi bir iyi derleyicinin varsayılan olarak yukarıdaki optimizasyonu yapacağı mikrodenetleyici programlama ile ilgilidir.

Bu yüzden lütfen oylamayı durdurun, çünkü nadir, olağandışı durumlarda bu doğru olmayabilir.


15
Kodun satır içi olup olmaması derleyici satıcısı uygulamalarına özgü bir sorundur; satır içi anahtar kelimeyi kullanmak satır içi kodu garanti etmez . Derleyiciye bir ipucu. Kesinlikle iyi derleyiciler, eğer onlar hakkında bilmek, sadece bir kez kullanılan fonksiyonları sıralı olacaktır. Yine de, kapsamda herhangi bir "uçucu" nesne varsa bunu yapmaz.
Peter Smith

9
Bu cevap doğru değil. @PeterSmith'in dediği gibi, C dili spesifikasyonuna göre, derleyici kodu satır içine alma seçeneğine sahiptir, ancak olmayabilir ve çoğu durumda bunu yapmayacaktır. Dünyada o kadar çok farklı hedef işlemciler için o kadar çok farklı derleyici var ki, bu cevapta bir çeşit battaniye ifadesi yapmak ve tüm derleyicilerin kodları yalnızca seçilemez bir seçenek olduğunda satır içi yerleştireceğini varsayarsak.
uɐɪ

2
@ ʎəʞouɐɪ Mümkün olmayan nadir vakaları işaret ediyorsunuz ve ilk etapta bir fonksiyon çağırmamak kötü bir fikir olacaktır. OP tarafından verilen basit örnekte gerçekten kullanmak için bu kadar aptal bir derleyici görmedim.
Dorian

6
Bu işlevlerin yalnızca bir kez çağrıldığı durumlarda, işlev çağrısını optimize etmek neredeyse bir sorun değildir. Sistemin kurulum sırasında gerçekten her bir saat döngüsünü geri alması gerekiyor mu? Her yerde optimizasyonda olduğu gibi - okunabilir kod yazın ve yalnızca profil oluşturma gerekli olduğunu gösteriyorsa optimize edin .
Baldrickk

5
@MSalters Derleyicinin burada ne yaptığıyla ilgilenmiyorum - daha fazla programcının buna yaklaşımı. Soruda görüldüğü gibi, başlatmayı bozmaktan hiç veya önemsiz performans isabeti yoktur.
Baldrickk

2

Öncelikle, en iyi ya da en kötü yoktur; hepsi bir fikir meselesi. Bunun verimsiz olduğu konusunda çok haklısınız. Optimize edilebilir veya edilmeyebilir; değişir. Genellikle bu tür işlevleri, saat, GPIO, zamanlayıcı vb. Ayrı dosyalarda / dizinlerde görürsünüz. Derleyiciler genellikle bu boşluklar arasında optimizasyon yapamamıştır. Bildiğim ama böyle şeyler için yaygın olarak kullanılmayan bir tane var.

Tek dosya:

void dummy (unsigned int);

void setCLK()
{
    // Code to set the clock
    dummy(5);
}

void setConfig()
{
    // Code to set the configuration
    dummy(6);
}

void setSomethingElse()
{
   // 1 line code to write something to a register.
    dummy(7);
}

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

Gösterim amacıyla bir hedef ve derleyici seçmek.

Disassembly of section .text:

00000000 <setCLK>:
   0:    e92d4010     push    {r4, lr}
   4:    e3a00005     mov    r0, #5
   8:    ebfffffe     bl    0 <dummy>
   c:    e8bd4010     pop    {r4, lr}
  10:    e12fff1e     bx    lr

00000014 <setConfig>:
  14:    e92d4010     push    {r4, lr}
  18:    e3a00006     mov    r0, #6
  1c:    ebfffffe     bl    0 <dummy>
  20:    e8bd4010     pop    {r4, lr}
  24:    e12fff1e     bx    lr

00000028 <setSomethingElse>:
  28:    e92d4010     push    {r4, lr}
  2c:    e3a00007     mov    r0, #7
  30:    ebfffffe     bl    0 <dummy>
  34:    e8bd4010     pop    {r4, lr}
  38:    e12fff1e     bx    lr

0000003c <initModule>:
  3c:    e92d4010     push    {r4, lr}
  40:    e3a00005     mov    r0, #5
  44:    ebfffffe     bl    0 <dummy>
  48:    e3a00006     mov    r0, #6
  4c:    ebfffffe     bl    0 <dummy>
  50:    e3a00007     mov    r0, #7
  54:    ebfffffe     bl    0 <dummy>
  58:    e8bd4010     pop    {r4, lr}
  5c:    e12fff1e     bx    lr

Buradaki cevapların çoğu size, naif olduğunuzu ve bunların optimize edildiğini ve işlevlerin kaldırıldığını söylüyor. Küresel olarak varsayılan olarak tanımlandıkları için kaldırılmazlar. Bu dosyanın dışında gerekli değilse bunları kaldırabiliriz.

void dummy (unsigned int);

static void setCLK()
{
    // Code to set the clock
    dummy(5);
}

static void setConfig()
{
    // Code to set the configuration
    dummy(6);
}

static void setSomethingElse()
{
   // 1 line code to write something to a register.
    dummy(7);
}

void initModule()
{
   setCLK();
   setConfig();
   setSomethingElse();
}

bunları artık satır içinde olduğu gibi kaldırır.

Disassembly of section .text:

00000000 <initModule>:
   0:    e92d4010     push    {r4, lr}
   4:    e3a00005     mov    r0, #5
   8:    ebfffffe     bl    0 <dummy>
   c:    e3a00006     mov    r0, #6
  10:    ebfffffe     bl    0 <dummy>
  14:    e3a00007     mov    r0, #7
  18:    ebfffffe     bl    0 <dummy>
  1c:    e8bd4010     pop    {r4, lr}
  20:    e12fff1e     bx    lr

Ancak gerçek şu ki, çip satıcısı veya BSP kütüphaneleri aldığınızda,

Disassembly of section .text:

00000000 <_start>:
   0:    e3a0d902     mov    sp, #32768    ; 0x8000
   4:    eb000010     bl    4c <initModule>
   8:    eafffffe     b    8 <_start+0x8>

0000000c <dummy>:
   c:    e12fff1e     bx    lr

00000010 <setCLK>:
  10:    e92d4010     push    {r4, lr}
  14:    e3a00005     mov    r0, #5
  18:    ebfffffb     bl    c <dummy>
  1c:    e8bd4010     pop    {r4, lr}
  20:    e12fff1e     bx    lr

00000024 <setConfig>:
  24:    e92d4010     push    {r4, lr}
  28:    e3a00006     mov    r0, #6
  2c:    ebfffff6     bl    c <dummy>
  30:    e8bd4010     pop    {r4, lr}
  34:    e12fff1e     bx    lr

00000038 <setSomethingElse>:
  38:    e92d4010     push    {r4, lr}
  3c:    e3a00007     mov    r0, #7
  40:    ebfffff1     bl    c <dummy>
  44:    e8bd4010     pop    {r4, lr}
  48:    e12fff1e     bx    lr

0000004c <initModule>:
  4c:    e92d4010     push    {r4, lr}
  50:    ebffffee     bl    10 <setCLK>
  54:    ebfffff2     bl    24 <setConfig>
  58:    ebfffff6     bl    38 <setSomethingElse>
  5c:    e8bd4010     pop    {r4, lr}
  60:    e12fff1e     bx    lr

Kesinlikle performans ve alan için gözle görülür bir maliyeti olan ek yük eklemeye başlayacaksınız. Her bir fonksiyonun ne kadar küçük olduğuna bağlı olarak birkaç ila beşi.

Bu neden yapılır? Bazıları, profesörlerin derecelendirme kodunu kolaylaştırmayı öğreteceği veya hala öğretecekleri kurallar kümesidir. İşlevler bir sayfaya sığmalıdır (çalışmanızı kağıda yazdırdığınızda geri dönün), bunu yapmayın, bunu yapmayın, vb. Birçoğu farklı hedefler için ortak adlara sahip kütüphaneler yapmaktır. Bazıları çevre birimlerini paylaşan ve bazıları aileleri, farklı GPIO'ları, SPI denetleyicilerini, vb. get_timer_count (), vb. Ve bu soyutlamaları farklı çevre birimleri için yeniden kullanın.

Olası bir okunabilirlik ile birlikte çoğunlukla sürdürülebilirlik ve yazılım tasarımının bir örneği haline gelir. Sürdürülebilirlik, okunabilirlik ve performansa sahip olamazsınız; her seferinde yalnızca bir veya iki tane seçebilirsiniz, üçünü de değil.

Bu çok fikir tabanlı bir sorudur ve yukarıdakiler bunun üç önemli yolunu göstermektedir. Hangi yol en iyi olduğu konusunda kesinlikle görüştür. Tüm işleri tek bir işlevde yapmak mı? Fikre dayalı bir soru, bazı insanlar performans için eğiliyor, bazıları modülerliği ve okunabilirlik versiyonlarını BEST olarak tanımlıyor. Birçok insanın okunabilirlik dediği ilginç sorun son derece acı verici; 50-10.000 dosyaya bir kerede açık olması gereken kodu "görmek" ve ne olduğunu görmek için bir şekilde işlevleri sırayla görmeye çalışın. Okunabilirliğin tersini buluyorum, ancak diğerleri her öğe ekrana / editör penceresine sığdığında ve çağrılan işlevleri ezberledikten ve / veya içine ve dışına çıkabilen bir düzenleyiciye sahip olduktan sonra tamamen tüketilebildiğini buluyor bir proje içindeki her fonksiyon.

Çeşitli çözümler gördüğünüzde başka bir büyük faktör budur. Metin editörleri, IDE'ler vb. Çok kişiseldir ve vi ve Emacs'ın ötesine geçer. Programlama verimliliği, kullandığınız araçla rahat ve verimli iseniz, günlük / aylık satır sayısı artar. Aracın özellikleri, kasıtlı olarak veya o aracın hayranlarının nasıl kod yazacağına yönelik olabilir. Sonuç olarak, eğer bir kişi bu kütüphaneleri yazıyorsa, proje bir dereceye kadar bu alışkanlıkları yansıtmaktadır. Bir ekip olsa bile, baş geliştirici veya patronun alışkanlıkları / tercihleri ​​ekibin geri kalanına zorlanabilir.

İçlerinde çok kişisel tercihler gömülü standartlar, yine çok dini vi ve Emacs, sekmeler ve boşluklar, köşeli parantezlerin nasıl sıralandığı vb.

Sizinkini nasıl yazmalısınız? İstediğiniz gibi çalışırsa gerçekten yanlış bir cevap yoktur. Kötü veya riskli bir kod olduğundan emin olun, ancak gerektiği gibi koruyabileceğiniz şekilde yazılırsa, tasarım hedeflerinize ulaşır, okunabilirlikten ve performans önemliyse bazı sürdürülebilirlikten vazgeçir veya tam tersi. Tek bir kod satırının editör penceresinin genişliğine sığması için kısa değişken adlarını sever misiniz? Ya da karışıklığı önlemek için aşırı uzun açıklayıcı isimler, ancak bir sayfada bir satır bulamadığınız için okunabilirlik azalır; şimdi görsel olarak parçalanıyor, akışla uğraşıyor.

Batta ilk kez bir ev koşusuna vuramayacaksınız. Tarzınızı gerçekten tanımlamak onlarca yıl alabilir / almalıdır. Aynı zamanda, o zaman, tarzınız değişebilir, bir süre bir şekilde eğilebilir, sonra başka bir şekilde eğilebilir.

Optimize etmeyin, asla optimize etmeyin ve erken optimizasyon duyacaksınız. Ancak gösterildiği gibi, başlangıçtan itibaren böyle tasarımlar performans sorunları yaratır, daha sonra gerçekleştirmek için yeniden tasarım yapmak yerine bu sorunu çözmek için kesmek görmeye başlarsınız. Durumlar olduğunu kabul ediyorum, tek bir fonksiyon, derleyicinin aksi halde ne yapacağına dair bir korkuya dayanarak derleyiciyi manipüle etmeye çalışabileceğiniz birkaç kod satırı. derleyicinin kodu nasıl derleyeceğini bilerek yazarken optimizasyon), o zaman saldırıdan önce döngü vapurunun gerçekten nerede olduğunu doğrulamak istersiniz.

Ayrıca, kodunuzu bir dereceye kadar kullanıcı için tasarlamanız gerekir. Bu sizin projenizse, tek geliştiricisiniz; ne istersen. Vermek veya satmak için bir kütüphane yapmaya çalışıyorsanız, muhtemelen kodunuzu diğer tüm kütüphanelere, küçük işlevlere sahip yüzlerce ila binlerce dosyaya, uzun işlev adlarına ve uzun değişken adlarına benzetmek istersiniz. Okunabilirlik sorunlarına ve performans sorunlarına rağmen, IMO bu kodu kullanabileceğiniz daha fazla kişi bulacağınızı göreceksiniz.


4
Gerçekten mi? Hangi "bazı hedefleri" ve "bazı derleyicileri" kullanabilirim?
Dorian

Bana 32/64 bit ARM8 gibi görünüyor, belki bir ahududu PI'den sonra normal bir mikrodenetleyiciden. Sorudaki ilk cümleyi okudun mu?
Dorian

Derleyici, kullanılmayan genel işlevleri kaldırmaz, ancak bağlayıcı kaldırır. Düzgün yapılandırılır ve kullanılırsa, yürütülebilir dosyada görünmezler.
berendi -

Birisi hangi derleyicinin dosya boşlukları arasında optimizasyon yapabileceğini merak ederse: IAR derleyicileri çoklu dosya derlemesini (bu şekilde adlandırırlar) destekler, bu da çapraz dosya optimizasyonuna izin verir. Tüm c / cpp dosyalarını tek seferde atarsanız, tek bir işlev içeren bir yürütülebilir dosya ile sonuçlanırsınız: main. Performans avantajları oldukça derin olabilir.
Arsenal

3
@Arsenal Elbette gcc, düzgün çağrıldıklarında derleme birimlerinde bile satır içi desteği destekler. Bkz. Gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html ve -flto seçeneğini arayın.
Peter - Monica'yı yeniden eski haline

1

Çok genel bir kural - derleyici sizden daha iyi optimizasyon yapabilir. Tabii ki, çok döngü yoğun şeyler yapıyorsanız istisnalar vardır, ancak genel olarak hız veya kod boyutu için iyi bir optimizasyon istiyorsanız derleyicinizi akıllıca seçin.


Ne yazık ki bugün çoğu programcı için geçerlidir.
Dorian

0

Elbette kendi kodlama stilinize bağlıdır. Orada genel bir kural, değişken adlarının yanı sıra işlev adlarının olabildiğince açık ve kendi kendini açıklayabilmesidir. Bir fonksiyona ne kadar çok alt arama veya kod satırı koyarsanız, o fonksiyon için net bir görev tanımlamak o kadar zorlaşır. Örneğinizde, işleri başlatan ve alt rutinleri çağıran ve daha sonra saati ayarlayan veya yapılandırmayı ayarlayan bir fonksiyonunuz initModule()var . Bunu sadece işlevin adını okuyarak söyleyebilirsiniz. Doğrudan alt programlardan tüm kodu koyarsanız , işlevin gerçekte ne yaptığı daha az belirginleşir. Ancak sık sık, bu sadece bir kılavuzdur.initModule()


Cevabın için teşekkürler. Performans için gerekirse stil değiştirebilir ama burada soru kodun okunabilirliği performansı etkiler mi?
MaNyYaCk

Bir işlev çağrısı bir çağrı veya jmp komutu ile sonuçlanacaktır, ancak bu bence kaynakların göz ardı edilebilir bir fedakarlığıdır. Tasarım desenleri kullanırsanız, bazen gerçek bir kod parçasına ulaşmadan önce bir düzine fonksiyon çağrısı ile sonuçlanırsınız.
po.pe

@Humpawumpa - Sadece 256 veya 64 bayt RAM'e sahip bir mikrodenetleyici için yazıyorsanız, bir düzine fonksiyon çağrısı katmanı ihmal edilebilir bir fedakarlık değildir, bu mümkün değildir
uɐɪ

Evet, ama bunlar iki uçtur ... genellikle 256 bayttan fazlasınız ve bir düzineden az katman kullanıyorsunuz - umarım.
po.pe

0

Bir işlev gerçekten çok küçük bir şey yaparsa, onu yapmayı düşünün static inline.

C dosyası yerine bir başlık dosyasına ekleyin ve static inlinetanımlamak için kelimeleri kullanın:

static inline void setCLK()
{
    //code to set the clock
}

Şimdi, işlev 3 satırdan fazla olmak gibi biraz daha uzunsa, bunu önlemek static inlineve .c dosyasına eklemek iyi bir fikir olabilir . Sonuçta, gömülü sistemlerin belleği sınırlıdır ve kod boyutunu çok fazla artırmak istemezsiniz.

Ayrıca, işlevi tanımlayıp işlevinden file1.ckullanırsanız file2.c, derleyici işlevi otomatik olarak satır içine almaz. Ancak, bunu file1.hbir static inlineişlev olarak tanımlarsanız , derleyiciniz bunu satır içi yapar.

Bu static inlineişlevler yüksek performanslı programlamada son derece kullanışlıdır. Kod performansını genellikle üçten fazla bir faktörle artırdıklarını gördüm.


"3 satırdan fazla olmak" - satır sayısının bununla hiçbir ilgisi yoktur; satır içi maliyet onunla ilgili her şeye sahiptir. Satır içi için mükemmel olan 20 satırlık işlev ve satırlama için korkunç olan 3 satırlık bir işlev yazabilirim (örn. FunctionB () 3 kez çağıran, functionC () 3 kez çağıran functionB () ve diğer birkaç düzey).
Jason S

Ayrıca, işlevi tanımlayıp işlevinden file1.ckullanırsanız file2.c, derleyici işlevi otomatik olarak satır içine almaz. Yanlış . Bkz. Örneğin -fltogcc veya clang.
berendi -

0

Mikrodenetleyiciler için verimli ve güvenilir kod yazmaya çalışmanın bir zorluğu, kod derleyiciye özgü yönergeler kullanmadıkça veya birçok optimizasyonu devre dışı bırakmadıkça bazı derleyicilerin belirli semantikleri güvenilir bir şekilde işleyememesidir.

Örneğin, interrupt servisi rutini olan tek çekirdekli bir sisteme sahipse [bir kronometre veya başka bir yöntemle çalıştırın]:

volatile uint32_t *magic_write_ptr,magic_write_count;
void handle_interrupt(void)
{
  if (magic_write_count)
  {
    magic_write_count--;
    send_data(*magic_write_ptr++)
  }
}

bir arka plan yazma işlemi başlatmak veya tamamlanmasını beklemek için işlevler yazmak mümkün olmalıdır:

void wait_for_background_write(void)
{
  while(magic_write_count)
    ;
}
void start_background_write(uint32_t *dat, uint32_t count)
{
  wait_for_background_write();
  background_write_ptr = dat;
  background_write_count = count;
}

ve daha sonra bu kodu kullanarak şunu çağırın:

uint32_t buff[16];

... write first set of data into buff
start_background_write(buff, 16);
... do some stuff unrelated to buff
wait_for_background_write();

... write second set of data into buff
start_background_write(buff, 16);
... etc.

Ne yazık ki, tam optimizasyonlar etkinleştirildiğinde, gcc veya clang gibi bir "akıllı" derleyici, ilk yazma dizisinin programın gözlemlenebilir üzerinde herhangi bir etkisi olmayacağına ve böylece optimize edilebileceğine karar verecektir. iccBir kesme ayarlama ve tamamlanmayı bekleyen eylem hem geçici yazma işlemleri hem de geçici okumaları (burada olduğu gibi) içeriyorsa, bunun gibi kaliteli derleyiciler bunu yapmaya daha az eğilimlidir, ancak hedeflenen platform iccgömülü sistemler için çok popüler değildir.

Standart, yukarıdaki yapının ele alınmasının birkaç makul yolu olduğunu düşünerek uygulama kalitesi sorunlarını kasten göz ardı etmektedir:

  1. Yalnızca üst düzey sayı kırma gibi alanlar için amaçlanan kaliteli uygulamalar , bu tür alanlar için yazılan kodun yukarıdaki gibi yapılar içermemesini makul bir şekilde bekleyebilir.

  2. Kaliteli bir uygulama, volatilenesnelere tüm erişimleri , dış dünya tarafından görülebilen herhangi bir nesneye erişebilecek eylemleri tetikleyebilecekmiş gibi ele alabilir.

  3. Gömülü sistemlerin kullanımına yönelik basit ama iyi kalitede bir uygulama, "satır içi" olarak işaretlenmemiş işlevlere yapılan tüm çağrıları volatile, # bölümünde açıklandığı gibi davranmasa bile dış dünyaya maruz kalmış herhangi bir nesneye erişmiş gibi ele alabilir 2.

Standart, yukarıdaki yaklaşımlardan hangisinin kaliteli bir uygulama için en uygun olacağını önermeye ya da "uygun" uygulamaların herhangi bir belirli amaç için kullanılabilecek kadar iyi kalitede olmasını gerektirmeye çalışmamaktadır. Sonuç olarak, gcc veya clang gibi bazı derleyiciler bu kalıbı kullanmak isteyen herhangi bir kodun birçok optimizasyon devre dışı bırakılmış olarak derlenmesini gerektirir.

Bazı durumlarda, G / Ç işlevlerinin ayrı bir derleme biriminde olduğundan ve bir derleyicinin başka bir seçeneğe sahip olmayacağından emin olmak, ancak dış dünyaya maruz kalan herhangi bir keyfi nesne alt kümesine erişebileceklerini varsaymak makul bir en az olabilir. gcc ve clang ile güvenilir bir şekilde çalışacak kod yazmanın kötü yolu. Ancak bu gibi durumlarda amaç, gereksiz bir işlev çağrısının ekstra maliyetinden kaçınmak değil, gerekli semantiği elde etmek için gereksiz olması gereken maliyeti kabul etmektir.


"G / Ç işlevlerinin ayrı bir derleme biriminde olduğundan emin olmak" ... bunun gibi optimizasyon sorunlarını önlemenin kesin bir yolu değildir. En azından LLVM ve ben GCC'nin birçok durumda tüm program optimizasyonunu yapacağına inanıyorum, bu yüzden ayrı bir derleme biriminde olsalar bile IO işlevlerinizi satır içi yapmaya karar verebilirler.
Jules

@Jules: Tüm uygulamalar gömülü yazılım yazmak için uygun değildir. Tüm program optimizasyonunu devre dışı bırakmak, gcc veya clang'ı bu amaca uygun bir kalite uygulaması olarak davranmaya zorlamanın en ucuz yolu olabilir.
supercat

@Jules: Tümleşik optimizasyonu tamamen devre dışı bırakmak zorunda kalmadan (ör. volatileErişimleri potansiyel olarak tetikleyebilecekmiş gibi davranma seçeneğine sahip olarak) gömülü veya sistem programlaması için tasarlanmış daha yüksek kaliteli bir uygulama, bu amaç için uygun semantiklere sahip olacak şekilde yapılandırılabilir olmalıdır. diğer nesnelere keyfi erişim), ancak gcc ve clang her ne sebeple olursa olsun, uygulama kalitesi konularını işe yaramaz bir şekilde davranmaya davet olarak ele almayı tercih eder.
supercat

1
"En kaliteli" uygulamalar bile buggy kodunu düzeltmez. Eğer buffbildirilmedi volatile, bu uçucu değişken olarak ele alınmayacaktır o yeniden düzenlenebilir veya görünüşte daha sonra kullanılmadığı takdirde dışarı tamamen optimize edilebilme erişir. Kural basittir: normal program akışı dışında erişilebilen tüm değişkenleri (derleyici tarafından görüldüğü gibi) olarak işaretleyin volatile. buffErişilen içeriğin bir kesme işleyicisinde var mı? Evet. O zaman olmalı volatile.
berendi -

@berendi: Derleyiciler, Standardın gerektirdiğinin ötesinde garantiler sunabilir ve kaliteli derleyiciler bunu yapar. Gömülü sistemlerin kullanımı için kaliteli ve bağımsız bir uygulama, programcıların muteks yapıları sentezlemelerine izin verecektir, bu da kodun yaptığı şeydir. Zaman magic_write_countsıfırdır, depolama ana hat aittir. Sıfır olmadığında, kesme işleyicisine aittir. Yapımı buffuçucu gerektireceğini kullanmak üzerine faaliyet her fonksiyon her yerde volatileçok daha bir derleyici sahip olmaktan optimizasyonu bozabilecek ulaşım kolaylığı işaretçileri, ...
SuperCat
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.