Ефективна стабільна сума упорядкованих чисел


12

У мене досить довгий список позитивних чисел з плаваючою комою ( std::vector<float>, розмір ~ 1000). Числа сортуються у порядку зменшення. Якщо я підсумую їх у порядку:

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

Я думаю, у мене може виникнути проблема чисельної стабільності, оскільки близько до кінця вектора sumбуде набагато більше, ніж v. Найпростішим рішенням було б переміщення вектора в зворотному порядку. Моє запитання: чи настільки ефективна, як і пряма справа? У мене буде більше кешу?

Чи є якесь інше розумне рішення?


1
На питання швидкості легко відповісти. Визначте це.
Девіде Спартаро

Чи швидкість важливіша за точність?
Сувора

Не зовсім дублікат, але дуже схожий питання: сума серій із використанням float
acraig5075

4
Можливо, вам доведеться звернути увагу на негативні числа.
AProgrammer

3
Якщо ви насправді дбаєте про точність до високих градусів, ознайомтеся з підсумками Кахана .
Макс Ленгоф

Відповіді:


3

Я думаю, у мене може бути якась проблема чисельності стабільності

Тож тестуйте на це. В даний час у вас є гіпотетична проблема, що означає, зовсім не проблема.

Якщо ви протестуєте, і гіпотетичне перетворюється на актуальну проблему, то вам слід потурбуватися про те, щоб її фактично виправити.

Тобто - точність з плаваючою комою може спричинити проблеми, але ви можете підтвердити, чи дійсно це стосується ваших даних, перш ніж визначити їх пріоритетними перед усім.

... У мене буде більше кешу?

Тисяча плавців становить 4 Кб - це вміститься в кеш-пам'яті в сучасній системі масового ринку (якщо ви маєте на увазі іншу платформу, скажіть, що це таке).

Єдиний ризик полягає в тому, що префетер не допоможе вам при повторенні назад, але, звичайно, ваш вектор може вже знаходитися в кеші. Ви дійсно не можете визначити це, доки ви не профайлюватимете в контексті вашої повної програми, тому немає ніякої користі з цього приводу, поки не будете мати повну програму.

Чи є якесь інше розумне рішення?

Не турбуйтеся про речі, які можуть стати проблемами, поки вони насправді не стануть проблемами. У крайньому випадку варто відзначити можливі проблеми та структурувати свій код так, щоб ви могли замінити найпростіше можливе рішення ретельно оптимізованим пізніше, не переписуючи все інше.


5

Я відмітив позначку вашої справи використання, і результати (див. Додане зображення) вказують на той напрямок, який не має різниці в продуктивності для циклу вперед або назад.

Ви можете також виміряти апаратний + компілятор.


Використання STL для виконання суми відбувається так само швидко, як і вручну, перебираючи дані, але набагато виразніше.

використовувати для зворотного накопичення:

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

в той час як для накопичення вперед:

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

введіть тут опис зображення


що веб-сайт дуже крутий. Просто для впевненості: ви не приурочуєте до випадкового покоління, правда?
Ruggero Turra

Ні, тільки частина в stateциклі приурочена.
Девід Спатаро

2

Найпростішим рішенням було б переміщення вектора в зворотному порядку. Моє запитання: чи настільки ефективна, як і пряма справа? У мене буде більше кешу?

Так, це ефективно. Прогнозування філії та стратегія розумного кешування з вашого обладнання налаштовуються на послідовний доступ. Ви можете сміливо накопичувати свій вектор:

#include <numeric>

auto const sum = std::accumulate(crbegin(v), crend(v), 0.f);

2
Чи можете ви уточнити: у цьому контексті "послідовний доступ" означає вперед, назад або те й інше?
Ruggero Turra

1
@RuggeroTurra Я не можу, якщо не можу знайти джерело, і я не в настрої читати таблиці даних ЦП прямо зараз.
ВАТ

@RuggeroTurra Зазвичай послідовний доступ означатиме форварди. Усі напівпристойні попередньо налаштовані оператори пам'яті забезпечують послідовний доступ уперед.
Зубна щітка

@Toothbrush, дякую. Отже, якщо я обернусь назад, в принципі, це може бути проблема виступу
Ruggero Turra

В принципі, по крайней мере , деякі апаратні засоби, якщо весь вектор не є вже в кеші L1.
Марно

2

Для цього ви можете використовувати зворотний ітератор без жодних транспозицій у вашому std::vector<float> vec:

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

Або виконайте ту саму роботу, використовуючи стандартний алгоритм:

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

Продуктивність повинна бути однаковою, змінювати лише напрямок обходу вашого вектора


Виправте мене, якщо я помиляюся, але я вважаю, що це навіть ефективніше, ніж використовує заяву Foreach, оскільки воно вводить накладні витрати. YSC має рацію щодо частини числової стійкості, тхо.
Сефірот

4
@sephiroth Ні, будь-який напівпристойний компілятор не буде дуже байдужим, ви написали діапазон для або ітератор для.
Макс Лангхоф

1
Результативність у реальному світі, як правило, не гарантується однаковою завдяки кешам / попередньому вибору. Розумно, що ОП обережно ставиться до цього.
Макс Ленгоф

1

Якщо під числовою стійкістю ви маєте на увазі точність, то так, у вас можуть виникнути проблеми з точністю. Залежно від співвідношення найбільших до найменших значень та ваших вимог щодо точності в результаті, це може бути або не бути проблемою.

Якщо ви хочете мати високу точність, тоді врахуйте підсумовування Кахана - для компенсації помилок використовується додатковий поплавок. Існує також парне підсумовування .

Детальний аналіз компромісу між точністю та часом див. У цій статті .

ОНОВЛЕННЯ для C ++ 17:

Декілька інших відповідей згадують std::accumulate. Оскільки на C ++ 17 існують політики виконання, які дозволяють алгоритми паралелізувати.

Наприклад

#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';
}

Це повинно зробити підсумовування великих наборів даних швидше ціною помилок недетермінованого округлення (я припускаю, що користувач не зможе визначити розділення потоку).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.