X86 montajında ​​büyüklük sırasını hesaplamanın en hızlı yolu


12

Görev basittir: Mümkün olduğunca az saat döngüsü kullanarak bir tamsayının büyüklük sırasını hesaplayan bir montaj yazın.

  • Büyüklük sırası log10değil olarak tanımlanır log2.
  • Geçerli giriş aralığıdır 0için , kapsayıcı. Bu aralığın dışındaki girdiler için davranış tanımlanmamış.1012
  • Değerler verilen giriş edilmesi dışında, en yakın tam sayıya yuvarlanır edilmelidir 0çıktı olmalıdır 0. (Bunu, imzasız tamsayılarda mümkün olan negatif sonsuzluğun en iyi temsili olarak düşünebilirsiniz).
  • X86 derlemesi olmalıdır.
  • Tamsayı statik / satır içi tamsayı değil, bir çalışma zamanı değeri olmalıdır . Derleme zamanında ne olduğunu bilmiyoruz.
  • Bir kayda zaten bir tamsayı yüklediğinizi varsayalım. (Ancak netlik için cevaba kayıttaki değeri ayarlamayı dahil edin).
  • Harici kitaplık veya işlev çağrılamaz.
  • Intel belgelerindeki mevcut talimatlardan herhangi birini kullanmak ücretsizdir .
  • Hayır C.
  • ~ 7 Intel Core mimarisinden herhangi biri kabul edilebilir ( sayfa 10'da listelenmiştir ). İdeal olarak Nehalem (Intel Core i7).

Kazanan cevap, mümkün olan en az saat döngüsünü kullanan cevaptır. Yani, saniyede en fazla işleme sahip olabilir. Yaklaşık saat döngüsü özetleri buradadır: http://www.agner.org/optimize/instruction_tables.pdf . Yanıt gönderildikten sonra saat döngülerinin hesaplanması olabilir.


'FYL2X' ve diğer FPU talimatlarına izin veriliyor mu?
Dijital Travma

1
Sonuç bir tamsayı mı olmalı? Eğer öyleyse, nasıl yuvarlanmalıdır?
Dijital Travma

3
Yani hem giriş hem de çıkış tamsayı, değil mi? Ancak giriş 10 ^ 12 kadar büyük olabilir, bu yüzden 32 bit int için çok büyük. Peki 64 bit tamsayı girişi var mı?
Paul R

3
Kazanan kriter maksimum veya ortalama döngü sayısına mı dayanıyor? Ve eğer ortalama ise, girdilerin dağılımı nedir?
Runer112

2
Hangi işlemci hedefleniyor? Bağlantılı belge 20'den fazla farklı işlemi listeler (AMD, Intel, diğerleri) ve gecikmelerde önemli farklılıklar vardır.
Dijital Travma

Yanıtlar:


8

7 döngü, sabit zaman

İşte bu SO Soru benim cevap dayalı bir çözüm . Sayıyı tutmak için kaç bit gerektiğini saymak için BSR kullanır. Birçok bitin tutabileceği en büyük sayıyı temsil etmek için kaç ondalık basamak gerektiğini gösterir. Daha sonra, gerçek sayı o kadar basamaklı 10'a en yakın güçten daha azsa 1'i çıkarır.

    .intel_syntax noprefix
    .globl  main
main:
    mov rdi, 1000000000000              #;your value here
    bsr rax, rdi
    movzx   eax, BYTE PTR maxdigits[1+rax]
    cmp rdi, QWORD PTR powers[0+eax*8]
    sbb al, 0
    ret
maxdigits:
    .byte   0
    .byte   0
    .byte   0
    .byte   0
    .byte   1
    .byte   1
    .byte   1
    .byte   2
    .byte   2
    .byte   2
    .byte   3
    .byte   3
    .byte   3
    .byte   3
    .byte   4
    .byte   4
    .byte   4
    .byte   5
    .byte   5
    .byte   5
    .byte   6
    .byte   6
    .byte   6
    .byte   6
    .byte   7
    .byte   7
    .byte   7
    .byte   8
    .byte   8
    .byte   8
    .byte   9
    .byte   9
    .byte   9
    .byte   9
    .byte   10
    .byte   10
    .byte   10
    .byte   11
    .byte   11
    .byte   11
    .byte   12
powers:
    .quad   0
    .quad   10
    .quad   100
    .quad   1000
    .quad   10000
    .quad   100000
    .quad   1000000
    .quad   10000000
    .quad   100000000
    .quad   1000000000
    .quad   10000000000
    .quad   100000000000
    .quad   1000000000000

Ubuntu için GCC 4.6.3 üzerinde derler ve çıkış kodundaki değeri döndürür.

Herhangi bir modern işlemci için bu döngü tablosunu yorumlamaktan emin değilim, ancak @ DigitalTrauma'nın yöntemini kullanarak Nehalim işlemcide 7 alıyorum ?

ins        uOps Latency
---        -    - 
BSR r,r  : 1    3
MOVZX r,m: 1    -
CMP m,r/i: 1    1 
SBB r,r/i: 2    2

Oh - benimkini yazmaya başladıktan sonra DigitalTrauma'yı gördüm. Benzer fikirler. Sayma metodolojisini kullanarak BSR, MOV, CMP, SBB için 3,1,1,1 = 6 elde ettim
AShelly

Evet, sanırım seninki benimkini dövüyor. Sadece a) Bir meclis programcısı değilim ve b) Meclis en iyi bizim tarafımızdan yalnız bırakılır ;-)
Dijital Travma

The integer must be a runtime value, not a static/inline integer. So we don't know what it is at compile time.
kedi

1
ve bir sonraki satır şöyle der: "Bir kayda zaten bir tamsayı yüklediğinizi varsayın. (Ancak, açıklık için yanıttaki değeri ayarlamayı da ekleyin). Ben de bunu yaptım.
AShelly

movzx eax yerine mov al. İlk 24 bit eax zaten sıfır olacaktır, bu nedenle zx gereksizdir (ve pahalıdır).
peter ferrie

6

En iyi durum 8 döngü, En kötü durum 12 döngü

Soruda net olmadığı için bunu Ivy Bridge gecikmelerinden dayandırıyorum.

Buradaki yaklaşım bsr(bit taraması ters) komutunu fakir bir adamın log2 () olarak kullanmaktır. Sonuç, 0 ila 42 bitleri içeren girişleri içeren bir atlama tablosuna bir dizin olarak kullanılır. 64bit veriler üzerinde işlemin dolaylı olarak gerekli olduğu göz önüne alındığında, bsrkomutun kullanımının iyi olduğunu varsayıyorum .

En iyi durum girişlerinde, atlanabilir tablo girişi bsrsonucu doğrudan büyüklüğe eşleyebilir . Örneğin, 32-63 aralığındaki girişler için, bsrsonuç 1 büyüklüğünde olacak şekilde 5 olacaktır. Bu durumda, komut yolu:

Instruction    Latency

bsrq                 3
jmp                  2
movl                 1
jmp                  2

total                8

En kötü durum girişlerinde, bsrsonuç iki olası büyüklükle eşleşecektir, bu nedenle atlanabilir giriş cmp, girişin> 10 n olup olmadığını kontrol etmek için bir ek daha yapar . Örneğin bsr64-127 aralığındaki girişler için sonuç 6 olur. Karşılık gelen takviye edilebilir giriş daha sonra girişin> 100 olup olmadığını kontrol eder ve çıkış büyüklüğünü buna göre ayarlar.

En kötü durum yoluna ek olarak, kullanımda 64bit anlık bir değer yüklemek için ek bir mov komutumuz var cmp, bu nedenle en kötü durum talimatı yolu:

Instruction    Latency

bsrq                 3
jmp                  2
movabsq              1
cmpq                 1
ja                   2
movl                 1
jmp                  2

total               12

İşte kod:

    /* Input is loaded in %rdi */
    bsrq    %rdi, %rax
    jmp *jumptable(,%rax,8)
.m0:
    movl    $0, %ecx
    jmp .end
.m0_1:
    cmpq    $9, %rdi
    ja  .m1
    movl    $0, %ecx
    jmp .end
.m1:
    movl    $1, %ecx
    jmp .end
.m1_2:
    cmpq    $99, %rdi
    ja  .m2
    movl    $1, %ecx
    jmp .end
.m2:
    movl    $2, %ecx
    jmp .end
.m2_3:
    cmpq    $999, %rdi
    ja  .m3
    movl    $2, %ecx
    jmp .end
.m3:
    movl    $3, %ecx
    jmp .end
.m3_4:
    cmpq    $9999, %rdi
    ja  .m4
    movl    $3, %ecx
    jmp .end
.m4:
    movl    $4, %ecx
    jmp .end
.m4_5:
    cmpq    $99999, %rdi
    ja  .m5
    movl    $4, %ecx
    jmp .end
.m5:
    movl    $5, %ecx
    jmp .end
.m5_6:
    cmpq    $999999, %rdi
    ja  .m6
    movl    $5, %ecx
    jmp .end
.m6:
    movl    $6, %ecx
    jmp .end
.m6_7:
    cmpq    $9999999, %rdi
    ja  .m7
    movl    $6, %ecx
    jmp .end
.m7:
    movl    $7, %ecx
    jmp .end
.m7_8:
    cmpq    $99999999, %rdi
    ja  .m8
    movl    $7, %ecx
    jmp .end
.m8:
    movl    $8, %ecx
    jmp .end
.m8_9:
    cmpq    $999999999, %rdi
    ja  .m9
    movl    $8, %ecx
    jmp .end
.m9:
    movl    $9, %ecx
    jmp .end
.m9_10:
    movabsq $9999999999, %rax
    cmpq    %rax, %rdi
    ja  .m10
    movl    $9, %ecx
    jmp .end
.m10:
    movl    $10, %ecx
    jmp .end
.m10_11:
    movabsq $99999999999, %rax
    cmpq    %rax, %rdi
    ja  .m11
    movl    $10, %ecx
    jmp .end
.m11:
    movl    $11, %ecx
    jmp .end
.m11_12:
    movabsq $999999999999, %rax
    cmpq    %rax, %rdi
    ja  .m12
    movl    $11, %ecx
    jmp .end
.m12:
    movl    $12, %ecx
    jmp .end

jumptable:
    .quad   .m0
    .quad   .m0
    .quad   .m0
    .quad   .m0_1
    .quad   .m1
    .quad   .m1
    .quad   .m1_2
    .quad   .m2
    .quad   .m2
    .quad   .m2_3
    .quad   .m3
    .quad   .m3
    .quad   .m3
    .quad   .m3_4
    .quad   .m4
    .quad   .m4
    .quad   .m4_5
    .quad   .m5
    .quad   .m5
    .quad   .m5_6
    .quad   .m6
    .quad   .m6
    .quad   .m6
    .quad   .m6_7
    .quad   .m7
    .quad   .m7
    .quad   .m7_8
    .quad   .m8
    .quad   .m8
    .quad   .m8_9
    .quad   .m9
    .quad   .m9
    .quad   .m9
    .quad   .m9_10
    .quad   .m10
    .quad   .m10
    .quad   .m10_11
    .quad   .m11
    .quad   .m11
    .quad   .m11_12
    .quad   .m12
    .quad   .m12
    .quad   .m12

.end:
/* output is given in %ecx */

Bu çoğunlukla yazdığım kavram kanıtı C kodu için gcc birleştirici çıktısından üretildi . C kodu atlama tablosunu uygulamak için hesaplanabilir bir goto kullandığını unutmayın. Ayrıca __builtin_clzll(), bsrtalimatı derleyen gcc yerleşimini kullanır (artı bir xor).


Ben bu bir gelmeden önce birkaç çözüm düşündüm:

  • FYL2Xdoğal kütüğü hesaplamak, sonra FMULgerekli sabit ile. Bu bir [tag: instruction: golf] yarışması olsaydı bu muhtemelen kazanırdı. Ancak FYL2XIvy köprüsü için 90-106 gecikme süresi var.

  • Sabit kodlu ikili arama. Bu gerçekten rekabetçi olabilir - uygulamak için başka birine bırakacağım :).

  • Sonuçların tam arama tablosu. Bu teorik olarak en hızlı olduğundan eminim, ancak 1TB arama tablosu gerektirir - henüz pratik değil - belki birkaç yıl içinde Moore Yasası tutmaya devam ederse.


Gerekirse izin verilen tüm girişler için ortalama bir gecikme süresi hesaplayabilirim.
Dijital Travma

jmpve jccgecikme yok, sadece üretim maliyetleri. Şube tahmini + spekülatif yürütme ortalama kontrol bağımlılıkları veri bağımlılığı zincirlerinin bir parçası değildir.
Peter Cordes
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.