Jumlah stabil yang efisien dari nomor yang dipesan

12

Saya memiliki daftar angka positif floating point yang cukup panjang ( std::vector<float>, ukuran ~ 1000). Angka-angka diurutkan dalam mengurangi pemesanan. Jika saya menjumlahkan mereka mengikuti pesanan:

for (auto v : vec) { sum += v; }

Saya kira saya dapat memiliki beberapa masalah stabilitas numerik, karena mendekati akhir vektor sumakan jauh lebih besar daripada v. Solusi termudah adalah dengan melintasi vektor dalam urutan terbalik. Pertanyaan saya adalah: apakah itu efisien serta kasus maju? Saya akan memiliki lebih banyak cache yang hilang?

Apakah ada solusi pintar lainnya?

Ruggero Turra
sumber
1
Pertanyaan kecepatan mudah dijawab. Benchmark it.
Davide Spataro
Apakah kecepatan lebih penting daripada akurasi?
stark
Bukan duplikat, tapi pertanyaan yang sangat mirip: jumlah seri menggunakan float
acraig5075
4
Anda mungkin harus memperhatikan angka negatif.
Pemrogram
3
Jika Anda benar-benar peduli tentang presisi hingga derajat tinggi, periksa penjumlahan Kahan .
Max Langhof

Jawaban:

3

Saya kira saya dapat memiliki beberapa masalah stabilitas numerik

Jadi tes untuk itu. Saat ini Anda memiliki masalah hipotetis, yaitu, tidak ada masalah sama sekali.

Jika Anda menguji, dan hipotetis terwujud menjadi masalah aktual , maka Anda harus khawatir benar-benar memperbaikinya.

Yaitu - presisi floating-point dapat menyebabkan masalah, tetapi Anda dapat mengonfirmasi apakah itu benar-benar cocok untuk data Anda, sebelum memprioritaskan hal itu di atas segalanya.

... Saya akan memiliki lebih banyak cache yang hilang?

Seribu pelampung adalah 4Kb - itu akan masuk dalam cache pada sistem pasar massal modern (jika Anda memiliki platform lain dalam pikiran, beri tahu kami apa itu).

Satu-satunya risiko adalah bahwa prefetcher tidak akan membantu Anda saat iterasi mundur, tetapi tentu saja vektor Anda mungkin sudah ada dalam cache. Anda tidak dapat benar-benar menentukan ini sampai profil Anda dalam konteks program lengkap Anda, jadi tidak ada gunanya mengkhawatirkannya sampai Anda memiliki program lengkap.

Apakah ada solusi pintar lainnya?

Jangan khawatir tentang hal-hal yang mungkin menjadi masalah, sampai mereka benar-benar menjadi masalah. Paling-paling ada baiknya diperhatikan kemungkinan masalah, dan penataan kode Anda sehingga Anda dapat mengganti solusi yang paling sederhana dengan yang dioptimalkan dengan hati-hati nanti, tanpa menulis ulang yang lainnya.

Tak berguna
sumber
5

Saya menandai penggunaan case Anda dan hasilnya (lihat gambar terlampir) menunjuk ke arah bahwa itu tidak membuat perbedaan kinerja untuk loop maju atau mundur.

Anda mungkin ingin mengukur pada kompiler perangkat keras + Anda juga.


Menggunakan STL untuk melakukan penjumlahan itu secepat pengulangan manual atas data tetapi jauh lebih ekspresif.

gunakan yang berikut ini untuk akumulasi terbalik:

std::accumulate(rbegin(data), rend(data), 0.0f);

sedangkan untuk akumulasi ke depan:

std::accumulate(begin(data), end(data), 0.0f);

masukkan deskripsi gambar di sini

Davide Spataro
sumber
situs web itu sangat keren. Hanya untuk memastikan: Anda tidak menghitung waktu generasi acak, bukan?
Ruggero Turra
Tidak, hanya bagian dalam stateloop yang diberi batas waktu.
Davide Spataro
2

Solusi termudah adalah dengan melintasi vektor dalam urutan terbalik. Pertanyaan saya adalah: apakah itu efisien serta kasus maju? Saya akan memiliki lebih banyak cache yang hilang?

Ya itu efisien. Prediksi cabang dan strategi cache cerdas dari perangkat keras Anda disesuaikan untuk akses berurutan. Anda dapat dengan aman mengumpulkan vektor Anda:

#include <numeric>

auto const sum = std::accumulate(crbegin(v), crend(v), 0.f);
YSC
sumber
2
Bisakah Anda mengklarifikasi: dalam konteks ini "akses sekuensial" berarti maju, mundur, atau keduanya?
Ruggero Turra
1
@ RagugoTurra Saya tidak bisa kecuali saya dapat menemukan sumber, dan saya tidak berminat untuk membaca lembar data CPU sekarang.
YSC
@ RuggerTurra Biasanya akses berurutan berarti maju. Semua prefetcher memori semi-layak menangkap akses berurutan.
Sikat gigi
@ Brushbrush, terima kasih. Jadi, jika saya memutar ke belakang, pada prinsipnya, itu bisa menjadi masalah kinerja
Ruggero Turra
Pada prinsipnya, pada setidaknya beberapa perangkat keras, jika seluruh vektor belum ada di L1 cache.
berguna
2

Untuk tujuan ini, Anda dapat menggunakan reverse iterator tanpa transposisi di std::vector<float> vec:

float sum{0.f};
for (auto rIt = vec.rbegin(); rIt!= vec.rend(); ++rIt)
{
    sum += *rit;
}

Atau lakukan pekerjaan yang sama menggunakan algortitme standar:

float sum = std::accumulate(vec.crbegin(), vec.crend(), 0.f);

Kinerja harus sama, hanya diubah arah memotong vektor Anda

Malov Vladimir
sumber
Koreksi saya jika saya salah, tapi saya pikir ini bahkan lebih efisien daripada pernyataan OP pendahuluan menggunakan, karena memperkenalkan overhead. YSC benar tentang bagian stabilitas numerik, tho.
Sephiroth
4
@sephiroth Tidak, kompiler setengah layak mana pun tidak akan peduli apakah Anda menulis range-for atau iterator untuk.
Max Langhof
1
Performa dunia nyata jelas tidak dijamin sama, karena cache / prefetching. Wajar bagi OP untuk mewaspadai hal itu.
Max Langhof
1

Jika dengan stabilitas numerik yang Anda maksud adalah akurasi, maka ya, Anda mungkin berakhir dengan masalah akurasi. Bergantung pada rasio nilai terbesar ke nilai terkecil, dan persyaratan Anda untuk akurasi dalam hasil, ini mungkin atau mungkin tidak menjadi masalah.

Jika Anda ingin memiliki akurasi tinggi, maka pertimbangkan penjumlahan Kahan - ini menggunakan pelampung tambahan untuk kompensasi kesalahan. Ada juga penjumlahan berpasangan .

Untuk analisis terperinci tentang tradeoff antara akurasi dan waktu, lihat artikel ini .

PEMBARUAN untuk C ++ 17:

Beberapa jawaban lain menyebutkan std::accumulate. Sejak C ++ 17 ada kebijakan eksekusi yang memungkinkan algoritma diparalelkan.

Contohnya

#include <vector>
#include <execution>
#include <iostream>
#include <numeric>

int main()
{  
   std::vector<double> input{0.1, 0.9, 0.2, 0.8, 0.3, 0.7, 0.4, 0.6, 0.5};

   double reduceResult = std::reduce(std::execution::par, std::begin(input), std::end(input));

   std:: cout << "reduceResult " << reduceResult << '\n';
}

Ini harus membuat menjumlahkan dataset besar lebih cepat dengan biaya kesalahan pembulatan nondeterministik (saya berasumsi bahwa pengguna tidak akan dapat menentukan partisi thread).

Paul Floyd
sumber