Performans

Build Etme Zamanı

Mümkünse Ön Deklerasyon Kullan

Bu:

// bir header dosyası
class MyClass;

void doSomething(const MyClass &);

bu değil:

// bir header dosyası
#include "MyClass.hpp"

void doSomething(const MyClass &);

Bu template'ler için de geçerli:

template<typename T> class MyTemplatedType;

Bu proaktif yaklaşım derleme zamanını ve bağımlılıkların yeniden inşasını azaltır.

Not: ön deklerasyon (forward declaration) daha fazla inline etme ve optimisyon yapılmasını önler. Yayın inşaları (release build) için Linkleme Zamanı Optimizasyonu veya Linkleme Zamanı Kod Üretimi kullanılması tavsiye olunur.

Template'lere Gereksiz Yere Örnek Oluşturtmaktan Kaçın

Template'lerin örnek (instantiate) oluşturmasının bir bedeli var. Çok fazla template'in örnek oluşturması veya gereğinden fazla koda sahip template'ler derlenen kod boyunu ve build zamanını artırır.

Daha fazla örnek için bu makaleye bakınız.

Rekursif (özyinelemeli) Template Örneklemelerinden Kaçın

Rekursif template örneklemeleri derleyiciye çok fazla yük oluşturur ve kodun anlaşılmasını zorlaştırır.

Bunun yerine mümkünse variadic expansion'ları ve fold'ları düşünmelisin.

Build'i Analiz Et

Templight aracını projenin build zamanını analiz etmede kullanabilirsin. Bu biraz çaba gerektirir ama bir kere yaptığın zaman anında clang++'ın yerine geçecek.

Templight kullanarak build ettikten sonra sonuçları analiz etmen gerekecek. templight-tools projesi çeşitli metodlar sunuyor. (Yazarın Notu: callgrind converter kullanmanı ve sonuçları kcachegrind ile görselleştirmeni öneririm.)

Sık Değişen Header Dosyalarını Engelle

Header Dosyalarını Gereksiz Yere Include Etme

Derleyici her include direktifi gördüğünde birşeyler yapmak zorunda. #ifndef include korumasını görür görmez dursa bile, hala dosyayı açmak ve işlemek zorunda.

include-what-you-use, hangi header'lara ihtiyacın olduğunu anlamana yardımcı olabilecek bir araçtır.

Önişlemcinin (preprocessor) yükünü azalt

Bu "Sık Değişen Header Dosyalarını Engelle" ve "Header Dosyalarını Gereksiz Yere Include Etme"nin genelleştirilmiş bir formu. BOOST_PP gibi araçlar oldukça yardımcı olabilir ama onlar da önişlemci üzerine büyük bir yük koyar.

Önderlenmiş (precompiled) header'ları kullanmayı düşün

Büyük projelerde önderlenmiş header'ların kullanımı derleme zamanını büyük ölçüde azaltabilir. Seçilmiş header dosyaları bir ara forma (PCH dosyaları) ve bu dosyalar derleyici tarafından daha hızlı işlenir. Derleme zamanının azaltılması için yalnızca sık kullanılan ama çoğu zaman da değişmeyen (system ve kütüphane header'ları) headerların seçilmesi tavsiye edilir.

Ama unulmaması gerekir ki, önderlenmiş header'lar bazı dezavantajlara sahiptir:

  • Önderlernmiş header'lar taşınabilir değildir.
  • Oluşturulan PCH dosyaları makine bağımlıdır.
  • Oluşturulan PCH dosyalarının boyu oldukça büyük olabilir.
  • PCH dosyaları header bağımlılıklarını kırabilir. Çünkü her dosyanın önderlenmiş header'a dahil edilmiş header'ı include etme olasılığı vardır. Sonuçta bu olabilir, önişlenmiş header'ları devre dışı bırakırsan buıild başarısız olabilir. Eğer kütüphane gibi bir şey oluşturuyorsan bu bir sorun olabilir. Bu yüzden bir kere önderlenmiş dosyalar aktifken, bir kere de onlar olmadan build etmen önerilir.

Önderlenmiş header'lar GCC, Clang ve Visual Studio gibi yaygın derleyiciler tarafından desteklenir. cotire (bir cmake eklentisi) gibi araçlar önderlenmiş dosyaları build sistemine eklemene yardımcı olabilir.

Araçları Kullanmayı Düşün

Bunlar iyi bir tasarımın yerine geçmez

  • ccache, unix-like işletim sistemleri için derleme sonuçlarını cache'leme
  • clcache, (MSVC) cl.exe derleme sonuçlarının cache'leme
  • warp, Facebook's önişlemcisi

Ramdisk'e tmp koy

Daha fazla detay için bu YouTube videosuna bakın.

Gold linker'i kullan

Linux ortamında çalışıyorsan, GCC için gold linker kullanmayı düşün.

Çalışma Zamanı (Runtime)

Kodu Analiz Et!

Kodu analiz etmeden darboğazın nerde olduğunu anlamanın hiçbir gerçek yolu yok.

Kodu Sadeleştir

Daha temiz, daha basit ve daha kolay okunan kodun derleyeci tarafından iyi implemente edilme şansı daha yüksektir.

Initializer List Kullan

// Bu
std::vector<ModelObject> mos{mo1, mo2};

// -veya-
auto mos = std::vector<ModelObject>{mo1, mo2};
// Bunu yapma
std::vector<ModelObject> mos;
mos.push_back(mo1);
mos.push_back(mo2);

Initializer list'ler önemli ölçüde daha etkili; nesne kopyalarını ve tutucunun (container) yeniden boyutlandırılmasını azaltır.

Geçici Nesneleri Azalt

// Bunun yerine
auto mo1 = getSomeModelObject();
auto mo2 = getAnotherModelObject();

doSomething(mo1, mo2);
// bunu kullanmayı düşün:

doSomething(getSomeModelObject(), getAnotherModelObject());

Bu türden bir kod, derleyicinin taşıma operasyonu yapmasını engeller...

Taşıma operasyonlarını kullan

Taşıma operasyonları C++11'in en çok satılan özelliklerinden biri. Bu özellik, belli durumlarda geçici nesneleri kopyalamak yerine taşıyarak fazladan kopya yapılmasını önler.

Belli kodlama seçimlerinde (kendi destructor veya atama operatörünü veya kopyalama constructor'ımızı tanımlamak gibi) derleyicinin taşıma (move constructor) constructor'ı oluşturmasını önleriz.

Pek çok kod için basit bir

ModelObject(ModelObject &&) = default;

yeterli olacaktır. Ancak MSVC2013 bu kodu henüz beğenmiyor.

shared_ptr Kopyalarını Yok Et

shared_ptr nesnelerini kopyalamak düşündüğünden çok daha maliyetli olabilir. Çünkü referans sayma atomic ve thread-safe olmalıdır. Bu yüzden bu yorum yukardaki notu tekrar ediyor: geçici nesneler ve fazla sayıda kopya üretmekten kaçın. pImpl kullanıyor olmamız, kopyaların bedava olduğu anlamına gelmiyor.

Kopyaları ve Yeniden Atamaları Mümkün Olduğunca Azalt

Basit durumlar için ternary operator kullanılabilir:

// Kotu fikir
std::string somevalue;

if (caseA) {
  somevalue = "Value A";
} else {
  somevalue = "Value B";
}
// Daha iyi bir fikir
const std::string somevalue = caseA ? "Value A" : "Value B";

Daha karmaşık durumlar anında çağrılan lambda ile kolaylaştırılabilir.

// Kotu fikir
std::string somevalue;

if (caseA) {
  somevalue = "Value A";
} else if(caseB) {
  somevalue = "Value B";
} else {
  somevalue = "Value C";
}
// Daha iyi bir fikir
const std::string somevalue = [&](){
    if (caseA) {
      return "Value A";
    } else if (caseB) {
      return "Value B";
    } else {
      return "Value C";
    }
  }();

Fazladan Exception Kullanmaktan Kaçın

Exception'lar (istisnai durumlar) normal işlenme sırasında program içinde fırlatılır ve yakalanır, bu da uygulamanın çalışmasını yavaşlatır. Exceptionlar debugger içindeyken kullanıcı deneyimini de yok eder, debugger her bir exception olayını izler ve raporlar. Mümkünse dahili exception işlemekten kaçınmak en iyisidir.

"new"lerden Kurtul

Ham hafıza erişim yapmamanız gerektiğini zaten biliyoruz, bu yüzden onların yerine unique_ptr ve shared_ptr kullanıyoruz değil mi? Heap'ten yapılan yer ayırmalar, stack'ten yapılanlardan çok daha maliyetlidir, ama bazen onları kullanmamız gerekir. Meseleyi daha kötü yapansa shared_ptr oluşturmak aslında heap'ten 2 kere yer ayırmayı gerektirmesidir.

Ancak, make_shared fonksiyonu bunu 1'e indirir.

std::shared_ptr<ModelObject_Impl>(new ModelObject_Impl());

// bu şekide olmalı
std::make_shared<ModelObject_Impl>(); // (Bu ayrıca daha kısa ve okunaklı)

unique_ptr Kullanmayı shared_ptr'a Terchi Et

Mümkünse shared_ptr yerine unique_ptr kullan. unique_ptr kopyalarını takip etmek zorunda değildir, çünkü kopyalanabilir değildir. Bu nedenle shared_ptr'den daha etkilidir. shared_ptr ve make_shared'a benzer şekilde , unique_ptr oluştururken make_unique (C++14 veya yukarısı) kullanmalısın:

std::make_unique<ModelObject_Impl>();

Şu anki en iyi uygulama, factory fonksiyonlardan unique_ptr döndürmeyi, sonra gerekliyse unique_ptrshared_ptr'a dönüştürmeyi öneriyor.

std::unique_ptr<ModelObject_Impl> factory();

auto shared = std::shared_ptr<ModelObject_Impl>(factory());

std::endl'lardan kurtul

std::endl fush operasyonu gerçekleştirir ve aslında şuna eşdeğerdir "\n" << std::flush.

Değişken Scope'unu Kısıtla

Değişkenler olabildiğince geç, ideal olarak yalnızca nesneye ilk değer atanırken, tanımlanmalı. Azaltılmış değişken scope'u daha az hafıza kullanımına, genel olarak daha etkili koda ve derleyicinin kodu daha iyi optimize etmesine yardımcı olur.

// Iyi Fikir
for (int i = 0; i < 15; ++i)
{
  MyObject obj(i);
  // obj ile birşeyler yap
}

// Kotu Fikir
MyObject obj; // anlamsız nesne tanımlaması
for (int i = 0; i < 15; ++i)
{
  obj = MyObject(i); // gereksiz atama operasyonu
  // obj ile birşeyler yap
}
// obj gereksiz yere hala hafızada yer kaplıyor

C++17'den itibaren, if ve switch içinde tanımlamaları kullanmayı düşünebilirsin:

if (MyObject obj(index); obj.good()) {
    // do something if obj is good
} else {
    // do something if obj is not good
}

Bu konuyla ilgili bir tartışma.

doublefloat'a Tercih Et, Ama Önce Test Et

Duruma ve derleyicinin optimize etme yeteneğine bağlı olarak, biri diğerinden daha hızlı olabilir. float seçmek daha az kesinliğe ve dönüşümden ötürü yavaşlığa neden olacaktır. Vektörize operasyonlarda float hassasiyet (precision) feda edilmesi uğruna, daha hızlı olabilir.

double, C++'ta kayar noktalı (floating point) değerler için varsayılan tip olduğundan kullanılması tavsiye edilir.

Daha fazla bilgi için stackoverflow'daki tartışma.

++i kullanmayı i++ kullanmaya tercih et

... semantik olarak doğru olduğu durumlarda. Önartırma, sonartırmadan daha hızlıdır çünkü oluşturulacak nesnenin kopyalanmasını gerektirmez.

// Kotu Fikir
for (int i = 0; i < 15; i++)
{
  std::cout << i << '\n';
}

// Iyi Fikir
for (int i = 0; i < 15; ++i)
{
  std::cout << i << '\n';
}

Modern derleyiciler bu iki döngüyü aynı assembly koduna optimize edecek olsa bile, ++i kullanmak hala iyi bir uygulamadır. Bunu yapmamak için kesinlikle bir sebep yok ve derleyicinin kodu optimize edeceğinden hiçbir zaman emin olamazsın. Sonuç olarak semantik olarak aynı olduğunda önartırmayı kullanmak daha kolaydır ve tavsiye edilir.

Char char'dır, string string'dir

// Kotu Fikir
std::cout << someThing() << "\n";

// Iyi Fikir
std::cout << someThing() << '\n';

Bu küçük bir fark ama bir "\n", derleyici tarafında const char * olarak parse edilmek zorunda, yani stream'e yazılmak istendiğinde (veya bir string'e eklenmek istendiğinde) \0 için aralık kontrolü yapılmasını gerektirir. \n tek bir karakterdir ve çoğu CPU komutunun kullanılmasını gerektirmez.

Verimsiz bir şekilde çok defa kullanılırsa, performansınız üzerinde bir etkisi olabilir, ancak daha da önemlisi bu iki kullanım durumu hakkında düşünürseniz, derleyici ve çalışma zamanının kodunuzu çalıştırmak için ne yapması gerektiği hakkında daha fazla fikir edinirsiniz.

Asla std::bind Kullanma

std::bind neredeyse her zaman ihtiyacından çok daha fazla (hem derleme hem de çalışma zamanında) yüke sahiptir. Onun yerinde basitçe lambda kullan.

// Kotu Fikir
auto f = std::bind(&my_function, "hello", std::placeholders::_1);
f("world");

// Iyi Fikir
auto f = [](const std::string &s) { return my_function("hello", s); };
f("world");

Standard Kütüphane'yi Öğren

Üreticinin sunduğu standard kütüphanenin, zaten çok iyi optimize edilmiş bileşenlerini uygun bir biçimde kullanmayı öğren.

in_place_t Ve İlgili Tagler

std::tuple, std::any ve std::variantgibi nesnelerin etkili şekilde oluşturmak için in_place_t ve ilgili taglarin nasıl kullanıldığından haberdar ol.

results matching ""

    No results matching ""