Günlüğün (x) daha hızlı yaklaşması hakkında


10

Bir süre önce kütüphane fonksiyonlarını kullanmadan hesaplamaya çalışan bir kod yazmıştım . Dün eski kodu gözden geçiriyordum ve mümkün olduğunca çabuk yapmaya çalıştım (ve doğru). İşte şimdiye kadar denemem:log(x)

const double ee = exp(1);

double series_ln_taylor(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now;
    int i, flag = 1;

    if ( n <= 0 ) return 1e-300;
    if ( n * ee < 1 )
        n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    for ( term = 1; term < n ; term *= ee, lgVal++ );
    n /= term;

    /* log(1 - x) = -x - x**2/2 - x**3/3... */
    n = 1 - n;
    now = term = n;
    for ( i = 1 ; ; ){
        lgVal -= now;
        term *= n;
        now = term / ++i;
        if ( now < 1e-17 ) break;
    }

    if ( flag == -1 ) lgVal = -lgVal;

    return lgVal;
}

Burada e bulmaya çalışıyorum ki e a sadece n'nin üzerinde ve sonra n'nin logaritma değerini ekliyorumaea , ki bu 1'den küçüktür. Bu noktada,log(1-x) 'in Taylor genişlemesiendişelenmeden kullanılabilir.nealog(1  x)

Son zamanlarda sayısal analiz bir ilgi büyüdü ve bu yüzden soru doğru sormaya yardımcı olamaz, yeterince doğru olurken bu kod segmenti pratikte ne kadar hızlı çalıştırılabilir? Ben gibi, sürekli kısmını kullanarak, örneğin, bazı diğer yöntemlere geçmek gerekiyor mu bu ?

C standart kütüphane ile temin edilmekte olan işlev, neredeyse 5.1 kat daha hızlı, bu uygulama daha fazladır.log(x)

GÜNCELLEME 1 : Wikipedia'da belirtilen hiperbolik arktan serisini kullanarak, hesaplama C standart kütüphane log fonksiyonundan neredeyse 2.2 kat daha yavaş görünüyor. Gerçi, performansı kapsamlı bir şekilde kontrol etmedim ve daha büyük sayılar için mevcut uygulamam GERÇEKTEN yavaş görünüyor. Uygulamamın her ikisini de hataya bağlı olup olmadığını ve çok sayıda sayı için ortalama süreyi kontrol edebilirsem kontrol etmek istiyorum. İşte ikinci çabam.

double series_ln_arctanh(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now, sm;
    int i, flag = 1;

    if ( n <= 0 ) return 1e-300;
    if ( n * ee < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    for ( term = 1; term < n ; term *= ee, lgVal++ );
    n /= term;

    /* log(x) = 2 arctanh((x-1)/(x+1)) */
    n = (1 - n)/(n + 1);

    now = term = n;
    n *= n;
    sm = 0;
    for ( i = 3 ; ; i += 2 ){
        sm += now;
        term *= n;
        now = term / i;
       if ( now < 1e-17 ) break;
    }

    lgVal -= 2*sm;

    if ( flag == -1 ) lgVal = -lgVal;
    return lgVal;
}

Herhangi bir öneri veya eleştiri takdir edilmektedir.

1e81e3084e15

double series_ln_better(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now, sm;
    int i, flag = 1;

    if ( n == 0 ) return -1./0.; /* -inf */
    if ( n < 0 ) return 0./0.;   /* NaN*/
    if ( n < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    /* the cutoff iteration is 650, as over e**650, term multiplication would
       overflow. For larger numbers, the loop dominates the arctanh approximation
       loop (with having 13-15 iterations on average for tested numbers so far */

    for ( term = 1; term < n && lgVal < 650 ; term *= ee, lgVal++ );
    if ( lgVal == 650 ){
        n /= term;
        for ( term = 1 ; term < n ; term *= ee, lgVal++ );
    }
    n /= term;

    /* log(x) = 2 arctanh((x-1)/(x+1)) */
    n = (1 - n)/(n + 1);

    now = term = n;
    n *= n;
    sm = 0;

    /* limiting the iteration for worst case scenario, maximum 24 iteration */
    for ( i = 3 ; i < 50 ; i += 2 ){
        sm += now;
        term *= n;
        now = term / i;
        if ( now < 1e-17 ) break;
    }

    lgVal -= 2*sm;

    if ( flag == -1 ) lgVal = -lgVal;

    return lgVal;
}

Yanıtlar:


17

Bu gerçekten güvenilir bir cevap değil, daha çok düşünmeniz gereken konuların bir listesi ve kodunuzu test etmedim.

log2.15.1

f(x)doublen12

n1.7976e+308term=infn=11017nterm *= e709.78266108405500745

1030000

Biraz çaba göstererek, örneğin argüman aralığını kısıtlayarak veya biraz daha az doğru sonuçlar döndürerek performans için bu sağlamlığın bir kısmını feda edebileceğinizden şüpheleniyorum.

3. Bu tür kodların performansı, üzerinde çalıştığı CPU mimarisine büyük ölçüde bağlı olabilir. Bu derin ve ilgili bir konudur, ancak Intel gibi CPU üreticileri, kodunuz ve üzerinde çalıştığı CPU arasındaki farklı etkileşimleri açıklayan optimizasyon kılavuzlarını yayınlar. Önbelleğe alma nispeten basit olabilir, ancak veri bağımlılıklarından dolayı şube tahmini, talimat düzeyinde paralellik ve boru hattı durakları gibi şeylerin yüksek düzey kodda tam olarak görülmesi zordur, ancak performans için çok önemlidir.

x~y~=f~(x~)y=f(x~)doğru?). Bu, kayan nokta yuvarlama hatalarının varlığı nedeniyle Taylor serisinin yakınsama gösterdiği ile aynı değildir.

4.5. Test edilmemiş bir işlevi doğruluk açısından test etmenin iyi bir yolu, dört milyarın her birinde (burada olduğu gibi argüman azaltmayı doğru şekilde yapıyorsanız daha az) tek duyarlıklı şamandıraları değerlendirmek ve hataları standart günlükle karşılaştırmak olacaktır. libm. Biraz zaman alır, ama en azından ayrıntılı.

5. Başından beri çiftlerin hassasiyetini bildiğiniz için, sınırsız bir döngüye sahip olmanız gerekmez: yineleme sayısı önceden belirlenebilir (muhtemelen yaklaşık 50'dir). Dalları kodunuzdan kaldırmak veya en azından yineleme sayısını önceden ayarlamak için bunu kullanın.

Döngü açma ile ilgili tüm genel fikirler de geçerlidir.

6. Taylor serisinden başka yaklaşım tekniklerini kullanmak mümkündür. Chebyshev serisi (Clenshaw rekürrensiyle birlikte), Pade yaklaşımları ve bazen fonksiyonunuz daha basit bir işlevin kökü olarak yeniden biçimlendirilebildiğinde Newton'un yöntemi gibi kök bulma yöntemleri de vardır (örneğin ünlü sqrt hilesi ).

Devam eden kesirler muhtemelen çok büyük olmayacaktır, çünkü çoğalma / eklemelerden çok daha pahalı olan bölünmeyi içerirler. Eğer bakarsak _mm_div_ssen https://software.intel.com/sites/landingpage/IntrinsicsGuide/ , bölme 3-5 / 0.5-1 ile karşılaştırıldığında, mimarisine bağlı olarak 13-14 döngü ve çıktı 5-14 gecikme var çarpma / ekleme / madd. Yani genel olarak (her zaman değil) bölünmeleri mümkün olduğunca ortadan kaldırmaya çalışmak mantıklıdır.

Ne yazık ki, matematik değil böyle ile ifadeler nedeniyle, burada büyük bir rehber kısa formüller mutlaka en hızlı olanlar değildir. Örneğin matematik bölümleri cezalandırmaz.

7. Kayan nokta sayıları dahili olarak şeklinde saklanır.x=m×2em12<m1exfrexp

8. Şu Verilerinizi Karşılaştırma logile logde libmveya openlibm(: örn https://github.com/JuliaLang/openlibm/blob/master/src/e_log.c ). Bu, diğer insanların zaten neyi çözdüğünü bulmanın en kolay yoludur. Ayrıca, libm CPU üreticilerine özgü özel olarak optimize edilmiş sürümleri de vardır , ancak genellikle kaynak kodları yayınlanmaz.

Boost :: sf'nin bazı özel işlevleri vardır, ancak temel işlevleri yoktur. Yine de log1p kaynağına bakmak öğretici olabilir: http://www.boost.org/doc/libs/1_58_0/libs/math/doc/html/math_toolkit/powers/log1p.html

Ayrıca, gereken yüksek hassasiyet nedeniyle libm'den farklı algoritmalar kullanabilen mpfr gibi açık kaynaklı rasgele kesinlikli aritmetik kütüphaneleri de vardır.

9. Higham'ın Sayısal Algoritmaların Doğruluğu ve Kararlılığı, sayısal algoritma hatalarını analiz etmek için iyi bir üst düzey giriştir. Yaklaşım algoritmalarının kendileri için Trefethen tarafından Yaklaşım Teorisi Yaklaşım Uygulaması iyi bir referanstır.

10. Bunun biraz sık söylendiğini biliyorum, ancak oldukça büyük yazılım projeleri nadiren tekrar tekrar çağrılan küçük bir işlevin çalışma süresine bağlıdır. Programınızın profilini oluşturmadığınız ve bunun önemli olduğundan emin olmadıkça, günlük performansı hakkında endişelenmeniz çok yaygın değildir .


26414e15

1.13e13term

 1e8

1
k=11071lnk

2
frexp x=m×2elnx=eln2+lnm

5

Kirill'in cevabı çok sayıda ilgili konuya değindi. Bazılarını pratik matematik kütüphanesi tasarım deneyimine dayalı olarak genişletmek istiyorum. Bir not: matematik kütüphanesi tasarımcıları, yayınlanan her algoritmik optimizasyonun yanı sıra hepsi yayınlanmayacak birçok makineye özgü optimizasyon kullanma eğilimindedir. Kod, derlenmiş kod kullanmak yerine sık sık derleme dilinde yazılır. Bu nedenle, basit ve derlenmiş bir uygulamanın, özdeş özellik kümeleri (doğruluk, özel durumlar işleme, hata raporlama, yuvarlama modu desteği) varsayarak, mevcut bir yüksek kaliteli matematik kütüphanesi uygulamasının performansının% 75'inden fazlasını elde etmesi olası değildir.

explogerfcΓ

Doğruluk tipik olarak (üçüncü taraf) daha yüksek hassasiyetli bir referans ile karşılaştırılarak değerlendirilir. Tek argümanlı tek duyarlıklı işlevler kolayca kapsamlı bir şekilde test edilebilir, diğer işlevler rastgele yönlendirilmiş vektörlerle test edilmesini gerektirir Açıkçası, sonsuz hassas sonuçlar elde edilemez, ancak Tablo Yapıcı'nın İkilemi üzerine yapılan araştırmalar, birçok basit fonksiyon için, referansın hedef hassasiyetinin yaklaşık üç katı bir hassasiyetle hesaplanmasının yeterli olduğunu düşündürmektedir. Örneğin bakınız:

Vincent Lefèvre, Jean-Michel Muller, "Çift Hassasiyette Temel Fonksiyonların Doğru Yuvarlanması için En Kötü Durumlar". In Bilgisayar Aritmetik ilişkin dava 15 IEEE Sempozyumu , 2001,111-118). (çevrimiçi baskı öncesi)

Performans açısından, gecikme için optimizasyon (biri bağımsız işlemlerin yürütme süresine bakıldığında önemlidir) ile verim için optimize etme (bağımsız işlemlerin yürütme süresi dikkate alındığında geçerlidir) arasında ayrım yapmak gerekir. Son yirmi yılda, talimat seviyesi paralelliği (örn. Süperskalar, sıra dışı işlemciler), veri seviyesi paralelliği (örneğin SIMD talimatları) ve iplik seviyesi paralelliği (örn. Hiper iş parçacığı, (çok çekirdekli işlemciler), daha alakalı bir metrik olarak hesaplama çıktısına vurgu yapılmasına yol açmıştır.

log(1+x)=p(x)log(x)=2atanh((x1)/(x+1))=p(((x1)/(x+1))2)p

İlk olarak 25 yıl önce IBM tarafından tanıtılan ve şimdi tüm büyük işlemci mimarilerinde bulunan kaynaştırılmış çoklu ekleme işlemi ( FMA ), modern matematik kütüphanesi uygulamalarının önemli bir yapı taşıdır. Yuvarlama hatası azaltma sağlar, çıkarma iptali için sınırlı koruma sağlar ve çift-çift aritmetiği büyük ölçüde basitleştirir .

C99log()C99fma()233

#include <math.h>

/* compute natural logarithm

   USE_ATANH == 1: maximum error found: 0.83482 ulp @ 0.7012829191167614
   USE_ATANH == 0: maximum error found: 0.83839 ulp @ 1.2788954397331760
*/
double my_log (double a)
{
    const double LOG2_HI = 0x1.62e42fefa39efp-01; // 6.9314718055994529e-01
    const double LOG2_LO = 0x1.abc9e3b39803fp-56; // 2.3190468138462996e-17
    double m, r, i, s, t, p, f, q;
    int e;

    m = frexp (a, &e);
    if (m < 0.70703125) { // 181/256
        m = m + m;
        e = e - 1;
    }
    i = (double)e;

    /* m in [181/256, 362/256] */

#if USE_ATANH
    /* Compute q = (m-1) / (m+1) */
    p = m + 1.0;
    m = m - 1.0;
    q = m / p;

    /* Compute (2*atanh(q)/q-2*q) as p(q**2), q in [-75/437, 53/309] */
    s = q * q;
    r =             0x1.2f1da230fb057p-3;  // 1.4800574027992994e-1
    r = fma (r, s,  0x1.399f73f934c01p-3); // 1.5313616375223663e-1
    r = fma (r, s,  0x1.7466542530accp-3); // 1.8183580149169243e-1
    r = fma (r, s,  0x1.c71c51a8bf129p-3); // 2.2222198291991305e-1
    r = fma (r, s,  0x1.249249425f140p-2); // 2.8571428744887228e-1
    r = fma (r, s,  0x1.999999997f6abp-2); // 3.9999999999404662e-1
    r = fma (r, s,  0x1.5555555555593p-1); // 6.6666666666667351e-1
    r = r * s;

    /* log(a) = 2*atanh(q) + i*log(2) = LOG2_LO*i + p(q**2)*q + 2q + LOG2_HI*i.
       Use K.C. Ng's trick to improve the accuracy of the computation, like so:
       p(q**2)*q + 2q = p(q**2)*q + q*t - t + m, where t = m**2/2.
    */
    t = m * m * 0.5;
    r = fma (q, t, fma (q, r, LOG2_LO * i)) - t + m;
    r = fma (LOG2_HI, i, r);

#else // USE_ATANH

    /* Compute f = m -1 */
    f = m - 1.0;
    s = f * f;

    /* Approximate log1p (f), f in [-75/256, 106/256] */
    r = fma (-0x1.961d64ddd82b6p-6, f, 0x1.d35fd598b1362p-5); // -2.4787281515616676e-2, 5.7052533321928292e-2
    t = fma (-0x1.fcf5138885121p-5, f, 0x1.b97114751d726p-5); // -6.2128580237329929e-2, 5.3886928516403906e-2
    r = fma (r, s, t);
    r = fma (r, f, -0x1.b5b505410388dp-5); // -5.3431043874398211e-2
    r = fma (r, f,  0x1.dd660c0bd22dap-5); //  5.8276198890387668e-2
    r = fma (r, f, -0x1.00bda5ecdad6fp-4); // -6.2680862565391612e-2
    r = fma (r, f,  0x1.1159b2e3bd0dap-4); //  6.6735934054864471e-2
    r = fma (r, f, -0x1.2489f14dd8883p-4); // -7.1420614809115476e-2
    r = fma (r, f,  0x1.3b0ee248a0ccfp-4); //  7.6918491287915489e-2
    r = fma (r, f, -0x1.55557d3b497c3p-4); // -8.3333481965921982e-2
    r = fma (r, f,  0x1.745d4666f7f48p-4); //  9.0909266480136641e-2
    r = fma (r, f, -0x1.999999d959743p-4); // -1.0000000092767629e-1
    r = fma (r, f,  0x1.c71c70bbce7c2p-4); //  1.1111110722131826e-1
    r = fma (r, f, -0x1.fffffffa61619p-4); // -1.2499999991822398e-1
    r = fma (r, f,  0x1.249249262c6cdp-3); //  1.4285714290377030e-1
    r = fma (r, f, -0x1.555555555f03cp-3); // -1.6666666666776730e-1
    r = fma (r, f,  0x1.999999999759ep-3); //  1.9999999999974433e-1
    r = fma (r, f, -0x1.fffffffffff53p-3); // -2.4999999999999520e-1
    r = fma (r, f,  0x1.555555555555dp-2); //  3.3333333333333376e-1
    r = fma (r, f, -0x1.0000000000000p-1); // -5.0000000000000000e-1

    /* log(a) = log1p (f) + i * log(2) */
    p = fma ( LOG2_HI, i, f);
    t = fma (-LOG2_HI, i, p);
    f = fma ( LOG2_LO, i, f - t);
    r = fma (r, s, f);
    r = r + p;
#endif // USE_ATANH

    /* Handle special cases */
    if (!((a > 0.0) && (a <= 0x1.fffffffffffffp1023))) {
        r = a + a;  // handle inputs of NaN, +Inf
        if (a  < 0.0) r =  0.0 / 0.0; //  NaN
        if (a == 0.0) r = -1.0 / 0.0; // -Inf
    }
    return r;
}

(+1) Ortak açık kaynaklı uygulamaların (openlibm gibi) olabildiğince iyi olup olmadığını veya özel işlevlerinin geliştirilebileceğini biliyor musunuz?
Kirill

1
@Kirill Açık kaynak uygulamalarına (yıllar önce) baktım, FMA'nın faydalarından faydalanmıyorlardı. O zamanlar IBM Power ve Intel Itanium, operasyonu içeren tek mimarilerdi, şimdi bunun için donanım desteği her yerde mevcut. Ayrıca, tablo artı polinom yaklaşımları en son teknoloji idi, şimdi tablolar lehte: bellek erişimi daha yüksek enerji kullanımı ile sonuçlanıyor, vektörleşmeye müdahale edebilir (ve yapabilirler) ve hesaplama verimi bellek veriminden daha fazla arttı sonuç olarak tablolardan olumsuz performans etkisi oluşabilir.
njuffa
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.