İçeriğe atla

Döngü açma

Döngü açma (İngilizce: loop unwinding veya loop unrolling), programın çalışmasını hızlandıran döngü dönüştürme yöntemlerinden biridir. Bu yöntem yazılan programın kod satır sayısını artırmaktadır. Döngülerdeki dönüşüm manuel olarak programcı tarafından yapılabileceği gibi kodlar derleyiciler tarafından da düzenlenebilmektedir.

Döngü açmanın amacı, döngülerdeki kontrol buyruklarını (örnek: gösterge aritmetiği ya da "döngü sonu" testleri) azaltarak ya da tamamen ortadan kaldırarak programın çalışmasını hızlandırmaktır. Döngüler, birbirini tekrarlayan ve bağımsız kod satırları eklenerek tekrar yazılır, bunun sonucunda da döngülerde harcanan fazla zaman yükü ortadan kaldırılır.

Yürütme zamanı gösterge aritmetiğinin statik şekilde ortadan kaldırılması

"Sıkı" döngülerdeki fazla zaman yükü, "döngü sonu" testleri ile olabileceği gibi, bir dizinin sonraki elemanına geçmeyi sağlayan gösterge indisini arttırma işlemi (gösterge aritmetiği) sebebiyle olur. Optimizasyon sağlayan bir derleyici ya da çevirici her ayrı ayrı referanslanmış dizi değişkeninin değerini hesapladığı takdirde, kodları direkt olarak makine dili buyruklarına çevirir ve böylece yürütme zamanında fazladan aritmetik operasyon yapılmasına gerek kalmaz.

Avantajlar

  • Yürütülen buyrukların azalmasından kaynaklanan çalışma hızındaki artış, programın uzunluğundaki fazlalaşmadan kaynaklanan yavaşlamayı dengelediği takdirde başarımda büyük ölçüde artış gözlenmektedir.
  • Döngüdeki kod satırlarının birbirinden bağımsız olması durumunda bu satırlar paralel işleme ile çalışabilir.
  • Optimizasyon sağlayan derleyiciler çoğunlukla döngü açma işlemini otomatik olarak ya da isteğe bağlı olarak yapabilmektedir.
  • Derleme zamanında dizideki elemanların sayısı bilinmiyor ise dinamik olarak gerçekleştirimi yapılabilir.

Dezavantajlar

  • Tek iterasyonda geçici değişkenleri saklamak için gereken yazmaç sayısında artış olma ihtimali vardır ki bu durum başarımı etkilemektedir.
  • Program kod uzunluğunda artış olacaktır. Bu durum gömülü sistemler için istenmeyen bir durumdur.
  • Kod uzunluğundaki artış, buyruk önbelleğinde isabetsizliklere yol açabilir ki bu durum başarımı olumsuz etkilemektedir.
  • Optimizasyon yapabilen derleyiciler tarafından yapılmadığı takdirde kodun okunabilirliğinde azalmalar olabilir.

C Dilinde yazılmış basit bir örnek

Aşağıda bir koleksiyondan 100 adet item silen bir bilgisayar programı vardır. Bu normalde delete(item_number) fonksiyonunu çağıran bir for-döngüsü tarafından yapılmaktadır. Eğer programın bu kısmı optimize edilecek olursa ve fazladan oluşan zaman yükü programdaki önemli kaynakları elinde barındırıyorsa, zaman yükü yok edildiğinde bu döngünün açılması programın hızlandırılması için kullanılabilir.

Normal döngü Döngü açıldıktan sonra
 int x;
 for (x = 0; x < 100; x++)
 {
     delete(x);
 }
 //.
 //.
 //.
 //.
 int x; 
 for (x = 0; x < 100; x += 5)
 {
     delete(x);
     delete(x+1);
     delete(x+2);
     delete(x+3);
     delete(x+4);
 }

Bu değişiklik sayesinde yeni programın 100 yerine 20 iterasyon yapması gerekir. Bu sayede atlamaların ve koşullu dallanmaların sadece %20'si yapılacak ve döngüde fazladan oluşan zaman yükü önemli ölçüde azalır. Eğer optimizasyon yapan derleyici otomatik olarak fonksiyonu çağıran satır yerine fonksiyonun içeriğini kullanırsa daha da büyük bir başarım artışı sağlanır. Çünkü 100 adet fonksiyon çağrımı yapan kod satırı da derleyici tarafından silinecektir. İyileştirmeyi en uygun hale getirmek için, açılan döngüde gösterge ile tanımlanmış değişken bulunmaması gerekmektedir. Bu da genellikle indis tabanlı referans yerine, "taban adresi + konum" şeklindeki adresleme ile yapılır.

Öte yandan bu şekilde manuel olarak yapılan döngü açma işlemi kaynak kod boyutunu 3 satırdan 7 satıra çıkarmaktadır. Bu da daha fazla satırın derleyici tarafından kontrol edilmesine ve işlenmesine sebep olacaktır. Derleyici kodu bu şekilde derlemek için daha fazla yazmaç kullanabilir.

WHILE döngülerinin açılması

Örnek bir while döngüsünün sözde-kodu şu şekildedir.

Normal döngü Döngü açıldıktan sonra Açılmamış ama ince ayar yapılmış döngü
  WHILE (condition) DO
         action
  ENDWHILE
.
.
.
.
.
.
  WHILE (condition) DO
         action
         IF NOT(condition) THEN GOTO break
         action
         IF NOT(condition) THEN GOTO break
         action
 ENDWHILE
 LABEL break:
.
 IF (condition) THEN
  REPEAT {
         action
         IF NOT(condition) THEN GOTO break
         action
         IF NOT(condition) THEN GOTO break
          action
         } WHILE (condition)
 LABEL break:

Açılmamış olan döngü daha hızlıdır. Bu kodda ENDWHILE (derlendiğinde döngünün ilk satırına bir "atla" kodu ekler) satırı %66 daha az çalışacaktır.

Daha da iyi olanı ince ayar yapılmış olan kod parçasıdır. Bazı derleyiciler otomatik olarak ince ayarı gerçekleştirir ve koşulsuz olan atlamaları elimine eder.

Dinamik döngü açma

Döngü açmanın faydaları programda kullanılan dizinin büyüklüğüne bağlı olduğu için çoğunlukla yürütme zamanından önce bilinememektedir. JIT (Just-in-time compilation) yapan derleyiciler döngülerin standart bir şekilde çalıştırılmasına veya ayrı ayrı buyrukları sıralayarak farklı bir kod oluşturup döngülerin açılmasına karar verebilirler. Bu esneklik just-in-time derlemenin statik veya manuel olarak döngülerin açılmasına göre büyük üstünlük sağladığını gösterir.

Assembly dili programcıları da verimli dallanma tablolarında kullanılan tekniğe benzer bir teknik kullanarak dinamik döngü açma tekniğinden faydalanabilmektedir. Aşağıdaki örnek IBM 360 veya Z mimarisini kullanan çevirici kodlarıyla yazılmıştır. 256 baytlık ve 50 elemanlı 2 dizi olan FROM ve TO dizilerinde, 'FROM' dizisinin 100 baytını 'TO' dizisine kopyalama işlemi gerçekleştirilmektedir.

Assembler örneği (IBM 360 veya Z Mimarisi)

* initialize all the registers to point to arrays etc  (R14 is return address)
         LM    R15,R2,INIT                       load R15= '16', R0=number in array, R1--> 'FROM array', R2--> 'TO array'
LOOP     EQU   *
         SR    R15,R0                            get 16 minus the number in the array
         BNP   ALL                               if n > 16 need to do all of the sequence, then repeat
* (if # entries = zero, R15 will now still be 16, so all the MVC's will be bypassed)
* calculate an offset (from start of MVC sequence) for unconditional branch to 'unwound' MVC loop
         MH    R15,=AL2(ILEN)                    multiply by length of (MVC..) instruction (=6 in this example)
         B     ALL(R15)                          indexed branch instruction (to MVC with drop through)
* Assignment (move) table - (first entry has maximum allowable offset with single register = X'F00' in this example)
ALL      MVC   15*256(100,R2),15*256(R1)         * move 100 bytes of 16th entry  from array 1 to array 2 (with drop through)
ILEN     EQU   *-ALL                                         length of (MVC...) instruction sequence; in this case =6
         MVC   14*256(100,R2),14*256(R1)         *
         MVC   13*256(100,R2),13*256(R1)         *
         MVC   12*256(100,R2),12*256(R1)         * All 16 of these 'move character' instructions use base plus offset addressing
         MVC   11*256(100,R2),11*256(R1)         * and each to/from offset decreases by the length of one array element (256).
         MVC   10*256(100,R2),10*256(R1)         * This avoids pointer arithmetic being required for each element up to a 
         MVC   09*256(100,R2),09*256(R1)         * maximum permissible offset within the instruction of X'FFF'. The instructions 
         MVC   08*256(100,R2),08*256(R1)         * are in order of decreasing offset, so the first element in the set is moved
         MVC   07*256(100,R2),07*256(R1)         * last.
         MVC   06*256(100,R2),06*256(R1)         *
         MVC   05*256(100,R2),05*256(R1)         *
         MVC   04*256(100,R2),04*256(R1)         *
         MVC   03*256(100,R2),03*256(R1)         *
         MVC   02*256(100,R2),02*256(R1)         *
         MVC   01*256(100,R2),01*256(R1)         move 100 bytes of 2nd entry
         MVC   00*256(100,R2),00*256(R1)         move 100 bytes of 1st entry
*
         S     R0,MAXM1                          reduce Count = remaining entries to process
         BNPR  R14                               ... no more, so return to address in R14
         AH    R1,=AL2(16*256)                   increment 'FROM' register pointer beyond first set
         AH    R2,=AL2(16*256)                   increment 'TO'   register pointer beyond first set
         L     R15,MAXM1                         re-load (maximum MVC's) in R15 (destroyed by calculation earlier)
         B     LOOP                              go and execute loop again
*
* ----- Define static Constants and variables (These could be passed as parameters) ---------------------------------  *
INIT     DS    0A                                4 addresses (pointers) to be pre-loaded with a 'LM' instruction
MAXM1    DC    A(16)                             maximum MVC's 
N        DC    A(50)                             number of actual entries in array (a variable, set elsewhere)
         DC    A(FROM)                           address of start of array 1 ("pointer")
         DC    A(TO)                             address of start of array 2 ("pointer")
* ----- Define static Arrays (These could be dynamically acquired) --------------------------------------------------  *
FROM     DS    50CL256                          array of (max) 50 entries of 256 bytes each
TO       DS    50CL256                          array of (max) 50 entries of 256 bytes each

Bu örnekte, klasik döngü kullanımıyla 50 iterasyonluk bir döngü ile yaklaşık 202 buyruk çalıştırılacakken, dinamik kod ile 89 buyrukta tamamlanabilecektir. Dizi 2 girdi ile yapılsaydı yaklaşık olarak yine normal, açılmamış döngüde harcanan zamana denk olacaktı. Koddaki artış ise 108 bayt kadar olup, binlerce girdi ile denense bile aynı sonucu verecekti. Tabii ki benzer yöntemler birden fazla buyruk işin içine katıldığında da birleşik buyruk uzunluğu ayarlanarak kullanılabilir. Örneğin yukarıdaki kod parçasında kopyalanan 100 bayttan hemen sonra kopyaladığımız kısımları silmek istersek fazladan temizleme buyruğu her MVC buyruğu çağırıldıktan sonra eklenebilir.' XC xx*256+100(156,R1),xx*256+100(R2)'(buradaki 'xx' bir üst satırdaki MVC buyruğundaki değerdir).

C örneği

Aşağıdaki örnek C programlama dilinde yazılmış bir döngü açma programıdır. Yukarıdaki assembler örneğinin aksine, gösterge/indis aritmetiği compiler tarafından kullanılmaktadır. Bunun sebebi dizinin adreslenmesinde kullanılan (i) değişkenidir. Tam optimizasyon için indislerin gerçek değerlerinin (i) değişken değeriyle değiştirilmesi gerekmektedir.

#include<stdio.h> 
#define TOGETHER (8)

int main(void)
{ 
 int i = 0; 
 int entries = 50;                                 /* total number to process     */
 int repeat;                                       /* number of times for while.. */
 int left =0;                                      /* remainder (process later)   */

/* If the number of elements is not be divisible by BLOCKSIZE,                   */ 
/*  get repeat times required to do most processing in the while loop             */

 repeat = (entries / TOGETHER);         /* number of times to repeat   */
 left  =  entries - (repeat * TOGETHER);           /* calculate remainder          */

/* Unroll the loop in 'bunches' of 8                                              */ 
 while( repeat-- > 0 ) 
 { 
    printf("process(%d)\n", i); 
    printf("process(%d)\n", i+1); 
    printf("process(%d)\n", i+2); 
    printf("process(%d)\n", i+3); 
    printf("process(%d)\n", i+4); 
    printf("process(%d)\n", i+5); 
    printf("process(%d)\n", i+6); 
    printf("process(%d)\n", i+7);

    /* update the index by amount processed in one go                            */ 
    i += TOGETHER; 
 } 
/* Use a switch statement to process remaining by jumping to the case label      */ 
/*  at the label that will then drop through to complete the set                 */

    switch (left) 
    { 
        case 7 : printf("process(%d)\n", i +6);           /* process and rely on drop through drop through */
        case 6 : printf("process(%d)\n", i +5); 
        case 5 : printf("process(%d)\n", i +4);  
        case 4 : printf("process(%d)\n", i +3);  
        case 3 : printf("process(%d)\n", i +2); 
        case 2 : printf("process(%d)\n", i +1);          /* two left                                      */
        case 1 : printf("process(%d)\n", i   );          /* just one left to process                      */ 
        case 0 :                                         /* none left                                     */
    }

}

Not

Bu madde, İngilizce Wikipedia'nın loop unwinding sayfasından çevrilmiştir.

İlgili Araştırma Makaleleri

<span class="mw-page-title-main">C (programlama dili)</span> programlama dili

C, yapısal bir programlama dilidir. Bell Laboratuvarları'nda, Ken Thompson ve Dennis Ritchie tarafından UNIX işletim sistemini geliştirebilmek amacıyla B dilinden türetilmiştir. Geliştirilme tarihi 1972 olmasına rağmen yaygınlaşması Brian Kernighan ve Dennis M. Ritchie tarafından yayımlanan "C Programlama Dili" kitabından sonra hızlanmıştır. Günümüzde neredeyse tüm işletim sistemlerinin yapımında %95'lere varan oranda kullanılmış, hâlen daha sistem, sürücü yazılımı, işletim sistemi modülleri ve hız gereken her yerde kullanılan oldukça yaygın ve sınırları belirsiz oldukça keskin bir dildir. Keskinliği, programcıya sonsuz özgürlüğün yanında çok büyük hatalar yapabilme olanağı sağlamasıdır. Programlamanın gelişim süreciyle beraber programlamanın karmaşıklaşması, gereksinimlerin artması ile uygulama programlarında nesne yönelimliliğin ortaya çıkmasından sonra C programcıları büyük ölçüde nesne yönelimliliği destekleyen C++ diline geçmişlerdir.

Komut kümesi mimarisi, CPU'nun yazılım tarafından nasıl kontrol edileceğini tanımlayan bilgisayar soyut modelinin bir parçasıdır. ISA, işlemcinin ne yapabileceğini ve bunu nasıl yapacağını belirterek donanım ve yazılım arasında bir arayüz gibi davranır.

<span class="mw-page-title-main">Ada (programlama dili)</span> programlama dili

Ada, yapısal, statik tipli, zorunlu, geniş spektrumlu ve nesne yönelimli bir üst düzey bilgisayar programlama dilidir. Pascal ve diğer dillerin genişletilmiş halidir. Gömülü design-by-contract (DbC), güçlü yazımı, açık eşzamanlı, senkronize mesaj geçişi, korunmuş objeli ve belirsiz bir dildir. Ada kod güvenliğini ve sürdürebilirliğini derleyicide hataları bularak geliştirdi.

İşlemci önbelleği, CPU'nun hafızadaki verilere ulaşma süresini azaltan bir donanımdır. Ana belleğe(RAM) kıyasla küçük, hızlı ve işlemci çekirdeğine yakındır. Sık kullanılan veriler ya da en güncel veriler işlemci önbelleğinde saklanır. Günümüzde pek çok CPU, birden çok seviyede önbellek içerir, bu önbellekler verilerin yanı sıra komutları da bünyesinde tutar.

ARM mimarisi RISC tabanlı bir işlemci mimarisidir. Genel itibarıyla düşük güç tüketimi, diğer RISC tabanlı işlemcilere göre yüksek performanslı oluşu ve x86-x64 işlemcilere göre daha hesaplı olmasından dolayı gömülü sistemlerde, taşınabilir aygıtlarda kullanılan yongasetlerinin dizaynında tercih edilir. 32 ve 64 bit modelleri bulunur.

<span class="mw-page-title-main">Sayısal kontrol</span> üretim tipi

Sayısal kontrol veya tam ismi ile bilgisayarlı sayısal kontrol, işleme takımlarının ve 3B yazıcıların bir bilgisayar aracılığıyla otomatik olarak kontrol edilerek şekil verilecek iş parçasının üzerinde operasyonda bulunan talaşlı imalat işlemidir. Bir CNC makinesi, kodlar ile programlanmış talimatı takip ederek manuel bir operatöre ihtiyaç duymadan, spesifikasyonları karşılamak için belli bir malzeme bazındaki iş parçasını istenilen şekle gelene kadar, manuelden daha hassas bir şekilde işler.

Boru hattı yöntemi bilgisayar mimarisi ve diğer sayısal ürünlerin tasarımında başarımı artırmak için uygulanan bir yöntemdir. Komutları, boru hattı yöntemi ile işleyip daha kısa süre içinde bitmesini sağlar. Asıl amacı saat sıklığını artırarak başarımı artırmaktır. Farklı kaynakları aynı anda, farklı işler tarafından kullanarak çalışır.

<span class="mw-page-title-main">İterasyon</span>

İterasyon, tekerrür, tekrarlama, yineleme ve mükerrer icrâ, ardışık işlem anlamlarına gelen iterasyon (iteration), programlamada bir dizi işlemin döngüler kullanarak yazılmasıdır.

Dallanma Öngörüsü, bilgisayar mimarisinde çalıştırılacak programın buyruk kümesi içindeki dallanma buyruklarına gelindiğinde koşula göre atlanacağını ya da atlanmayacağını önceden varsayarak veya geçmişine bakıp tahmin ederek öngörüde bulunma işidir. Bugünkü işlemcilerin tasarımında boru hattı (bilgisayar) yöntemi kullanıldığı ve başarım hedeflerinin yüksek olduğu düşünüldüğünde bir dallanmada hangi yöne gidileceğini yüksek doğrulukta tahmin etmek kaçınılmaz olmuştur. Bu öngörü işlemciye dallanmanın sonucunu beklemeden diğer buyrukları işleme imkânı verir. Bu da zamandan kazanç anlamına gelir ki başarımı yükseltir. Bu arada da işlemcinin şimdiki buyruğun işlenmesi bitmeden sonraki buyruğun adresini bilmesi gerekir.

Bilgisayar mimarı Gene Amdahl'ın ismini alan Amdahl Yasası, sistemin bir parçasının hızlandırılması sonucunda, sistemin bir bütün olarak ele alındığında toplam hızlanmasının ne olacağını hesaplamak için kullanılır. Sıklıkla, birden fazla işlemci kullanıldığında erişilebilecek azami hızlanmayı tahmin etmek için paralel hesaplamalarda da kullanılır.

CUDA, GPU için NVIDIA'nın sunduğu C programlama dili üzerinde eklenti olarak kullanıma sunulan bir mimari ve teknolojidir.

Bilgisayar programlamada dinamik iletim, altyordam çağrılarının ilişkin altyordam başlangıç adresine dinamik olarak bağlanmasıdır. Bir diğer deyişle, dinamik iletim program metnindeki bir çağrı ile işletilen altyordamın programın çalışması sırasında birbirine bağlanması durumudur. Geri çağrı ve çokbiçimliliğin realize edilmesinde kullanılan bu bağlama yöntemi, yordamsal programlama dillerinde altyordam göstericileriyle gerçekleştirilirken, nesne yönelimli dillerde kalıtlama ve gerçekleştirme ilişkilerinin kullanılmasıyla otomatikman sağlanır. Altyordamların birinci sınıf dil öğesi olarak ele alındığı fonksiyonel programlama dillerinde ise, aynı işlevsellik altyordamların argüman olarak geçirilmesi ile sağlanabilir.

<span class="mw-page-title-main">OpenMP</span>

OpenMP; Solaris, IBM AIX, HP-UX, GNU/Linux, MAC OS X ve Windows işletim sistemleri üzerinde çoğu işlemci mimarisi üzerinde Fortran, C++, C programlama dillerinde çoklu platform paylaşımlı bellek çoklu işlemeyi destekleyen bir uygulama geliştirme arayüzüdür, yani bir API'dir. OpenMP derleyici yönergelerinin kütüphane rutinlerini ve ortam değişkenlerinin çalışma zamanı davranışını etkileyen bir kümesini içerir.

SHA-2, ABD Ulusal Güvenlik Ajansı (NSA) tarafından tasarlanmış kriptografik özet (hash) fonksiyonları kümesidir. Kriptografik özet fonksiyonları, hesaplanmış “özet” ile bilinen ve beklenen özet değerinin karşılaştırılmasıyla, dijital veri üzerinde yürüyen matematiksel operasyonlardır. Özet fonksiyonları ile bir kişi verinin bütünlüğüne karar verebilir. Örneğin, yüklenmiş bir dosyanın özet değerini hesaplamak ve sonucu önceden açıklanmış özet sonucu ile karşılaştırmak, yüklemenin değiştirilip değiştirilmediğini veya üzerinde oynama yapılıp yapılmadığını gösterebilir. Kriptografik Hash fonksiyonlarının kilit noktası çakışma dirençleridir: hiç kimse aynı özet çıktısı veren iki farklı girdi bulamamalıdır.

Argon2, Temmuz 2015'te Parola Özetleme Yarışmasının galibi olarak seçilen anahtar türetme fonksiyonudur. Lüksemburg Üniversitesi'nden Alex Biryukov, Daniel Dinu ve Dmitry Khovratovich tarafından tasarlanmıştır. Argon2'nin referans uygulaması, CC0 lisansı veya Apache License 2.0 altında yayınlanmış ve üç ilgili sürüm sağlamıştır:

Kriptografide, scrypt, Colin Percival tarafından Tarsnap çevrimiçi yedekleme hizmeti için oluşturulan bir parola tabanlı anahtar türetme fonksiyonudur. Bu algoritma, büyük miktarda bellek gerektirerek büyük ölçekli özel donanım saldırılarını gerçekleştirmeyi pahalı hale getirmek için özel olarak tasarlanmıştır. 2016 yılında, scrypt algoritması IETF tarafından RFC 7914 olarak yayınlandı. Scrypt algoritmasının, ArtForz kullanıcı adına sahip ve gerçek adı bilinmeyen bir programcı tarafından implemente edilmiş, basitleştirilmiş bir sürümü, önce Tenebrix'te ve ardından Fairbrix ve Litecoin olmak üzere bir dizi kripto para birimi tarafından iş kanıtı şeması olarak kullanıldı.

Bcrypt, Niels Provos ve David Mazières tarafından Blowfish şifreleme yöntemi esas alınarak geliştirilmiş ve ilk kez USENIX’te, 1999 yılında sunulmuş bir parola özet fonksiyonudur. Rainbow table saldırılarına karşı salt kullanmasının yanı sıra adaptif bir fonksiyon olma özelliğine sahiptir. İterasyon sayacı arttırılarak yavaşlatılabilir ve bu sayede kaba kuvvet saldırılarına karşı dirençli kalabilmektedir.

Kod enjeksiyonu, geçersiz verilerin işlenmesinden kaynaklanan bilgisayar hatasından yararlanmadır. Enjeksiyon, saldırgan tarafından savunmasız bir bilgisayar programına kod enjekte etmek ve yürütmenin seyrini değiştirmek için kullanılır. Başarılı kod eklemenin sonucu felaket olabilir. Örneğin, bilgisayar virüslerinin ya da solucanların yayılmasına izin verilebilir.

<span class="mw-page-title-main">Kernel panic</span>

Bir kernel panic, bir işletim sisteminin çekirdeği tarafından, güvenli bir şekilde kurtarılamadığı veya sistemi çalıştırmaya devam etmenin büyük veri kaybı yaşanabilmesinin yüksek bir riske sahip olacağı dahili bir kritik hata tespit edildiğinde alınan bir güvenlik önlemidir. Terim büyük ölçüde Unix ve Unix benzeri sistemlere özgüdür. Microsoft Windows işletim sistemlerindeki eşdeğeri, genellikle "mavi ekran" olarak adlandırılan bir durdurma hatasıdır.

Lineer cebirde bir matris, Gauss eliminasyonunun sonucu olan şekle sahipse eşelon biçimindedir.