Большинство проблем научных вычислений, которые нам необходимо решить путем реализации конкретного алгоритма в C / C ++, требуют точности, которая намного ниже, чем двойная точность. Например, 1e-6
, 1e-7
точность покрытия 99%
случаев для решения ОДУ или численного интегрирования. Даже в тех редких случаях, когда нам нужна более высокая точность, как правило, сам численный метод терпит неудачу, прежде чем мы можем мечтать о достижении точности, близкой к двойной точности. Пример: мы не можем ожидать точности 1e-16 от простого метода Рунге – Кутты даже при решении стандартного обыкновенного дифференциального уравнения Ностифа из-за ошибок округления. В этом случае требование двойной точности аналогично требованию лучшего приближения неправильного ответа.
Кроме того, агрессивная оптимизация с плавающей запятой в большинстве случаев представляется беспроигрышной ситуацией, поскольку она ускоряет ваш код (намного быстрее!) И не влияет на целевую точность вашей конкретной задачи. Тем не менее, кажется невероятно трудным убедиться, что конкретная реализация / код устойчива к оптимизации fp. Классический (и несколько тревожный) пример: GSL, научная библиотека GNU, является не только стандартной числовой библиотекой на рынке, но и очень хорошо написанной библиотекой (я не могу себе представить, что делаю лучше). Однако GSL не стабилен против fp-оптимизаций. Фактически, если вы компилируете GSL, например, с помощью компилятора intel, его внутренние тесты не пройдут, если вы не включите -fp-model strict
флаг, который отключает fp оптимизации.
Таким образом, мой вопрос: существуют ли общие рекомендации по написанию кода, устойчивого к агрессивным оптимизациям с плавающей запятой. Являются ли эти рекомендации специфическими для языка (компилятора). Если да, то каковы лучшие практики C / C ++ (gcc / icc)?
Примечание 1: Этот вопрос не задает вопрос о том, каковы флаги оптимизации fp в gcc / icc.
Примечание 2: этот вопрос не касается общих рекомендаций по оптимизации на C / C ++ (например, не используйте виртуальные функции для небольших функций, которые вызываются много).
Примечание 3: Этот вопрос не задает список большинства стандартных оптимизаций fp (например, x / x -> 1).
Примечание 4: Я твердо верю, что это НЕ субъективный / не по теме вопрос, похожий на классический «Самые крутые имена серверов». Если вы не согласны (потому что я не предоставляю конкретный пример / код / проблему), отметьте это как вики сообщества. Меня гораздо больше интересует ответ, чем получение нескольких статусных очков (не они не важны — вы получите точку!).
Производители компиляторов оправдывают -ffast-math
вид оптимизаций с утверждением, что влияние этих оптимизаций на численно устойчивые алгоритмы минимален
Поэтому, если вы хотите написать код, устойчивый к этим оптимизациям, достаточным условием является написание только численно стабильного кода.
Теперь ваш вопрос может звучать так: «Как мне написать численно устойчивый код?». Здесь ваш вопрос может быть немного широким: есть целые книги, посвященные этой теме. На странице Википедии, на которую я уже ссылался, есть хороший пример, и Вот еще один хороший. Я не мог бы рекомендовать книгу, в частности, это не моя область знаний.
Примечание 1: Желательность численной стабильности выходит за рамки оптимизации компилятора. Если у вас есть выбор, напишите численно устойчивый код, даже если вы не планируете использовать -ffast-math
стиль оптимизации. Численно нестабильный код может давать неправильные результаты даже при компиляции со строгой семантикой IEEE 754 с плавающей запятой.
Примечание 2: вы не можете ожидать, что внешние библиотеки будут работать при компиляции с -ffast-math
Флаги Этим библиотекам, написанным экспертами с плавающей запятой, может потребоваться выполнить тонкие хитрости со свойствами вычислений IEEE 754. Этот вид трюка может быть сломан -ffast-math
оптимизации, но они улучшают производительность больше, чем можно было ожидать от компилятора, даже если вы позволите. Для вычислений с плавающей точкой эксперт со знанием предметной области каждый раз побеждает компилятор. На примере среди многих — реализация с тройным двойным CRlibm. Этот код ломается, если он не скомпилирован со строгой семантикой IEEE 754. Другой, более элементарный алгоритм, который нарушает оптимизацию компилятора, Суммирование Кахана: при компиляции с небезопасными оптимизациями, c = (t - sum) - y
оптимизирован для c = 0
, Это, конечно, полностью противоречит цели алгоритма.
Других решений пока нет …