【问题标题】:Optimizing Karatsuba Implementation优化 Karatsuba 实施
【发布时间】:2010-02-02 19:47:04
【问题描述】:

因此,我正在尝试改进 .net 4 的 BigInteger 类提供的一些操作,因为这些操作看起来是二次的。我做了一个粗略的 Karatsuba 实现,但它仍然比我预期的要慢。

主要问题似乎是 BigInteger 没有提供简单的方法来计算位数,因此我必须使用 BigInteger.Log(..., 2)。根据 Visual Studio,大约 80-90% 的时间都花在计算对数上。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numerics;

namespace Test
{
    class Program
    {
        static BigInteger Karatsuba(BigInteger x, BigInteger y)
        {
            int n = (int)Math.Max(BigInteger.Log(x, 2), BigInteger.Log(y, 2));
            if (n <= 10000) return x * y;

            n = ((n+1) / 2);

            BigInteger b = x >> n;
            BigInteger a = x - (b << n);
            BigInteger d = y >> n;
            BigInteger c = y - (d << n);

            BigInteger ac = Karatsuba(a, c);
            BigInteger bd = Karatsuba(b, d);
            BigInteger abcd = Karatsuba(a+b, c+d);

            return ac + ((abcd - ac - bd) << n) + (bd << (2 * n));
        }

        static void Main(string[] args)
        {
            BigInteger x = BigInteger.One << 500000 - 1;
            BigInteger y = BigInteger.One << 600000 + 1;
            BigInteger z = 0, q;

            Console.WriteLine("Working...");
            DateTime t;

            // Test standard multiplication
            t = DateTime.Now;
            z = x * y;
            Console.WriteLine(DateTime.Now - t);

            // Test Karatsuba multiplication
            t = DateTime.Now;
            q = Karatsuba(x, y);
            Console.WriteLine(DateTime.Now - t);

            // Check they're equal
            Console.WriteLine(z == q);

            Console.Read();
        }
    }
}

那么,我该怎么做才能加快速度呢?

【问题讨论】:

  • 您能否介绍一下 Karatsuba 是什么?
  • 我不确定这是否会有所帮助,但也许您可以以某种方式将其转换为 BitArray 以便您可以计算位数。
  • @aaronls:这要快得多,谢谢。
  • &lt;&lt; 的优先级低于+/-

标签: c# biginteger


【解决方案1】:

为什么要计算所有位?

在 vb 中我这样做:

<Runtime.CompilerServices.Extension()> _
Function BitLength(ByVal n As BigInteger) As Integer
    Dim Data() As Byte = n.ToByteArray
    Dim result As Integer = (Data.Length - 1) * 8
    Dim Msb As Byte = Data(Data.Length - 1)
    While Msb
        result += 1
        Msb >>= 1
    End While
    Return result
End Function

在 C# 中是:

public static int BitLength(this BigInteger n)
{
    byte[] Data = n.ToByteArray();
    int result = (Data.Length - 1) * 8;
    byte Msb = Data[Data.Length - 1];
    while (Msb != 0) {
        result += 1;
        Msb >>= 1;
    }
    return result;
}

终于……

    static BigInteger Karatsuba(BigInteger x, BigInteger y)
    {
        int n = (int)Math.Max(x.BitLength(), y.BitLength());
        if (n <= 10000) return x * y;

        n = ((n+1) / 2);

        BigInteger b = x >> n;
        BigInteger a = x - (b << n);
        BigInteger d = y >> n;
        BigInteger c = y - (d << n);

        BigInteger ac = Karatsuba(a, c);
        BigInteger bd = Karatsuba(b, d);
        BigInteger abcd = Karatsuba(a+b, c+d);

        return ac + ((abcd - ac - bd) << n) + (bd << (2 * n));
    }

调用扩展方法可能会减慢速度,所以也许这样会更快:

int n = (int)Math.Max(BitLength(x), BitLength(y));

仅供参考:使用位长方法,您还可以比 BigInteger 方法更快地计算出对数的良好近似值。

bits = BitLength(a) - 1;
log_a = (double)i * log(2.0);

至于访问 BigInteger 结构的内部 UInt32 数组,这里有一个 hack。

导入反射命名空间

Private Shared ArrM As MethodInfo
Private Shard Bits As FieldInfo
Shared Sub New()
    ArrM = GetType(System.Numerics.BigInteger).GetMethod("ToUInt32Array", BindingFlags.NonPublic Or BindingFlags.Instance)
    Bits = GetType(System.Numerics.BigInteger).GetMember("_bits", BindingFlags.NonPublic Or BindingFlags.Instance)(0)

End Sub
<Extension()> _
Public Function ToUInt32Array(ByVal Value As System.Numerics.BigInteger) As UInteger()
    Dim Result() As UInteger = ArrM.Invoke(Value, Nothing)
    If Result(Result.Length - 1) = 0 Then
        ReDim Preserve Result(Result.Length - 2)
    End If
    Return Result
End Function

那么就可以得到大整数的底层UInteger()为

 Dim Data() As UInteger = ToUInt32Array(Value)
 Length = Data.Length 

或交替

Dim Data() As UInteger = Value.ToUInt32Array()

请注意,_bits fieldinfo 可用于直接访问 BigInteger 结构的底层 UInteger() _bits 字段。这比调用 ToUInt32Array() 方法要快。但是,当 BigInteger B

我也无法像通常使用反射一样使用 _bits.SetValue(B, Data())。为了解决这个问题,我使用了有开销的 BigInteger(bytes() b) 构造函数。在 c# 中,您可以使用不安全的指针操作将 UInteger() 转换为 Byte()。由于 VB 中没有指针操作,所以我使用 Buffer.BlockCopy。以这种方式访问​​数据时,请务必注意,如果设置了 bytes() 数组的 MSB,MS 会将其解释为负数。我希望他们制作一个带有单独符号字段的构造函数。字数组就是增加一个0字节来取消选中MSB

此外,在进行平方时,您可以进一步提高

 Function KaratsubaSquare(ByVal x As BigInteger)
    Dim n As Integer = BitLength(x) 'Math.Max(BitLength(x), BitLength(y))

    If (n <= KaraCutoff) Then Return x * x
    n = ((n + 1) >> 1)

    Dim b As BigInteger = x >> n
    Dim a As BigInteger = x - (b << n)
    Dim ac As BigInteger = KaratsubaSquare(a)
    Dim bd As BigInteger = KaratsubaSquare(b)
    Dim c As BigInteger = Karatsuba(a, b)
    Return ac + (c << (n + 1)) + (bd << (2 * n))

End Function

这从乘法算法的每次递归中消除了 2 次移位、2 次加法和 3 次减法。

【讨论】:

  • 亚历山大·希金斯的杰作! +1 为您的回答,这帮助我寻找完美的数字...
  • 令人着迷,但从一个简短的微基准测试来看,.Net 似乎已经使用了这种优化;时间很接近,有时会更快一些,但平均而言(不做数学计算)默认实现似乎以微弱优势获胜。
  • 在实践中,由于 4/3 的改进,“小学”输给了“Karatsuba”。这是由于算法开销cburch.com/proj/karat/results.html。还有其他快速乘法方法,例如 Toom-Cook,它比“小学”提高了 9/5,但在实践中,由于开销,Karatsuba 会在一定程度上胜出。同样,Fast Fourier 击败 Tom-Cook 的几千位数范围内也有一个截止点。
猜你喜欢
  • 2018-03-17
  • 1970-01-01
  • 2013-05-28
  • 2013-11-19
  • 1970-01-01
  • 1970-01-01
  • 2020-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多