Невеликі, непередбачувані результати в роботі детермінованої моделі


10

У мене велика модель (~ 5000 рядків), написана на C. Це серійна програма, де ніде не утворюється випадкових чисел. Він використовує бібліотеку FFTW для функцій, що використовують FFT - я не знаю деталей реалізації FFTW, але я припускаю, що функції в ньому також детерміновані (виправте мене, якщо я помиляюся).

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

Я використовую змінні з подвійною точністю, і для виведення результату в змінну, valueнаприклад, видаю: fprintf(outFID, "%.15e\n", value);або
fwrite(&value, 1, sizeof(double), outFID);

І я постійно отримував би різниці, такі як:
2.07843469652206 4 e-16 проти 2.07843469652206 3 e-16

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

Що може бути причиною цього? Зараз це невелике питання, але мені цікаво, чи це "верхівка айсберга" (серйозна проблема).

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

Продовження коментарів:
Крістіан Класон та Вікрам: по-перше, дякую за увагу до мого запитання. У статтях, до яких ви посилаєтесь, висловлюється думка, що: 1. помилки округлення обмежують точність, і 2. різний код (наприклад, введення начебто нешкідливих тверджень про друк) може впливати на результати аж до епсилону машини. Я повинен уточнити, що я не порівнюю ефекти fwriteта fprintfфункції. Я використовую одне АБО інше. Зокрема, для обох запусків використовується однаковий виконуваний файл. Я просто констатую, що питання виникає, чи використовую я fprintfАБО fwrite.

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

21016

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


21016

Ви запитуєте, чому ваша машина не точніша за точність машини. en.wikipedia.org/wiki/Machine_epsilon
Вікрам

1
Див. Inf.ethz.ch/personal/gander/Heisenberg/paper.html для пов'язаного прикладу тонкого впливу кодових шляхів на арифметику з плаваючою комою. І, звичайно, ece.uwaterloo.ca/~dwharder/NumericalAnalysis/02Numerics/Double/…
Крістіан Классон

1
1016

2
1

Відповіді:


9

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

Приклад того, що може піти не так, спираючись на власний досвід. Розглянемо задачу обчислення крапкового добутку двох векторів x і y.

d=i=1nxiyi

xiyi

Наприклад, ви можете спершу обчислити добуток двох векторів як

d=((x1y1)+(x2y2))+(x3y3)

а потім як

d=(x1y1)+((x2y2)+(x3y3))

Як це могло статися? Ось дві можливості.

  1. Багатопотокові обчислення на паралельних ядрах. Сучасні комп'ютери зазвичай мають 2, 4, 8 або навіть більше процесорних ядер, які можуть працювати паралельно. Якщо ваш код використовує паралельні потоки для обчислення точкового продукту на кількох процесорах, то будь-яке випадкове збурення системи (наприклад, користувач перемістив мишу, і одне з ядер процесора повинно обробити цей рух миші, перш ніж повернутися до крапкового продукту). призводять до зміни порядку доповнень.

  2. Розміщення даних та векторних інструкцій. Сучасні процесори Intel мають спеціальний набір інструкцій, який може працювати (наприклад) для цифр з плаваючою комою одночасно. Ці векторні інструкції найкраще працюють, якщо дані вирівняні на 16-ти байтових межах. Як правило, цикл крапкових продуктів розбиває дані на розділи з 16 байтів (4 поплавці одночасно.) Якщо ви повторно повторюєте код, ці дані можуть бути вирівняні по-різному з 16-ти байтними блоками пам'яті, щоб додатки були виконується в іншому порядку, в результаті чого виникає інша відповідь.

Ви можете звернутися до точки 1, змусивши ваш код запускатись як єдиний потік і відключивши всю паралельну обробку. Ви можете звернутися до точки 2, вимагаючи виділення пам’яті для вирівнювання блоків пам’яті (як правило, ви робите це, компілюючи код за допомогою перемикача, наприклад -align.) Якщо ваш код все ще дає результати, які змінюються, то є інші можливості подивитися у.

Ця документація від Intel обговорює проблеми, які можуть призвести до невідтворюваності результатів за допомогою бібліотеки ядер Intel Math Kernel. Ще один документ від Intel, в якому обговорюються перемикачі компіляторів, які використовуються з компіляторами Intel.


Я бачу, що ви думаєте, що ваш код працює однопотоково. Хоча ви, напевно, добре знаєте свій код, я не був би здивований, якби ви викликали підпрограми (наприклад, підпрограми BLAS), які працюють багатопотоково. Слід перевірити, які саме бібліотеки ви використовуєте. Ви також можете скористатися інструментами системного моніторингу, щоб переглянути ваше використання процесора.
Брайан Борчерс

1
або, як було сказано, бібліотека FFTW ...
Крістіан Класон

@BrianBorchers, дякую. Приклад випадковості, що виникає з неасоціативного характеру додавання плаваючої точки, є просвітлюючим. Крістіан Класон висунув другорядне питання про те, чи є важливий вихід моєї моделі, враховуючи масштабність чисел - це може бути головним питанням, якщо він має рацію (і я його правильно розумію), тому я зараз розглядаю це питання.
boxofchalk1

2

Згадана бібліотека FFTW може працювати в недетермінованому режимі.

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


1

Хоча це правда, що зміни порядку замовлення термінів виразів можуть дуже часто відбуватися через багатоядерні / багатопотокові сценарії обробки, не забувайте, що можуть існувати (хоча це і тривалий час) якийсь недолік дизайнерського обладнання в роботі. Пам'ятаєте проблему Pentium FDIV? (Див. Https://en.wikipedia.org/wiki/Pentium_FDIV_bug ). Деякий час тому я працював над програмним забезпеченням для моделювання аналогових схем на ПК. Частина нашої методології включала розробку наборів регресійних тестів, які ми б протистояли нічним складанням програмного забезпечення. Для багатьох моделей, які ми розробили, ітераційні методи (наприклад, Ньютон-Рафсон ( https://en.wikipedia.org/wiki/Newton%27s_method) та Runge-Kutta) широко використовувались в алгоритмах моделювання. З аналоговими пристроями часто трапляються випадки, коли внутрішні артефакти, такі як напруги, струми тощо, мають надзвичайно малі числові значення. Ці значення, як частина процесу моделювання, поступово змінюються протягом (імітованого) часу. Масштаби цих змін можуть бути надзвичайно малі, і те, що ми часто спостерігали, - це те, що наступні операції ФПУ на таких значеннях дельти межують з порогом "шуму" точності FPU (64-бітний плаваючий має 53-бітну мантісу, IIRC). Це в поєднанні з тим, що нам часто доводилося впроваджувати журнальний код "PrintF" у моделі, щоб дозволити налагодження (ах, добрі дні!), Практично гарантованих спорадичні результати, щодня! І що' s все це означає? За таких обставин ви повинні розраховувати на відмінності, і найкраще, що потрібно зробити, - це визначити та застосувати спосіб вирішити (масштабність, частота, тенденція тощо), коли / як їх ігнорувати.


Дякую тобі, Джиме за розуміння. Будь-які уявлення про те, які фундаментальні явища спричинили б такі "внутрішні артефакти"? Я думав, що електромагнітні перешкоди можуть бути одними, але тоді значні біти також вплинуть, чи не так?
boxofchalk1

1

Хоча округлення плаваючої точки від операцій асинхронізації може бути проблемою, я підозрюю, що це щось більш банальне. Використання неініціалізованої змінної, яка додає випадковості вашому інакше детермінованому коду. Це поширена проблема, яку розробники часто не помічають, оскільки при запуску в режимі налагодження всі змінні ініціалізуються до 0 при оголошенні. Якщо не працює в режимі налагодження, пам'ять, присвоєна змінній, має будь-яке значення, яке пам'ять мала до призначення. Пам'ять не присвоюється нулю при призначенні як оптимізації. Якщо це відбувається у вашому коді, це буде легко виправити, тим більше в коді бібліотек.

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