2009-05-18 4 views
12

Я пишу алгоритмы, которые работают над серией числовых данных, где иногда значение в серии должно быть нулевым. Однако, поскольку это приложение критично критично, я избежал использования типов с возможностью NULL. Я имею первичную проверку алгоритмов, чтобы конкретно сравнить производительность использования типов с нулевым значением по сравнению с типами, не допускающими нулевое значение, и в наилучшем случае типы с нулевым значением являются 2x медленнее, но часто намного хуже.Альтернативы nullable типам в C#

Тип данных, который чаще всего используется, является двойным, и в настоящее время выбранная альтернатива null равна double.NaN. Однако я понимаю, что это не точное назначение для значения NaN, поэтому я не уверен, есть ли какие-либо проблемы с этим, я не могу предвидеть и какова будет лучшая практика.

Я интересно узнать, что лучшие нулевые альтернативы для следующих типов данных, в частности: двойной/поплавка, десятичный, DateTime, внутр/длинные (хотя другие являются более чем приветствуется)

Edit: я думаю, мне нужно уточнить мои требования к производительности. Концерты числовых данных обрабатываются с помощью этих алгоритмов в то время, которое занимает несколько часов. Поэтому, хотя разница между, например, 10 мс или 20 мс обычно незначителен, в этом случае она действительно оказывает значительное влияние на время.

+0

Некоторая информация о типе и количестве данных, которые вы ожидаете, будет хорошо. – peterchen

+1

Для информации - мои тесты показывают, что для случая, когда ни одно значение не равно нулю, Nullable и магические числа довольно равны; когда задействован нуль, да, подход магического числа немного быстрее ... но достаточно ли (er), чтобы стоить неудобства? Это все еще очень, очень быстро - 50 М итераций в 86 мс (магическое число) против 144 мс (Nullable ) на моей машине ... –

+0

(см. Также мой ответ на ваш тест i == null и т. Д., Компилятор уже делает это через " вы, вероятно, дублируете его ...) –

ответ

18

Ну, если вы исключили Nullable<T>, у вас остались значения домена - то есть магическое число, которое вы считаете нулевым. В то время как это не ideal, это также не редкость - например, много основного кода рамки рассматривает DateTime.MinValue так же, как и нуль. Это, по крайней мере, передвигает ущерб далеко от общих ценностей ...

редактировать, чтобы выделить только там, где нет NaN

Так где нет NaN, возможно, использовать .MinValue - но только помните, что зло, если вас случайно использовать такое же значение значение одинаковое число ...

Очевидно, что для неподписанных данных вам потребуется .MaxValue (избегайте нуля !!!).

Лично я попытался бы использовать Nullable<T>, чтобы выразить свое намерение более безопасно ... могут быть способы оптимизации вашего кода Nullable<T>, возможно. А также - к тому времени, когда вы проверили магическое число во всех местах, где вам нужно, возможно, это будет не намного быстрее, чем Nullable<T>?

+0

Согласен, я думаю, что это гораздо лучшая альтернатива, чем двойные, если у вас не должно быть long.MaxValue. – BobbyShaftoe

+1

Для значений double или float NaN или одна из бесконечностей может использоваться как «нулевое» значение, если они вам не нужны. – Joey

+0

Что касается проверок, для типов Null требуется одинаковое количество проверок, где я проверяю магическое число, я проверяю значение null. Таким образом, перфомансы, которые я выполнил, учитывали это. Я согласен, что это не идеально, но в этом сценарии производительность - нет. 1. И в этом сценарии, перфорация разницы между операциями так же просто, как int + int и int? + int? является значительным. – Ryan

4

Я несколько не согласен с Gravell в этом конкретном случае края: переменная Null-ed считается «не определена», она не имеет значения. Так что все, что используется, чтобы сигнализировать, что все в порядке: даже магические числа, но с магическими номерами вы должны учитывать, что волшебное число всегда будет преследовать вас в будущем, когда оно станет «действительным» значением внезапно. С Double.NaN вам не нужно бояться за это: он никогда не станет действительным двойником. Хотя, вы должны учитывать, что NaN в смысле последовательности удвоений можно использовать только как маркер для «не определено», вы также не можете использовать его как код ошибки в последовательностях.

Таким образом, все, что используется для обозначения «неопределенного»: в контексте набора значений должно быть ясно, что это конкретное значение считается значением «неопределенного» и которое не изменится в будущем.

Если Nullable дает вам слишком много проблем, используйте NaN или что-то еще, если вы рассматриваете последствия: выбранное значение представляет собой «неопределенный», и это останется.

+0

Вы правы, и я был неясен. Я имел в виду только MinValue и т. Д. В те времена, когда нет NaN - int, long, decimal, DateTime и т. Д. Для double/float NaN - очевидный ответ (который я предположил из вопроса). –

2

Частичный ответ:

Float и Double обеспечивают NaN (не число). NaN немного сложнее, так как, согласно спецификации, NaN! = NaN. Если вы хотите узнать, является ли число NaN, вам нужно использовать Double.IsNaN().

См. Также Binary floating point and .NET.

+1

Как в стороне ... в большинстве баз данных, null! = Null тоже, поэтому это не обязательно неожиданная территория ... но да: это отличается от того, как C# обрабатывает равенство Nullable . –

4

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

NaN арифметики - В то время, большая часть времени, «продвижение NaN» это хорошая вещь, это не всегда может быть то, что вы ожидать.

Сравнение - Сравнение значений становится довольно дорогостоящим, если вы хотите сравнить NaN равными. Теперь тестирование поплавков на равенство в любом случае не так просто, но упорядочение (< b) может стать действительно уродливым, так как иногда нано должно быть меньше, иногда больше нормальных значений.

Code Infection - Я вижу много арифметического кода, который требует правильной обработки файлов NaN. Таким образом, вы получаете «функции, которые принимают NaN» и «функции, которые не работают» по соображениям производительности.

Прочие нефиниты NaN - это единственное не конечное значение. Следует иметь в виду ...

Исключения с плавающей запятой не являются проблемой при отключении. Пока кто-то не позволит им. Истинная история: статическая инициализация NaN в элементе управления ActiveX. Не звучит страшно, пока вы не измените установку на использование InnoSetup, которая использует ядро ​​Pascal/Delphi (?), Которое по умолчанию имеет исключения FPU. Понадобился время, чтобы разобраться.

Итак, в общем, ничего серьезного, хотя я бы предпочел не часто рассматривать NaNs.


Я хотел бы использовать NULLABLE типы так часто, как это возможно, если только они не являются (оказались) производительность/Ressource ограничений. В одном случае могут быть большие векторы/матрицы со случайными NaN или большими наборами именованных индивидуальных значений , где правильное поведение NaN соответствует.


В качестве альтернативы, вы можете использовать вектор индекса для векторов и матриц, стандартные «разреженных матриц» реализаций, или отдельного BOOL/битового вектора.

0

Возможно, значительное снижение производительности происходит при вызове одного из членов или свойств Nullable (бокса).

Попробуйте использовать struct с double + boolean, указав, указано это значение или нет.

+0

Но типы с нулевыми типами являются структурами уже ... – Ryan

+0

Это именно то, что делает Nullable - это структура, она имеет значение (например, двойного типа) и логическое указание, которое имеет или не присвоило значение. И без бокса над головой. –

+0

Свойства (HasValue и Value) являются методами внутри (get_HasValue и get_Value). Таким образом, они подвержены боксу (при условии, что здесь нет специальной магии компилятора для Nullable). –

0

можно избежать некоторых ухудшения производительности, связанной с Nullable<T>, определив собственную структуру

struct MaybeValid<T> 
{ 
    public bool isValue; 
    public T Value; 
} 

При желании, можно определить конструктор или оператор преобразования из T в MaybeValid<T> и т.д., но злоупотребление такими вещи могут дать не оптимальную производительность. Структуры открытого поля могут быть эффективными, если вы избегаете ненужного копирования данных. Некоторые люди могут недооценивать понятие открытых полей, но они могут быть значительно более эффективными, чем свойства. Если функция, которая вернет T, должна иметь переменную типа T, чтобы сохранить ее возвращаемое значение, используя MaybeValid<Foo>, просто увеличивает на 4 размер вещи, которую нужно вернуть. Напротив, использование Nullable<Foo> потребует, чтобы функция сначала вычислила Foo, а затем передала ее копию конструктору для Nullable<Foo>. Кроме того, для возврата Nullable<Foo> потребуется, чтобы любой код, который хочет использовать возвращаемое значение, должен сделать по крайней мере одну дополнительную копию в хранилище (переменную или временную) типа Foo, прежде чем он сможет сделать что-нибудь полезное с ним. Напротив, код может использовать поле Value переменной типа Foo примерно так же эффективно, как и любая другая переменная.

Смежные вопросы