Optimize ediciyi bir tamsayı aralığı vererek ipucu verebilir miyim?


173

intBir değeri saklamak için bir tür kullanıyorum . Programın anlambilimiyle, değer her zaman çok küçük bir aralıkta değişir (0 - 36) ve int(a değil char) yalnızca CPU verimliliği nedeniyle kullanılır.

Bu kadar az sayıda tamsayı üzerinde birçok özel aritmetik optimizasyon yapılabiliyor gibi görünüyor. Bu tamsayılardaki birçok işlev çağrısı, küçük bir "büyülü" işlem kümesine optimize edilebilir ve hatta bazı işlevler tablo aramaları haline getirilebilir.

Peki, derleyiciye bunun inther zaman küçük aralıkta olduğunu söylemek mümkün mü ve derleyicinin bu optimizasyonları yapması mümkün mü?


4
değer aralığı optimizasyonları birçok derleyicide vardır, örn. llvm ama bildirmek için herhangi bir dil ipucunun farkında değilim.
Remus Rusanu

2
Hiç negatif sayınız yoksa unsigned, derleyicinin akıl yürütmesi daha kolay olduğu için türleri kullanmak için küçük kazançlarınız olabileceğini unutmayın .
user694733

4
@RemusRusanu: Pascal, alt aralık türlerini tanımlamanızı sağlar , örn var value: 0..36;.
Edgar Bonet

7
" int (karakter değil) yalnızca CPU verimliliği nedeniyle kullanılır. " Geleneksel bilgeliğin bu eski parçası genellikle çok doğru değildir. Dar türlerin bazen sıfır veya işaretin tam kayıt genişliğine genişletilmesi gerekir, özellikle. dizi indeksleri olarak kullanıldığında, ancak bazen bu ücretsiz olur. Bu tür bir diziniz varsa, önbellek kapladığı alandaki azalma genellikle başka bir şeyden ağır basar.
Peter Cordes

1
Söylemeyi unuttum: intve unsigned int64 bit işaretçileri olan çoğu sistemde de 32'den 64 bit'e işaret ya da sıfır genişletilmesi gerekiyor. X86-64'te, 32-bit kayıtlarda yapılan işlemlerin sıfır-64'e 64-bit için ücretsiz olduğunu unutmayın (işaret genişletme değil, ancak imzalı taşma tanımsız bir davranıştır, bu nedenle derleyici isterse 64 bit imzalı matematik kullanabilir). Bu nedenle, hesaplama sonuçlarını değil, yalnızca 32 bit işlev bağımsız değişkenlerini sıfırlamak için ek yönergeler görürsünüz. Daha dar işaretsiz tipler için kullanılır.
Peter Cordes

Yanıtlar:


230

Evet mümkün. Örneğin , derleyiciye imkansız koşullar hakkında bilgi vermek için gcckullanabilirsiniz __builtin_unreachable, örneğin:

if (value < 0 || value > 36) __builtin_unreachable();

Yukarıdaki koşulu bir makroda sarabiliriz:

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

Ve şu şekilde kullanın:

assume(x >= 0 && x <= 10);

Gördüğünüz gibi , gccbu bilgilere dayanarak optimizasyonları gerçekleştirir:

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

üretir:

func(int):
    mov     eax, 17
    ret

Bununla birlikte, bir dezavantajı, kodunuz bu varsayımları ihlal ederse, tanımsız davranışlar elde etmenizdir .

Bu, hata ayıklama yapılarında bile size bildirilmez. Hatalarla varsayımları daha kolay hata ayıklamak / test etmek / yakalamak için, aşağıdakine benzer bir karma varsayım / onaylama makrosu (@David Z'ye yapılan krediler) kullanabilirsiniz:

#if defined(NDEBUG)
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#include <cassert>
#define assume(cond) assert(cond)
#endif

Ayıklama (yapılarınızı olarak NDEBUG değil tanımlanmıştır), sıradan bir gibi çalışır asserthata mesajı yazdırmak ve, abort'programını ing ve sürümde optimize kod üreten, bu bir varsayım kullanır inşa eder.

Bununla birlikte, normalin yerine geçmediğini unutmayın assert- condsürüm yapılarında kalır, bu yüzden böyle bir şey yapmamalısınız assume(VeryExpensiveComputation()).


5
@Xofo, benim örneğimde bu gerçekleşmedi, çünkü return 2şube derleyici tarafından koddan çıkarıldı.

6
Bununla birlikte, gcc'nin OP'nin beklediği gibi işlevleri büyülü işlemlere veya tablo aramasına göre optimize edemeyeceği görülmektedir .
jingyu9575

19
@ user3528438, __builtin_expectkatı olmayan bir ipucudur. __builtin_expect(e, c)" edeğerlendirilmesi en muhtemel olan " olarak okunmalıdır cve şube tahminini optimize etmek için yararlı olabilir, ancak eher zaman olması kısıtlanmaz c, bu nedenle optimize edicinin diğer durumları atmasına izin vermez. Şubelerin mecliste nasıl düzenlendiğine bakın .

6
Teoride, koşulsuz olarak tanımlanmamış davranışa neden olan herhangi bir kod yerine kullanılabilir __builtin_unreachable().
CodesInChaos

14
Bu kötü bir fikir yapar hakkında bilmiyorum bazı cilvesi var sürece, bu birleştirmek mantıklı olabilir asserttanımlamak, örneğin assumeolarak assertne zaman NDEBUGtanımlı değil ve aynı __builtin_unreachable()zaman NDEBUGtanımlanır. Bu şekilde üretim kodundaki varsayımdan faydalanırsınız, ancak bir hata ayıklama derlemesinde hala açık bir kontrolünüz olur. Tabii ki, varsayımın vahşi doğada tatmin olacağını garanti altına almak için yeterli testleri yapmanız gerekir.
David Z

61

Bunun için standart destek var. Yapmanız gereken, stdint.h( cstdint) öğesini dahil etmek ve ardından türü kullanmaktır uint_fast8_t.

Bu derleyiciye yalnızca 0 - 255 arasındaki sayıları kullandığınızı, ancak daha hızlı kod verirse daha büyük bir tür kullanmanın ücretsiz olduğunu bildirir. Benzer şekilde, derleyici değişkenin asla 255'in üzerinde bir değere sahip olmayacağını varsayabilir ve buna göre optimizasyonlar yapabilir.


2
Bu türler neredeyse olması gerektiği kadar kullanılmaz (Ben şahsen var olduklarını unutmaya eğilimliyim). Hem hızlı hem de taşınabilir, oldukça parlak olan kodlar veriyorlar. 1999'dan beri
Lundin

Bu, genel durum için iyi bir öneridir. deniss'in cevabı belirli senaryolar için daha yumuşak bir çözüm göstermektedir.
Orbit'te Hafiflik Yarışları

1
Derleyici, yalnızca x86 / ARM / MIPS / PPC'de olduğu gibi uint_fast8_t8 bitlik bir tür (örn. unsigned char) Olan sistemler hakkında 0-255 aralığı bilgilerini alır ( godbolt.org/g/KNyc31 ). On 21164A önce erken DEC Alpha herhangi aklı başında uygulaması kullanmak istiyorsunuz, böylece bayt saklar / yükler, desteklenmemektedir edildi typedef uint32_t uint_fast8_t. AFAIK, bir türün çoğu derleyici (gcc gibi) ile ekstra aralık sınırlarına sahip olması için bir mekanizma yoktur, bu yüzden bu durumda uint_fast8_ttam olarak aynı şekilde davranacağından eminim unsigned int.
Peter Cordes

( boolözeldir ve aralık 0 veya 1 ile sınırlıdır, ancak chargcc / clang açısından başlık dosyaları tarafından tanımlanmayan yerleşik bir türdür . Dediğim gibi, çoğu derleyicinin bir mekanizması olduğunu düşünmüyorum bunu mümkün kılar.)
Peter Cordes

1
Her neyse, uint_fast8_tiyi bir öneri, çünkü platformlarda 8 bitlik bir tür kullanacak unsigned int. (Ben değil emin aslında ne olduğum fasttipleri hızlı olması gerekiyordu için olup olmadığı ve önbellek ayakizi Tradeoff bunun bir parçası olması gerekiyordu.). x86, bellek kaynağı ile bayt ekleme yapmak için bile bayt işlemleri için geniş bir desteğe sahiptir, bu nedenle ayrı bir sıfır genişletme yükü bile yapmanız gerekmez (bu da çok ucuzdur). gcc uint_fast16_t, x86'da çoğu kullanım için çılgın olan 64 bitlik bir tür yapar (32 bit'e karşı). godbolt.org/g/Rmq5bv .
Peter Cordes

8

Bildiğiniz zaman geçerli cevabı durum için iyidir kesin aralığı nedir, ama yine de doğru bir davranış istiyorsanız değeri, beklenen aralık dışında o zaman iş olmaz zaman.

Bu durumda, bu tekniğin işe yarayabileceğini buldum:

if (x == c)  // assume c is a constant
{
    foo(x);
}
else
{
    foo(x);
}

Fikir bir kod verisi dengesidir: 1 bit veriyi (olsun x == c) kontrol mantığına taşıyorsunuz .
Bu x, aslında bilinen bir sabit olan optimize ediciyi ima eder ve conu foo, muhtemelen oldukça ağır bir şekilde diğerlerinden ayrı olarak ilk çağrıyı satır içi ve optimize etmeye teşvik eder .

fooYine de kodu tek bir alt programa kattığınızdan emin olun - kodu çoğaltmayın.

Misal:

Bu tekniğin çalışması için biraz şanslı olmanız gerekir - derleyicinin şeyleri statik olarak değerlendirmemeye karar verdiği durumlar vardır ve bunlar keyfidir. Ancak işe yaradığında, iyi çalışır:

#include <math.h>
#include <stdio.h>

unsigned foo(unsigned x)
{
    return x * (x + 1);
}

unsigned bar(unsigned x) { return foo(x + 1) + foo(2 * x); }

int main()
{
    unsigned x;
    scanf("%u", &x);
    unsigned r;
    if (x == 1)
    {
        r = bar(bar(x));
    }
    else if (x == 0)
    {
        r = bar(bar(x));
    }
    else
    {
        r = bar(x + 1);
    }
    printf("%#x\n", r);
}

Sadece kullanmak -O3ve önceden değerlendirilerek sabitleri fark 0x20ve 0x30ede montajcı çıkışı .


İstemez misin if (x==c) foo(c) else foo(x)? Sadece uygulamalarını yakalamak constexpriçin foo?
MSalters

@MSalters: Birisinin bunu soracağını biliyordum !! Bu tekniğe daha önce geldim constexprve daha sonra "güncellemekten" hiç rahatsız olmadım ( constexprdaha sonra bile endişelenmeme rağmen), ama başlangıçta yapmamamın nedeni derleyicinin ortak kod olarak bunları hesaba katmasını ve normal yöntem çağrıları olarak bırakmaya ve optimize etmemeye karar verdiyse dalı kaldırmayı kolaylaştırır. Eğer cben asla doğrulanmadı olsa ben derleyici c (üzgünüm, kötü şaka) iki aynı kod olduğunu gerçekten zor koymak eğer bekledi .
user541686

4

Ben sadece daha standart C ++ olan bir çözüm istiyorsanız [[noreturn]], kendi yazmak için özniteliği kullanabilirsiniz söylemek için yunuslama unreachable.

Bu yüzden göstermek için mükemmel bir örnek deniss :

namespace detail {
    [[noreturn]] void unreachable(){}
}

#define assume(cond) do { if (!(cond)) detail::unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

Hangi gördüğünüz gibi , hemen hemen aynı kodu sonuçları:

detail::unreachable():
        rep ret
func(int):
        movl    $17, %eax
        ret

Dezavantajı elbette, bir [[noreturn]]fonksiyonun gerçekten döndüğüne dair bir uyarı almanızdır.


O çalışır clang, benim orijinal çözüm değil yaptığında , güzel hile ve +1 böylece. Ancak her şey derleyiciye bağımlıdır (Peter Cordes'in bize gösterdiği iccgibi, performansı kötüleştirebilir ), bu yüzden hala evrensel olarak uygulanabilir değildir. Ayrıca, küçük bir not: unreachabletanımın optimize edici için kullanılabilir olması ve bunun çalışması için satır içi olması gerekir .
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.