Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > .NET Frameworks (точка нет фреймворки) > C# (си шарп)
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 27.01.2012, 12:36   #1
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию Ускоряем IsNaN() в три раза

Пройдясь профайлером по своей проге, я обнаружил, что аж 7% времени занимают вызовы float.IsNaN(). Вообще функция довольно тяжёлая (по сравнению с элементарными операторами сравнения), потому что NaN кодируется не одним двоичным представлением, а целым диапазоном. Покурив IEEE 754 и взяв за основу чей-то полезный класс DoubleUtil я сделал такой вот сравнятор:

Код:
using System;
using System.Runtime.InteropServices;

public static class FloatComparer
{
	// float
	[StructLayout (LayoutKind.Explicit)]
	struct FloatUnion
	{
		[FieldOffset (0)]
		public float value;

		[FieldOffset (0)]
		public UInt32 binary;
	}

	public static bool IsNaN (float value)
	{
		FloatUnion union = new FloatUnion ();
		union.value = value;

		return ((union.binary & 0x7F800000) == 0x7F800000) && ((union.binary & 0x007FFFFF) != 0);
	}

	// double
	[StructLayout (LayoutKind.Explicit)]
	struct DoubleUnion
	{
		[FieldOffset (0)]
		public double value;

		[FieldOffset (0)]
		public UInt64 binary;
	}

	public static bool IsNaN (double value)
	{
		DoubleUnion union = new DoubleUnion ();
		union.value = value;

		return ((union.binary & 0x7ff0000000000000) == 0x7ff0000000000000) && ((union.binary & 0x000fffffffffffff) != 0);
	}
}
Он перегружен для float и double. Суть том, что мы записываем в структуру float, а потом читаем с того же места как int (по аналогии с union из C++). И, посмотрев двоичное представление, делаем нужные выводы. Поскольку структура union у нас локальная, находится в стеке, то даже при многократном вызове память из кучи не забивается. Вот программа для сравнения скорости float.IsNaN() и FloatComparer.IsNaN(), также проверяющая, что результаты совпадают.

Код:
using System;
using System.Diagnostics;

class Program
{
	static void Main ()
	{
		Random rand = new Random ();
		float[] array = new float[10000000];

		for (int i=0; i<array.Length; i++)
			array[i] = (float)Math.Log (rand.Next (-1000, 1000)); // many NaN's

		// System
		bool[] systemResults = new bool[array.Length];
		Stopwatch stopwatch = new Stopwatch ();
		stopwatch.Start ();
		for (int i=0; i<array.Length; i++)
			systemResults[i] = float.IsNaN (array[i]);
		stopwatch.Stop ();
		Console.WriteLine ("System: " + stopwatch.ElapsedTicks*0.0001 + " ms");

		// My
		bool[] myResults = new bool[array.Length];
		stopwatch.Restart ();
		for (int i=0; i<array.Length; i++)
			myResults[i] = FloatComparer.IsNaN (array[i]);
		stopwatch.Stop ();
		Console.WriteLine ("My: " + stopwatch.ElapsedTicks*0.0001 + " ms");

		// Check results
		for (int i=0; i<array.Length; i++)
			if (myResults[i] != systemResults[i])
			{
				Console.WriteLine ("Error");
				break;
			}

		Console.ReadKey ();
	}
}
Если запускать в релизе, то выигрыш в скорости получается в три раза, как для float, так и для double. Для меня остаётся загадкой, почему вручную написанная функция оказывается в разы быстрее стандартной элементарной функции.

З. Ы. если убрать статики и не пересоздавать юнион, то работать будет ещё немного быстрее. А если ещё запускать напрямую, не из студии, то выигрыш получается в 4,5 раза.

Последний раз редактировалось ds.Dante; 27.01.2012 в 14:29.
ds.Dante вне форума Ответить с цитированием
Старый 27.01.2012, 14:01   #2
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Апдейт. В исходниках дотнета IsNaN выглядит так:

Код:
        [Pure]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 
        public static bool IsNaN(float f) {
            //Jit will generate inlineable code with this
// warning CS1718:  Comparison to same variable
#pragma warning disable 1718 
            if (f != f)
            { 
                return true; 
            }
            else 
            {
                return false;
            }
#pragma warning restore 1718 
        }
Эта элементарная проверка с собой, которая, очевидно, выполняется сопроцессором, оказывается медленнее проверки собственно процессором в несколько ходов. Как я понимаю, такая проверка if (f != f) используется и плюсовиками. Надо проверить, не окажется проверка через двоичку быстрее и в плюсах.
ds.Dante вне форума Ответить с цитированием
Старый 27.01.2012, 14:52   #3
Hollander
Участник клуба
 
Аватар для Hollander
 
Регистрация: 03.05.2007
Сообщений: 1,189
По умолчанию

Спасибо, как только будет возможность, дам репутацию. Отличное исследование.
Hollander вне форума Ответить с цитированием
Старый 27.01.2012, 16:07   #4
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Спасибо. :) А каких-то пару лет назад (один из которых я провёл в армии) ты сам отвечал на мои бесчисленные вопросы по дотнету. :)

С unsafe код выглядит гораздо проще и работает ещё чуть быстрее.

Код:
static class NaNComparer
{
	public static unsafe bool IsNaN (float f)
	{
		int binary = *(int*)(&f);
		return ((binary & 0x7F800000) == 0x7F800000) && ((binary & 0x007FFFFF) != 0);
	}

	public static unsafe bool IsNaN (double f)
	{
		long binary = *(long*)(&f);
		return ((binary & 0x7ff0000000000000) == 0x7ff0000000000000) && ((binary & 0x000fffffffffffff) != 0);
	}
}
Примерно так в дотнете работает float.IsInfinity()
ds.Dante вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
составить рисунок:три лапки справа вертикально,три лапки слева горизонтально) Glebio95 Паскаль, Turbo Pascal, PascalABC.NET 6 02.12.2011 11:22
как создать матрицу три на три в мемо (делфи) Lordigan Помощь студентам 0 15.03.2010 18:37
Повысить громкость раза в 3. Alex Cones Софт 7 23.10.2009 04:13
Заменить повторяющиеся символы(которые повторяются два или три раза) в тексте пробелами. Майки Паскаль, Turbo Pascal, PascalABC.NET 3 12.01.2009 17:45
черный ящик, на три входа и на три выхода Shanson Помощь студентам 7 30.01.2008 09:45