Clang neden x * 1.0'ı optimize ederken x + 0.0'ı SEÇMİYOR?


125

Clang neden bu koddaki döngüyü optimize ediyor?

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

ama bu koddaki döngü değil mi?

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

(Hem C hem de C ++ olarak etiketleme çünkü cevabın her biri için farklı olup olmadığını bilmek istiyorum.)


2
Şu anda hangi optimizasyon bayrakları etkin?
Iwillnotexist Idonotexist

1
@IwillnotexistIdonotexist: Sadece kullandım -O3, yine de neyi etkinleştirdiğini nasıl kontrol edeceğimi bilmiyorum.
user541686

2
Komut satırına -ffast-math eklerseniz ne olacağını görmek ilginç olurdu.
plugwash

static double arr[N]C'de izin verilmez; constdeğişkenler bu dilde sabit ifadeler olarak sayılmaz
MM

1
[Daha önce çağırmış olsanız bile, C'nin nasıl C ++ olmadığı hakkında keskin bir yorum ekleyin.]
user253751

Yanıtlar:


164

Kayan Nokta Aritmetiği için IEEE 754-2008 Standardı ve ISO / IEC 10967 Dilden Bağımsız Aritmetik (LIA) Standardı, Bölüm 1 bunun neden böyle olduğunu yanıtlıyor.

IEEE 754 § 6.3 İşaret biti

Bir girdi veya sonuç NaN olduğunda, bu standart bir NaN'nin işaretini yorumlamaz. Bununla birlikte, bit dizelerindeki işlemlerin - copy, negate, abs, copySign - bazen bir NaN işleneninin işaret bitine bağlı olarak bir NaN sonucunun işaret bitini belirttiğini unutmayın. TotalOrder mantıksal yüklemi ayrıca bir NaN işleneninin işaret bitinden de etkilenir. Diğer tüm işlemler için, bu standart, yalnızca bir NaN girişi olduğunda veya geçersiz bir işlemden NaN üretildiğinde bile bir NaN sonucunun işaret bitini belirtmez.

Ne girdiler ne de sonuç NaN olmadığında, bir çarpımın veya bölümün işareti işlenenlerin işaretlerinin dışlayıcı VEYA'sıdır; x + (xy) toplamı olarak kabul edilen bir toplamın veya bir x - y farkının işareti, en fazla toplananların işaretlerinden birinden farklıdır; ve dönüşümlerin sonucunun işareti, niceleme işlemi, roundTo-Integral işlemleri ve roundToIntegralExact (bkz. 5.3.1), ilk veya tek işlenenin işaretidir. Bu kurallar, işlenenler veya sonuçlar sıfır veya sonsuz olduğunda bile geçerli olacaktır.

Karşıt işaretli iki işlenen toplamı (veya benzer işaretli iki işlenen arasındaki fark) tam olarak sıfır olduğunda, bu toplamın (veya farkın) işareti roundTowardNegative hariç tüm yuvarlama yönü özelliklerinde +0 olacaktır; bu nitelik altında, tam bir sıfır toplamın (veya farkın) işareti −0 olacaktır. Bununla birlikte, x + x = x - (−x), x sıfır olduğunda bile x ile aynı işareti korur.

Ekleme Durumu

Varsayılan yuvarlama modu altında (Gidiş-to-yakın, Kravatlar-to-bile) bunu görmek x+0.0üreten xdışında, xbir -0.0: Bu durumda biz toplamı sıfırdır ters işaretli iki işlenen bir miktar var ve §6.3 paragraf Bu eklemenin ürettiği 3 kural +0.0.

Yana +0.0değildir bit düzeyinde orijinal aynıdır -0.0ve bu -0.0girdi olarak oluşabilir meşru bir değerdir, derleyici potansiyel negatif sıfır dönüştürecek bu kodu koymak zorundadır+0.0 .

Özet: Varsayılan yuvarlama modunda, içinde x+0.0, eğerx

  • değil -0.0 , o zaman xkendisi kabul edilebilir bir çıktı değeridir.
  • olduğu -0.0 , daha sonra çıkış değeri olmalıdır +0.0 bitler aynı olmadığı, -0.0.

Çarpma Durumu

Varsayılan yuvarlama modunda , ile böyle bir sorun oluşmaz x*1.0. Eğer x:

  • bir (alt) normal sayıdır, x*1.0 == xher zaman .
  • olduğu +/- infinity, daha sonra sonucudur +/- infinityaynı işaretin.
  • olduğu NaNsonra göre,

    IEEE 754 § 6.2.3 NaN Yayılımı

    Bir NaN işlenenini sonucuna yayan ve giriş olarak tek bir NaN'ye sahip olan bir işlem, hedef biçiminde gösterilebiliyorsa, giriş NaN'nin yüküyle bir NaN üretmelidir.

    bunlardan üs ve mantis (değil işareti) bu araçlar NaN*1.0vardır önerilen girişinden değişmemesini NaN. İşaret, yukarıdaki §6.3p1'e göre belirtilmemiştir, ancak bir uygulama bunun kaynak ile aynı olduğunu belirtebilir NaN.

  • ise +/- 0.0sonuç, §6.3p2 ile uyumlu olarak 0işaret biti 1.0ile XORed işaret biti ile bir olur. İşaret biti olduğu 1.0için 0, çıkış değeri girişten değişmez. Böylece, (negatif) sıfır x*1.0 == xolduğunda bile x.

Çıkarma Durumu

Varsayılan yuvarlama kipinde , çıkarma işlemi x-0.0de işlemsizdir çünkü eşdeğerdir x + (-0.0). Eğer xDİR

  • bu durumda NaN, §6.3p1 ve §6.2.3, toplama ve çarpma ile hemen hemen aynı şekilde uygulanır.
  • olduğu +/- infinity, daha sonra sonucudur +/- infinityaynı işaretin.
  • x-0.0 == xher zaman bir (alt) normal sayıdır .
  • bir -0.0"O §6.3p2 ile biz, [...], veya bir fark X bir toplamın işareti - olarak y toplamı x + (y), addends' işaretleri en az biri farklıdır; ". Bu bizi -0.0sonuç olarak atamaya zorlar (-0.0) + (-0.0), çünkü -0.0işarette hiçbir eklentiden farklıyken, +0.0işaret ikiden farklıdır. Bu maddeye aykırı olarak addends arasında.
  • olduğunu +0.0, o zaman bu ekleme duruma düşürür (+0.0) + (-0.0)yukarıda kabul İlavesi Durumunda §6.3p3 tarafından vermeye almaması nedeniyle, +0.0.

Tüm durumlar için girdi değeri çıktı olarak yasal olduğundan x-0.0, x == x-0.0işlemsiz ve totolojinin dikkate alınmasına izin verilir .

Değeri Değiştiren Optimizasyonlar

IEEE 754-2008 Standardı aşağıdaki ilginç alıntıya sahiptir:

IEEE 754 § 10.4 Birebir anlam ve değer değiştiren optimizasyonlar

[...]

Aşağıdaki değer değiştiren dönüşümler, diğerlerinin yanı sıra, kaynak kodun gerçek anlamını korur:

  • X sıfır olmadığında ve bir sinyal NaN olmadığında ve sonuç x ile aynı üsse sahip olduğunda, 0 + x kimlik özelliğini uygulamak.
  • X bir sinyal NaN olmadığında ve sonuç x ile aynı üsse sahip olduğunda 1 × x kimlik özelliğini uygulamak.
  • Sessiz bir NaN'nin yükünü veya işaret bitini değiştirme.
  • [...]

Tüm NaN'ler ve tüm sonsuzluklar aynı üssü paylaştığından x+0.0ve x*1.0sonlu için doğru yuvarlatılmış sonuç xtam olarak aynı büyüklüğe sahip xolduğundan, üsleri aynıdır.

sNaNs

Sinyalizasyon NaN'leri kayan nokta tuzak değerleridir; Kayan noktalı işlenen olarak kullanılması geçersiz bir işlem istisnasına (SIGFPE) neden olan özel NaN değerleridir. Bir istisnayı tetikleyen bir döngü optimize edilmiş olsaydı, yazılım artık aynı şekilde davranmazdı.

Bununla birlikte, user2357112'nin yorumlarda işaret ettiği gibi, C11 Standardı, NaN'lerin ( sNaN) sinyalizasyonunun davranışını açıkça tanımsız bırakır , böylece derleyicinin bunların meydana gelmediğini varsaymasına izin verilir ve böylece ortaya çıkan istisnalar da meydana gelmez. C ++ 11 standardı, NaN'leri işaretlemek için bir davranışı tanımlamaz ve bu nedenle onu tanımsız bırakır.

Yuvarlama Modları

Alternatif yuvarlama modlarında, izin verilen optimizasyonlar değişebilir. Örneğin, Round-to-Negative-Infinity modunda, optimizasyona x+0.0 -> xizin verilebilir, ancakx-0.0 -> x yasaklanır.

GCC'nin varsayılan yuvarlama modlarını ve davranışlarını üstlenmesini önlemek için deneysel bayrak -frounding-mathGCC'ye geçirilebilir.

Sonuç

Clang ve GCC , olsa bile -O3IEEE-754 uyumludur. Bu, IEEE-754 standardının yukarıdaki kurallarına uyması gerektiği anlamına gelir. x+0.0olduğu bit aynı değil için xtüm xbu kurallar altında, ama x*1.0 böyle olması tercih edilebilir : ne zaman, Yani

  1. xBir NaN olduğunda , yükün değiştirilmeden geçirilmesi önerisine uyun .
  2. NaN sonucunun işaret bitini ile değiştirmeden bırakın * 1.0.
  3. Zaman, işaret biti bir bölüm / ürün esnasında XOR emre itaat xolduğunu değil bir NaN.

IEEE-754-güvenli olmayan optimizasyonu etkinleştirmek için (x+0.0) -> x, bayrağın -ffast-mathClang veya GCC'ye geçirilmesi gerekir.


2
Uyarı: Ya sinyal veren bir NaN ise? (Aslında bunun bir şekilde neden olabileceğini düşünmüştüm, ama gerçekten nasıl olduğunu bilmiyordum, bu yüzden sordum.)
user541686

6
@Mehrdad: IEEE 754'e C uyumu belirten C standardının (isteğe bağlı) bölümü olan Annex F, açıkça sinyal veren NaN'leri kapsamaz. (C11 F.2.1., İlk satır: "Bu belirtim, NaN'lerin sinyalleşme davranışını tanımlamaz.") Ek F'ye uygunluğu beyan eden uygulamalar, NaN'lerin sinyal göndermesiyle istediklerini yapmakta serbest kalır. C ++ standardının kendi IEEE 754 idaresi vardır, ancak ne olursa olsun (aşina değilim), NaN davranışını da işaret ettiğinden şüpheliyim.
user2357112,

2
@Mehrdad: sNaN, standarda göre tanımsız davranışı çağırır (ancak muhtemelen platform tarafından iyi tanımlanmıştır), böylece derleyicinin burada ezilmesine izin verilir.
Joshua

1
@ user2357112: Aksi takdirde kullanılmayan hesaplamalar için bir yan etki olarak hata yakalama olasılığı genellikle pek çok optimizasyona müdahale eder; Bir hesaplamanın sonucu bazen göz ardı edilirse, bir derleyici, sonucun kullanılıp kullanılmayacağını bilene kadar hesaplamayı faydalı bir şekilde erteleyebilir, ancak hesaplama önemli bir sinyal üretmişse, bu kötü olabilir.
supercat

2
Bakın, hem C hem de C ++ için geçerli olan ve tek bir standarda referansla her iki dil için de doğru şekilde yanıtlanan bir soru . Bu, soru bir dil ortaklığıyla ilgiliyken bile, insanların hem C hem de C ++ ile etiketlenen sorulardan şikayet etme olasılığını düşürür mü? Maalesef sanmıyorum.
Kyle Strand

35

x += 0.0eğer bir NOOP değildir xolduğunu -0.0. Yine de sonuçlar kullanılmadığından, optimize edici tüm döngüyü kaldırabilir. Genel olarak, bir optimize edicinin aldığı kararları neden aldığını söylemek zordur.


2
Ben sonra ben aslında bu yayınlanmıştır sadece neden okumak x += 0.0bir no-op değildir, ama ben tüm döngü her iki şekilde dışarı optimize edilmelidir, çünkü bu muhtemelen neden değildir düşündüm.
Satın alabilirim

Nesne yönelimli dillerin yan etkiler üretme eğilimi göz önüne alındığında, optimize edicinin gerçek davranışı değiştirmediğinden emin olmanın zor olacağını düşünürdüm.
Robert Harvey

Bunun nedeni, long longoptimizasyonun etkin olması nedeniyle olabilir (gcc ile mi, en azından iki kez aynı davranır )
e2-e4

2
@ ringø: long longbir IEEE754 türü değil, integral bir türdür.
MSalters

1
Peki ya x -= 0, aynı mı?
Viktor Mellgren
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.