Быстрое сравнение двух двойных, игнорируя все, что идет после 6 десятичных цифр - PullRequest
0 голосов
/ 04 декабря 2018

Я пытаюсь оптимизировать производительность какого-то процесса расчета.Достойное количество времени тратится на вычисления, подобные следующим:

var isBigger = Math.Abs((long) (a * 1e6) / 1e6D) > ((long) ((b + c) * 1e6)) / 1e6D;

, где «a», «b» и «c» - это двойные числа, «b» и «c» - положительные значения, «a» можетбыть отрицательным.

isBigger должен быть истинным, только если абсолютное значение «a» больше, чем «b + c», независимо от чего-либо после 6-й десятичной цифры.

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

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

class Program
{
    static void Main(string[] args)
    {
        var arrLength = 1000000;
        var arr1 = GetArrayOf_A(arrLength);
        var arr2 = GetArrayOf_B(arrLength);
        var arr3 = GetArrayOf_C(arrLength);
        var result1 = new bool[arrLength];
        var result2 = new bool[arrLength];


        var sw = new Stopwatch();

        sw.Start();
        for (var i = 0; i < arrLength; i++)
        {
            result1[i] = Math.Abs((long) (arr1[i] * 1e6) / 1e6D) 
                         > 
                         (long) ((arr2[i] + arr3[i]) * 1e6) / 1e6D;
        }
        sw.Stop();
        var t1 = sw.Elapsed.TotalMilliseconds;

        sw.Restart();
        for (var i = 0; i < arrLength; i++)
        {
            //result2[i] = Math.Round(Math.Abs(arr1[i]) - (arr2[i] + arr3[i]),6) > 0; // Incorrect, example by index = 0
            //result2[i] = Math.Abs(arr1[i]) - (arr2[i] + arr3[i]) > 0.000001; // Incorrect, example by index = 1
            //result2[i] = Math.Abs(arr1[i]) - (arr2[i] + arr3[i]) > 0.0000001; // Incorrect, example by index = 2
            result2[i] = Math.Abs(arr1[i]) - (arr2[i] + arr3[i]) > 0.00000001; // Incorrect, example by index = 3
        }
        sw.Stop();
        var t2 = sw.Elapsed.TotalMilliseconds;

        var areEquivalent = true;
        for (var i = 0; i < arrLength; i++)
        {
            if (result1[i] == result2[i]) continue;

            areEquivalent = false;
            break;
        }

        Console.WriteLine($"Functions are equivalent : {areEquivalent}");
        if (areEquivalent)
        {
            Console.WriteLine($"Current function total time: {t1}ms");
            Console.WriteLine($"Equivalent function total time: {t2}ms");
        }

        Console.WriteLine("Press ANY key to quit . . .");
        Console.ReadKey();
    }

    private static readonly Random _rand = new Random(DateTime.Now.Millisecond);
    private const int NumberOfRepresentativeExamples = 4;

    private static double[] GetArrayOf_A(int arrLength)
    {
        if(arrLength<=NumberOfRepresentativeExamples) 
            throw new ArgumentException($"{nameof(arrLength)} should be bigger than {NumberOfRepresentativeExamples}");

        var arr = new double[arrLength];

        // Representative numbers
        arr[0] = 2.4486382579120365;
        arr[1] = -1.1716818990000011;
        arr[2] = 5.996414627393257;
        arr[3] = 6.0740085822069;

        // the rest is to build time statistics
        FillTheRestOfArray(arr);

        return arr;
    }
    private static double[] GetArrayOf_B(int arrLength)
    {
        if(arrLength<=NumberOfRepresentativeExamples) 
            throw new ArgumentException($"{nameof(arrLength)} should be bigger than {NumberOfRepresentativeExamples}");

        var arr = new double[arrLength];

        // Representative numbers
        arr[0] = 2.057823225;
        arr[1] = 0;
        arr[2] = 2.057823225;
        arr[3] = 2.060649901;

        // the rest is to build time statistics
        FillTheRestOfArray(arr);

        return arr;
    }
    private static double[] GetArrayOf_C(int arrLength)
    {
        if(arrLength<=NumberOfRepresentativeExamples) 
            throw new ArgumentException($"{nameof(arrLength)} should be bigger than {NumberOfRepresentativeExamples}");

        var arr = new double[arrLength];

        // Representative numbers
        arr[0] = 0.3908145999796302;
        arr[1] = 1.1716809269999997;
        arr[2] = 3.9385910820740282;
        arr[3] = 4.0133582670728858;

        // the rest is to build time statistics
        FillTheRestOfArray(arr);

        return arr;
    }
    private static void FillTheRestOfArray(double[] arr)
    {
        for (var i = NumberOfRepresentativeExamples; i < arr.Length; i++)
        {
            arr[i] = _rand.Next(0, 10) + _rand.NextDouble();
        }
    }
}

Ответы [ 4 ]

0 голосов
/ 05 декабря 2018

Помимо того, что я задавал этот вопрос на этом сайте, я также спросил моего хорошего друга, и до сих пор он давал лучший ответ.Вот оно:

result2[i] = Math.Abs(arr1[i]) - (arr2[i] + arr3[i]) > 0.000001 || 
             Math.Abs((long)(arr1[i] * 1e6)) > (long)((arr2[i] + arr3[i])*1e6);

Я рад, что у меня есть такие друзья:)

0 голосов
/ 05 декабря 2018

Вам не нужно деление, так как если (x/100) < (y/100), это означает, что x<y.

for(var i = 0; i < arrLength; i++)
{
    result2[i] = Math.Abs((long)(arr1[i] * 1e6)) 
                    > (long)((arr2[i] + arr3[i]) * 1e6);
}

с результатами для меня:

Arrays have 1000000 elements.
Functions are equivalent : True
   Current function total time: 40.10ms 24.94 kflop
Equivalent function total time: 22.42ms 44.60 kflop
A speedup of 78.83 %

PS.Убедитесь, что вы сравниваете RELEASE версии двоичного файла, который включает математические оптимизации.

PS2.Код дисплея

Console.WriteLine($"Arrays have {arrLength} elements.");
Console.WriteLine($"Functions are equivalent : {areEquivalent}");
Console.WriteLine($"   Current function total time: {t1:F2}ms {arrLength/t1/1e3:F2} kflop");
Console.WriteLine($"Equivalent function total time: {t2:F2}ms {arrLength/t2/1e3:F2} kflop");
Console.WriteLine($"An speedup of {t1/t2-1:P2}");
0 голосов
/ 05 декабря 2018

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

.NET - не идеальный сценарий для такого рода операций.Обычно это делается в специальных языках.Следующая лучшая вещь - делать это на ассемблере, C или нативном C ++..NET имеет дополнительные функции, такие как сборщик мусора и компилятор Just In Time, которые делают даже получение надежных результатов тестов сложнее.Гораздо менее надежная производительность во время выполнения.

Для типов данных Float должен быть самой быстрой из существующих операций.По историческим причинам операции с плавающей точкой были оптимизированы.

В одном из ваших комментариев упоминается физика, и у вас есть массив.И я вижу такие вещи, как array[i] = array2[i] + array3[i].Так что, может быть, это должна быть матричная операция, которую вы вместо этого запускаете на GPU?Этот тип «огромных операций с паралелизированными массивами» - именно то, в чем хорош GPU.Именно то, что рисует на экране , является в его ядре.

Если вы не скажете нам, что вы на самом деле делаете здесь в качестве операции, это лучший ответ, который я могу дать.

0 голосов
/ 04 декабря 2018

Это то, что вы ищете?

Math.Abs(a) - (b + c) > 0.000001

или хотите узнать, больше ли разница (разница в любом случае):

Math.Abs(Math.Abs(a) - (b + c)) > 0.000001

(I 'м, если вы не ограничиваете эту точность из-за скорости, но из-за присущей точности с плавающей запятой.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...