|
Kod Optimizasyonu ve Taşınabilir Programlar Üretmek |
|
Gönderiliyor lütfen bekleyin... |
|
|
Bu yazımızda programlarımızı daha etkin hale nasıl getirebileceğimizle ilgili birkaç konuya değineceğiz.
İlk önce programımızın verimliliğini etkileyen faktörleri belirtirsek, kullanılan bellek miktarı, diskte kullandığı alan,
CPU’nun kullanım yüzdesi gibi etkenlerdir. Bu faktörlerin programdan programa göre değişebileceği, bazı etkenlerin de
göz ardı edilebileceğini belirtmemiz gerekli. Örneğin, Word programını göz önüne alalım bir de sisteme yönelik olan bir ağ
programını.İkisinden de aynı performansı ya da programlama tekniğini beklemek pek uygun olmaz. Word programında
bazen kullanıcıya kolaylık sağlamak açısından hızdan ödün verilebilir ama bir ağ programında bunu beklemek yanlış olur.
Düşünün ki sizin yazdığınız program sisteme yönelik ve bir fonksiyon 2000 defa çağrılacak. Bu durumda 0.01 saniyelik
bir gecikmenin bile hıza ne kadar etkisi olduğunu açıkça görebiliriz.
Programımızı hangi yönden optimize ediyor olursak olalım,elimizde öyle esnek bir dil var ki optimizasyonları en etkin
biçimde yapmamıza olanak sağlayacaktır. Tabi ki C dilinden bahsediyoruz. Şimdi programımızı nasıl optimize edebileceğimize
yönelik kullanılan en yaygın yöntemleri beraberce inceleyelim.
Artırma ve Eksiltme operatörleri
Birçok sitede bu konular ile ilgili makalelere baktığımızda ilk olarak bu konuya değinildiğini göreceksiniz.
Bu operatörlerin doğru kullanılması derleyicilerin daha etkin kod üretmesine katkı sağlayabilir. Örneğin;
1. Yol
a = a + b;
b = b + 1;
2. Yol
a = a + b++;
|
Yukardaki iki örnekte de a ya a+b nin değeri atıldıktan sonra b’nin değeri 1 artırılır. Ama dikkat edersek,
1. yolda b iki defa registerda depolanacaktır (ilki toplama ikincisi artırma için).Ama ikinci yolda b sadece 1
defa registerda depolanacaktır.Bu hem hız konusunda etkili olacak aynı zamanda programımızın boyutunu
azaltacaktır.
register Değişkenleri
Kodumuz hızlandırmak için en iyi yöntemlerden birisi de register değişkenlerini kullanmaktır.
Özellikle en uygun kullanım alanı döngü değişkenleri içindir. Daha önceden de bildiğimiz gibi
register anahtar kelimesi, bir değişkenin CPU nun erişmesini en kısa süreye indirecek
şekilde depolanması için kullanırız.
for (i = 0; i < 10000; ++i)
{
/* ................ */
}
|
Şimdi yukardaki örneği incelersek, döngünün her tekrarında i nin değerine bakılır, eğer 10000 den kucukse artırılır vs vs..
Bu işlemin daha hızlı olması programın performansını olumlu etkileyeceği çok açıktır. Eğer bu döngü içerisinde bir değişken
kullanıyorsak bununda register niteleyicisi ile depolamak iyi bir yöntem olacaktır. İstediğimiz sayıda değişkeni register niteleyicisi
kullanarak tanımlayabileceğimiz biliyoruz. Ama derleyici çok sayıda register ile nitelediğimiz değişkenleri göz ardı eder. Bu yüzden
optimize edeceğimiz değişkenleri seçerken dikkatli olmalıyız.
Pointerlar ve Dizi İndeksleme
Bir çok defa programlarımızda dizi elemanlarına erişirken indeksleyici değişken kullanırız. Ama bazı durumlarda
bu değişken yerine pointer aritmetiğini kullanmak daha etkin bir çözüm sağlayacaktır. Örneğin;
for(;;) for(;;)
{ {
eleman = dizi[ind++]; eleman = *(p++); /* p nin bir diziyi gösterdiğini düşünelim p = dizi; gibi ...*/
} }
|
Pointer aritmetiğini kullandığmızda, p ye bir defa dizinin adresi atanacak sonraki her döngüde sadece bir atırım gerçekleştirilecektir.
Fakat indeksleyici kullandığımızda ind in değerine bakılacak daha sonra ind in değerine göre dizi indeksi hesaplanacaktır ki bu pointer
aritmetiğine göre daha karmaşık işlemler demektir. Bazen kodlarımızın anlaşılmasında ziyade hızlı olması daha iyidir.
Fonksiyonlar
Yapısal programlamanın en temel yapıtaşları fonksiyonlardır. Bu bakımdan C nin en güçlü tarafı da bağımsız fonksiyonları esnek bir şekilde
kullanabilme becerisidir. Gelin foksiyonların, kodlarımızın büyüklüğü ve hızındaki etkilerini inceleyelim. Bir derleyici bir fonksiyon ile karşılaşınca,
eğer fonsksiyonun parametreleri varsa parametreleri, geri dönüş değerini ve fonksiyon içinde kullandığımız yerel değişkenleri yığında (stack) depolar.
Biz fonksiyonu çağırdığımızda bütün bu depolanan bilgiler tekarar yığından geri alınır. Bu işlemler tabiki zaman almaktadır öyleki bazen de tahminimizden
çok daha fazla zaman almaktadır. Şimdi bir örnekle konuyu daha da açalım.
for(x = 1; x < 100; ++i) for(x = 1; x < 100; ++x)
{ {
y = hesapla(x); y = fabs(sin(x) / 100 / 3.1416);
} }
double hesapla(int x)
{
return fabs(sin(x) / 100 / 3.1416);
}
|
İki örneğimiz aynı işlemi yapsa da ikinci örnek daha hızlı çalışacaktır. Nedeni ise yukarda açıklandığı gibi, değişkenlerin yığına kopyalanamsı,
daha sonra fonksiyon çağırılırken bunların tekrar yığında çekilmesi zaman alacak işlemlerdir. İkinci örnekte yerelde değişkenler kullanılarak
zaman alan işlemlerden kaçınılmıştır.Peki bu özelliği ne zaman kullanmamız gerekir. Zamanın ve hızın önemli olduğu uygulamalarımızda kullanmamız
doğru olacaktır. Örneğin ağır matematik işlemleri yapan bir programda hız bizim için önemlidir. Ama unutulmamalıdır ki fonksiyonların kullanılış amacı
hatta icat edilmesinin nedeni bellekten maksimum düzeyde yararlanılmasıdır.Bu özelliği kullandığımızda programımızın büyüyeceğimi farketmişsinizdir.
O zaman şöyle bir genelleme doğru olacaktır; eğer bir programı hızlandırmak istiyorsak kodumuzu büyütecek, eğer kodumuzun küçük olmasını
istiyor isek programımızı yavaş olmasına gözyumacağız. Bu durumda en etkin yöntem, eğer C99 standartlarını destekleyen bir derleyici kullanıyorsak
inline anahtar
kelimesini kullanarak fonksiyonlarımızı inline olarak tanımlamak, eğer C99 destekelemeyen bir derleyici kullanıyor isek benzer sonuçlar elde
edecek şekilde makrolar tasarlayarak kullanmaktır. Ama unutulmamalıdır ki makroları kullanmak görünmez hatalara yol açabilir.
Taşınabilirlik
Kim istemez ki yazdığımız kodların başka başka işletim sistemlerinde derlenerek o sistemde de kullanılmasını.
Belki de birkaç projede bu özellik sizden istenmiştir. Peki yazdığımız kodların taşınabilir olması için neler
yapabiliriz. Öncelikle taşınabilirlik nedir onu inceleyelim. Bir makinada yazılan bir programın farklı bir işlemciye
yada farklı bir işletim sistemine ait ya da herikisi de farklı olan başka bir makinaya kolaylıkla taşınabilime özelliğine
taşınabilirlik denir.Kodlarımızda işletim sistemine bağımlı ya da işlemciye yönelik kodlar içermesi taşınabilrliği olumsuz
yönde etkileyecektir.Her zamanki gibi C nin esnekliği ve gücü ile taşınabilir kod üretmek mümkündür. Şimdi bir kaç önemli
özelliğe değinip çözüm yollarını inceleyelim.
#define Kullanımı
Belki de taşınabilirliğin en etkili yöntemi #define kullanmaktır. Sistemden sisteme değişen ne varsa - disk için tampon büyüklüğü,
ekran yada klavye özellikleri, bellek ayırma bilgileri- #define kullanarak tanımlayabilir ve kodlarımız başka bir sisteme taşındığında
küçük değişikliklerle özel değerleri tanımlayabiliriz. Örnek olarak fread(...) fonksiyonunu inceleyelim.
fread(buf, 256, 1, fp);
Şimdi bu koddaki sorun 256 değerinin fread kodunun içine gömülmüş olmasıdır. Kodumuzu başka bir sisteme taşıdığımızda
256 değeri bu sistem için büyük bir değer ya da çok küçük bir değer olabilir. Fakat
#define MAX_BUF_SIZE 256
fread(buf, MAX_BUF_SIZE, 1, fp);
şeklinde yazdığımız kodu başka bir sisteme taşırken sadece #define bildirimini değiştirmemiz işimizi kolaylaştırmanın yanı sıra
düzenleme hatalarını da en az düzeye indirir. Burda yapılan bir değişiklik ile MAX_BUF_SIZE a yapılan bütün referansların da
otomatik olarak düzeltilmesini sağlar.
İşletim Sistemine Bağımlılık
Sanırım birçoğumuz programlarımızı yazarken sistem özelliklerini içeren ve sisteme özel kodlar kullanırız.Örneğin
Windows 2000 için bir program geliştirdiğimizde, işletim sistemine özel API ler veya sadece NT sistemlerde
desteklenen özellikleri kullandığımızı düşünelim. Bu programı Win9x yada 16 bit windows sürümlerinde çalıştırmamız
olanaksızdır. Bu yüzden işletim sistemine olan bağımlılık programlarımızın taşınamaz olmasına neden olur.
Bu bağımlılığı en aza indirebilmek için çok geçerli bir yöntem olmamasına karşın, programımızın uygulamalara yönelik kısmı
ile sistemem bağlı olan kısmı ayırarak, kodumuz yeni bir ortama taşındığında sadece arabirim mödüllerini değiştirerek
bir nebze de olsa taşınabilirliği sağlayabiliriz.
Veri Büyüklüklerindeki Farklılıklar
Taşınabilir kodlar yazmak istiyorsak şunu aklımızdan çıkarmamalıyız; Sistemden sisteme değişkenlerin boyutları farlıdır.
Bu yüzden asla varsayınlarda bulunmamalıyız. Örneğin "hmm ben Xp deyim, hmm int boyutu ne kadar 32 bit hmm tamam",
diye varsayımlar taşınabilirliği bozacaktır. Şöyle düşünürsek programı 32 bitlik bir sistemde yazıyorsunuz ve bunu 16 bitlik
bir sisteme taşıyacaksınız. Biliyoruz ki 16 bitlik sistemde int 16 bittir. Tabiki bu durum taşınabilirliği etkileyecektir. Bu durumlarda
verinin büyüklüğüne bağımlılığı önlemek için sizeof operatörünü kullanabiliriz. Böylece kodumuzun taşındığı sistemde verilerimizin
büyüklüğünün ne olduğunu bilememiz gerekmez. Örneğin;
fwrite(&val, sizeof(int), fp);
kodu ile her sistemde bir int değeri bir dosyaya yazacaktır. Ama sizeof(int) yerine 4 yazsa idik bu kod 16 bitlik sistemde hatalı
çalışacaktı.
Sonuç olarak programlarımızı yazarken biraz daha geniş düşünmeliyiz. Elimizden geldiği kadar taşınabilir olmasına özen göstermemiz,
programımızın amacına göre hız ya da kod optimizasyonu yapmamız daha profesyonel yazılımlar üretmemizi sağlayacaktır.
Makale:
Kod Optimizasyonu ve Taşınabilir Programlar Üretmek C ve Sistem Programlama Oğuz Yağmur
|
|
|
-
-
Eklenen Son 10
-
Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor
|
|