Подтвердить что ты не робот

Должен ли я использовать NaN с плавающей запятой или с плавающей точкой + bool для набора данных, который содержит недопустимые значения?

У меня есть большой объем данных для обработки с интенсивными математическими операциями для каждого набора данных. Большая часть из них аналогична обработке изображений. Однако, поскольку эти данные считываются непосредственно с физического устройства, многие значения пикселей могут быть недействительными.

Это делает свойство NaN для представления значений, которые не являются числом, и распространяется на арифметические операции очень убедительно. Однако, похоже, также требуется отключить некоторые оптимизации, такие как gcc -ffast-math, плюс нам нужно быть кросс-платформой. В нашем текущем проекте используется простая структура, содержащая значение float и bool, указывающее на достоверность.

Пока он кажется NaN был разработан с учетом этого использования, другие считают, что это больше проблем, чем это стоит. Кто-нибудь имеет рекомендации, основанные на их более интимном опыте с IEEE754 с учетом производительности?

4b9b3361

Ответ 1

КРАТКОЕ ОПИСАНИЕ: Для обеспечения максимальной мобильности не используйте NaN. Используйте отдельный допустимый бит. Например. шаблон, например Valid. Однако, если вы знаете, что вы будете работать только на машинах IEEE 754-2008, а не на IEEE 754-1985 (см. Ниже), то вы можете с ним справиться.

Для производительности, скорее всего, быстрее не использовать NaN на большинстве машин, к которым у вас есть доступ. Тем не менее, я занимался аппаратным дизайном FP ​​на нескольких машинах, которые улучшают производительность обработки NaN, поэтому существует тенденция к тому, что NaNs быстрее, и, в частности, сигнализация NaN должна скоро быть быстрее, чем Действительная.

ДЕТАЛЬ:

Не все форматы с плавающей запятой имеют NaN. Не во всех системах используется плавающая точка IEEE. Ячейка с шестью точками с шестью точками IBM все еще может быть найдена на некоторых машинах - на самом деле это системы, поскольку IBM теперь поддерживает IEEE FP на более поздних машинах.

Кроме того, у IEEE Floating Point были проблемы с совместимостью с NaNs в IEEE 754-1985. Например, см. Wikipedia http://en.wikipedia.org/wiki/NaN:

Оригинальный стандарт IEEE 754 от 1985 (только IEEE 754-1985) описал двоичные форматы с плавающей запятой и не указал, как сигнализированное/тихое состояние должно быть помечено. На практике большинство Значительный бит значимости определял, является ли NaN сигнализация или тишина. Две различные реализации с обратным значения. * большинство процессоров (в том числе семейства Intel/AMD x86-32/x86-64, семейства Motorola 68000, семейства AIM PowerPC, ARM семейство и семейство Sun SPARC) устанавливают бит сигнализации/ ненулевое, если NaN тихо, и к нулю, если NaN сигнализирует. Таким образом, на этих процессорах бит представляет собой флаг "is_quiet". * в NaN, генерируемых процессорами PA-RISC и MIPS, бит с сигналом/типом равен нулю, если NaN является тихим и отличным от нуля, если NaN сигнализирует. Таким образом, на этих процессорах бит представляет собой флаг is_signaling.

Это, если ваш код может работать на старых машинах HP или на существующих машинах MIPS (которые повсеместно распространены в встроенных системах), вы не должны зависеть от фиксированной кодировки NaN, но должны иметь зависимые от машины #ifdef для вашего специального NaNs.

IEEE 754-2008 стандартизирует кодировки NaN, так что это улучшается. Это зависит от вашего рынка.

Что касается производительности: многие машины, по сути, ловушки или, как правило, делают большую икону в производительности, при выполнении вычислений, включающих как SNaN (которые должны ловушки), так и QNaN (которые не нужно ловушки, то есть, которые могут быть быстрыми, и которые становятся быстрее на некоторых машинах, когда мы говорим.)

Я могу с уверенностью сказать, что на старых машинах, особенно на старых компьютерах Intel, вы НЕ хотели использовать NaN, если бы вы заботились о производительности. Например. http://www.cygnus-software.com/papers/x86andinfinity.html говорит: "Intel Pentium 4 очень плохо справляется с бесконечностями, NAN и denormals.... Если вы пишете код, который добавляет числа с плавающей запятой со скоростью одного за каждый такт, а затем бросать в него бесконечности в качестве входных данных, производительность падает... Много... Огромное количество... NAN еще медленнее. Дополнение с NAN занимает около 930 циклов... Денормалы немного сложнее измерить."

Получить изображение? Почти 1000 раз медленнее использовать NaN, чем выполнять обычную операцию с плавающей запятой? В этом случае почти гарантировано, что использование шаблона типа Valid будет быстрее.

Однако см. ссылку на "Pentium 4"? Это действительно старая веб-страница. В течение многих лет такие люди, как я, говорили, что "QNaNs должны быть быстрее", и он медленно овладевает.

Совсем недавно (2009) Microsoft говорит http://connect.microsoft.com/VisualStudio/feedback/details/498934/big-performance-penalty-for-checking-for-nans-or-infinity "Если вы делаете математику на массивах двойных, содержащих большое количество NaN или Infinities, есть заказ от величины штрафа за производительность".

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

Это должно меняться, потому что не так быстро делать QNaNs. Но это всегда была проблема с курицей и яйцом: аппаратные парни вроде тех, с кем я работаю, говорят: "Никто не использует NaNs, поэтому мы победили, сделаем их быстрыми", в то время как программные ребята не используют NaN, потому что они медленны. Тем не менее, поток медленно меняется.

Если вы используете gcc и хотите получить лучшую производительность, вы включаете такие оптимизации, как "-ffinite-math-only... Разрешить оптимизацию для арифметики с плавающей запятой, которые предполагают, что аргументы и результаты не являются NaN или + -Infs". Аналогично для большинства компиляторов.

Кстати, вы можете google, как я, "с плавающей запятой производительности NaN" и проверить, что вы выбрали. И/или запускайте свои собственные микрообъекты.

Наконец, я предполагаю, что вы используете шаблон, например

template<typename T> class Valid {
    ...
    bool valid;
    T value;
    ...
};

Мне нравятся шаблоны, подобные этому, потому что они могут приводить "отслеживание достоверности" не только к FP, но и к целому (Valid) и т.д.

Но у них может быть большая стоимость. Операции, вероятно, не намного дороже обработки NaN на старых машинах, но плотность данных может быть очень низкой. sizeof (Действительный) может иногда быть 2 * sizeof (float). Эта плохая плотность может повредить производительность намного больше, чем операции.

Кстати, вам следует рассмотреть специализированную специализацию, так что Valid использует NaN, если они доступны и быстры, и в противном случае допустимый бит.

template <> class Valid<float> { 
    float value; 
    bool is_valid() { 
        return value != my_special_NaN; 
    } 
}

и др.

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

struct Point { float x, y, z; };
Valid<Point> pt;

лучше (плотность), чем

struct Point_with_Valid_Coords { Valid<float> x, y, z; };

если вы не используете NaNs или какую-либо другую специальную кодировку.

И

struct Point_with_Valid_Coords { float x, y, z; bool valid_x, valid_y, valid_z };

находится между ними, но тогда вы должны сами выполнять весь код.

Кстати, я предполагаю, что вы используете С++. Если FORTRAN или Java...

BOTTOM LINE: отдельные допустимые биты, вероятно, быстрее и более переносимы.

Но управление NaN ускоряется, и в один прекрасный день скоро будет достаточно хорошо

Кстати, мое предпочтение: создать шаблон Valid. Затем вы можете использовать его для всех типов данных. Специализируйте его для NaNs, если это поможет. Хотя моя жизнь ускоряет работу, ИМХО обычно важнее сделать код чистым.

Ответ 2

Если недействительные данные очень распространены, вы, конечно, тратите много времени на запуск этих данных с помощью обработки. Если недопустимые данные являются достаточно распространенными, вероятно, лучше использовать какую-то разреженную структуру данных только для достоверных данных. Если это не очень распространено, вы можете, конечно, сохранить редкую структуру данных, данные которой недействительны. Таким образом, вы не потеряете bool для каждой ценности. Но, возможно, память для вас не проблема...

Если вы выполняете такие операции, как, например, многократное использование двух, возможно, недопустимых записей данных, я понимаю, что они вынуждены использовать NaN вместо того, чтобы делать проверки для обеих переменных, чтобы убедиться, что они действительны и установить тот же флаг в результирующем.

Насколько портативен вам быть? Вам когда-нибудь понадобится возможность перенести его в архитектуру с поддержкой только фиксированной точки? Если это так, я думаю, что ваш выбор ясен.

Лично я бы использовал только NaN, если бы это оказалось намного быстрее. В противном случае я бы сказал, что код становится более понятным, если у вас есть явная обработка недопустимых данных.

Ответ 3

Поскольку числа с плавающей запятой поступают с устройства, они, вероятно, имеют ограниченный диапазон. Вы можете использовать другое специальное число, а не NaN, чтобы указать отсутствие данных, например. 1e37. Это решение переносимо. Я не знаю, подходит ли вам более или менее, чем использовать флаг bool.