Mengapa Dentang mengoptimalkan pengulangan dalam kode ini
#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);
}
tetapi bukan loop dalam kode ini?
#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);
}
(Menandai C dan C ++ karena saya ingin tahu apakah jawabannya berbeda untuk masing-masing.)
c++
c
optimization
floating-point
clang
pengguna541686
sumber
sumber
-O3
, saya tidak tahu bagaimana memeriksa apa yang diaktifkan.static double arr[N]
tidak diizinkan dalam C;const
variabel tidak dihitung sebagai ekspresi konstan dalam bahasa ituJawaban:
Standar IEEE 754-2008 untuk Floating-Point Arithmetic dan ISO / IEC 10967 Language Independent Arithmetic (LIA) Standard, Bagian 1 menjawab mengapa demikian.
Kasus Penambahan
Di bawah mode pembulatan standar (Round-to-Nearest, Ties-to-Even) , kita melihat bahwa
x+0.0
menghasilkanx
, KECUALI ketikax
adalah-0.0
: Dalam hal itu kita memiliki jumlah dua operan dengan tanda-tanda berlawanan yang jumlahnya adalah nol, dan §6,3 paragraf 3 aturan yang dihasilkan tambahan ini+0.0
.Karena bitwise
+0.0
tidak identik dengan aslinya , dan itu adalah nilai yang sah yang dapat terjadi sebagai input, kompiler wajib memasukkan kode yang akan mengubah nol potensial menjadi negatif .-0.0
-0.0
+0.0
Ringkasan: Di bawah mode pembulatan default, dalam
x+0.0
, jikax
-0.0
, makax
itu sendiri adalah nilai output yang dapat diterima.-0.0
, maka nilai output harus+0.0
, yang tidak identik dengan bitwise-0.0
.Kasus Multiplikasi
Di bawah mode pembulatan default , tidak ada masalah seperti itu terjadi
x*1.0
. Jikax
:x*1.0 == x
selalu.+/- infinity
, maka hasilnya adalah+/- infinity
dari tanda yang sama.adalah
NaN
, maka menurutyang berarti bahwa eksponen dan mantissa (meskipun tidak tanda) dari
NaN*1.0
yang dianjurkan tidak berubah dari inputNaN
. Tanda tidak ditentukan sesuai dengan §6.3p1 di atas, tetapi implementasi dapat menetapkannya identik dengan sumberNaN
.+/- 0.0
, maka hasilnya adalah0
dengan bit tanda XOR dengan bit tanda1.0
, sesuai dengan §6.3p2. Karena bit tanda1.0
adalah0
, nilai output tidak berubah dari input. Jadi,x*1.0 == x
bahkan ketikax
nol (negatif).Kasus Pengurangan
Di bawah mode pembulatan default , pengurangan
x-0.0
juga merupakan larangan, karena setara denganx + (-0.0)
. Jikax
adaNaN
, maka §6.3p1 dan §6.2.3 berlaku dengan cara yang sama seperti untuk penambahan dan penggandaan.+/- infinity
, maka hasilnya adalah+/- infinity
dari tanda yang sama.x-0.0 == x
selalu.-0.0
, maka oleh §6.3p2 kita memiliki " [...] tanda penjumlahan, atau perbedaan x - y dianggap sebagai penjumlahan x + (fromy), berbeda dari paling banyak salah satu dari tanda tambahan; ". Ini memaksa kita untuk menetapkan-0.0
sebagai hasil dari(-0.0) + (-0.0)
, karena-0.0
berbeda dalam tanda dari tidak ada tambahan, sementara+0.0
berbeda dalam tanda dari dua dari tambahan, yang melanggar pasal ini.+0.0
, maka ini mengurangi kasus penambahan yang(+0.0) + (-0.0)
dipertimbangkan di atas dalam Kasus Penambahan , yang oleh §6.3p3 diperintahkan untuk diberikan+0.0
.Karena untuk semua kasus, nilai input legal sebagai output, diizinkan untuk mempertimbangkan
x-0.0
larangan, danx == x-0.0
tautologi.Optimalisasi Perubahan Nilai
Standar IEEE 754-2008 memiliki kutipan menarik berikut:
Karena semua NaN dan semua infinities berbagi eksponen yang sama, dan hasil benar bulat
x+0.0
danx*1.0
untuk yang terbatasx
memiliki tepat besarnya samax
, eksponen mereka adalah sama.sNaNs
Signalling NaNs adalah nilai perangkap floating-point; Mereka adalah nilai NaN khusus yang digunakan sebagai operan titik-mengambang menghasilkan pengecualian operasi tidak valid (SIGFPE). Jika loop yang memicu pengecualian dioptimalkan, perangkat lunak tidak akan lagi berperilaku sama.
Namun, seperti yang ditunjukkan oleh user2357112 dalam komentar , Standar C11 secara eksplisit membiarkan perilaku pensinyalan NaNs (
sNaN
), sehingga penyusun diizinkan untuk menganggap mereka tidak terjadi, dan dengan demikian pengecualian yang mereka ajukan juga tidak terjadi. C ++ 11 menghilangkan standar yang menggambarkan perilaku untuk memberi sinyal NaN, dan dengan demikian juga membiarkannya tidak terdefinisi.Mode Pembulatan
Dalam mode pembulatan alternatif, optimisasi yang diizinkan dapat berubah. Misalnya, dalam mode Round-to-Negative-Infinity , optimasi
x+0.0 -> x
menjadi diizinkan, tetapix-0.0 -> x
menjadi terlarang.Untuk mencegah GCC dari mengasumsikan mode dan perilaku pembulatan default, bendera eksperimental
-frounding-math
dapat diteruskan ke GCC.Kesimpulan
Dentang dan GCC , bahkan pada
-O3
, tetap sesuai IEEE-754. Ini berarti harus mematuhi aturan standar IEEE-754 di atas.x+0.0
adalah tidak bit-identik untukx
semuax
di bawah aturan-aturan, tetapix*1.0
dapat dipilih untuk menjadi begitu : Yaitu, ketika kitax
ketika itu adalah NaN.* 1.0
.x
ini tidak NaN.Untuk mengaktifkan pengoptimalan yang tidak aman dari IEEE-754
(x+0.0) -> x
, flag tersebut-ffast-math
harus diteruskan ke Dentang atau GCC.sumber
x += 0.0
bukan NOOP jikax
ada-0.0
. Pengoptimal tetap bisa menghapus seluruh loop karena hasilnya tidak digunakan. Secara umum, sulit untuk mengatakan mengapa pengoptimal membuat keputusan.sumber
x += 0.0
bukan no-op, namun saya pikir itu mungkin bukan alasan karena seluruh loop harus dioptimalkan dengan cara baik. Saya dapat membelinya, hanya saja tidak sepenuhnya meyakinkan seperti yang saya harapkan ...long long
optimasi berlaku (melakukannya dengan gcc, yang berperilaku sama untuk setidaknya dua kali lipat )long long
adalah tipe integral, bukan tipe IEEE754.x -= 0
, apakah sama?