Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify BigInteger.Multiply #98587

Merged
merged 4 commits into from
May 10, 2024
Merged

Conversation

kzrnm
Copy link
Contributor

@kzrnm kzrnm commented Feb 16, 2024

In my previous pull request #92208, I split BigIntegerCalculator.Multiply into two methods: MultiplyNearLength and MultiplyFarLength.
However, this division resulted in a lot of code duplication.

The reason for this division is that coreLength can be greater than bits.Length - n. Actually, core.Slice(0, ActualLength(core)) seems to be sufficient since ActualLength(core)) is never greater than bits.Length - n.

https://github.com/kzrnm/dotnet-runtime/blob/f7eaf3b17fb4bd753b3404f0c9977c9693a2b480/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs#L282-L326

Benchmark

BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);

public class Tests
{
    public IEnumerable<object> GetMultiplyArgs()
    {
        var bytes = new byte[1000000];
        bytes.AsSpan().Fill(byte.MaxValue);
        var lengths = new int[] { 1000, 10000, 100000, 1000000 };
        for (int i = lengths.Length - 1; i >= 0; i--)
        {
            var largeLength = lengths[i];
            var large = Make(largeLength);
            foreach (var p in new double[] { 1, 0.75, 0.5, 0.25 })
            {
                var smallLength = (int)(p * lengths[i]);
                var small = Make(smallLength);
                yield return new Data($"{largeLength:D7}-{smallLength:D7}", large, small);
            }
        }
        BigInteger Make(int length)
        {
            return new BigInteger(bytes.AsSpan().Slice(0, length), isUnsigned: true);
        }
    }

    public record Data(string Name, BigInteger Large, BigInteger Small)
    {
        public override string ToString() => Name;
    }

    [Benchmark]
    [ArgumentsSource(nameof(GetMultiplyArgs))]
    public BigInteger Multiply(Data data)
    {
        return data.Large * data.Small;
    }
}

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3155/23H2/2023Update/SunValley3)
13th Gen Intel Core i5-13500, 1 CPU, 20 logical and 14 physical cores
.NET SDK 9.0.100-alpha.1.23615.4
  [Host]     : .NET 9.0.0 (9.0.23.61410), X64 RyuJIT AVX2
  Job-IVRPVB : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
  Job-VXHELJ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2


Method Job Toolchain data Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
Multiply Job-IVRPVB \main\corerun.exe 0001000-0000250 9.472 μs 0.0781 μs 0.0731 μs 1.00 0.00 0.0916 - - 1.25 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0001000-0000250 8.178 μs 0.0726 μs 0.0644 μs 0.86 0.01 0.0916 - - 1.25 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0001000-0000500 13.684 μs 0.0564 μs 0.0528 μs 1.00 0.00 0.1068 - - 1.49 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0001000-0000500 12.726 μs 0.0695 μs 0.0650 μs 0.93 0.01 0.1068 - - 1.49 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0001000-0000750 18.784 μs 0.2849 μs 0.2665 μs 1.00 0.00 0.1221 - - 1.73 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0001000-0000750 18.205 μs 0.0999 μs 0.0935 μs 0.97 0.02 0.1221 - - 1.73 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0001000-0001000 20.274 μs 0.1754 μs 0.1640 μs 1.00 0.00 0.1526 - - 1.98 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0001000-0001000 20.543 μs 0.1343 μs 0.1257 μs 1.01 0.01 0.1526 - - 1.98 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0010000-0002500 365.931 μs 3.1887 μs 2.9827 μs 1.00 0.00 0.9766 - - 12.23 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0010000-0002500 353.082 μs 3.8782 μs 3.6277 μs 0.96 0.01 0.9766 - - 12.23 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0010000-0005000 538.574 μs 3.0983 μs 2.8982 μs 1.00 0.00 0.9766 - - 14.67 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0010000-0005000 522.857 μs 6.3931 μs 5.9801 μs 0.97 0.01 0.9766 - - 14.67 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0010000-0007500 738.188 μs 14.5480 μs 13.6082 μs 1.00 0.00 0.9766 - - 17.12 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0010000-0007500 727.770 μs 14.2161 μs 14.5989 μs 0.99 0.04 0.9766 - - 17.12 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0010000-0010000 782.050 μs 11.8638 μs 11.0974 μs 1.00 0.00 0.9766 - - 19.55 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0010000-0010000 798.392 μs 6.1704 μs 5.7718 μs 1.02 0.02 0.9766 - - 19.55 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0100000-0025000 14,109.077 μs 106.4570 μs 88.8964 μs 1.00 0.00 31.2500 31.2500 31.2500 122.12 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0100000-0025000 14,089.276 μs 126.7526 μs 118.5645 μs 1.00 0.01 31.2500 31.2500 31.2500 122.12 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0100000-0050000 21,654.298 μs 103.0624 μs 91.3621 μs 1.00 0.00 31.2500 31.2500 31.2500 146.53 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0100000-0050000 20,357.098 μs 162.0356 μs 143.6403 μs 0.94 0.01 31.2500 31.2500 31.2500 146.53 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0100000-0075000 28,971.251 μs 347.1229 μs 324.6990 μs 1.00 0.00 31.2500 31.2500 31.2500 170.96 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0100000-0075000 28,363.322 μs 210.1909 μs 196.6127 μs 0.98 0.01 31.2500 31.2500 31.2500 170.96 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 0100000-0100000 31,670.054 μs 217.2007 μs 203.1696 μs 1.00 0.00 31.2500 31.2500 31.2500 195.36 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 0100000-0100000 32,274.430 μs 520.1727 μs 486.5699 μs 1.02 0.02 - - - 195.34 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 1000000-0250000 552,147.007 μs 4,111.9758 μs 3,846.3447 μs 1.00 0.00 - - - 1220.79 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 1000000-0250000 547,253.633 μs 10,752.1365 μs 11,504.6656 μs 0.99 0.02 - - - 1221.45 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 1000000-0500000 849,144.546 μs 3,451.2756 μs 2,881.9714 μs 1.00 0.00 - - - 1464.93 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 1000000-0500000 806,284.180 μs 13,105.8916 μs 12,259.2592 μs 0.95 0.01 - - - 1464.93 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 1000000-0750000 1,124,194.693 μs 16,599.1463 μs 15,526.8517 μs 1.00 0.00 - - - 1709.12 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 1000000-0750000 1,069,443.869 μs 5,409.4313 μs 4,517.1201 μs 0.95 0.01 - - - 1709.12 KB 1.00
Multiply Job-IVRPVB \main\corerun.exe 1000000-1000000 1,286,760.120 μs 19,244.8875 μs 18,001.6797 μs 1.00 0.00 - - - 1953.87 KB 1.00
Multiply Job-VXHELJ \pr\corerun.exe 1000000-1000000 1,238,765.540 μs 10,636.9478 μs 9,949.8076 μs 0.96 0.02 - - - 1953.21 KB 1.00

@ghost
Copy link

ghost commented Feb 16, 2024

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

In my previous pull request #92208, I split BigIntegerCalculator.Multiply into two methods: MultiplyNearLength and MultiplyFarLength.
However, this division resulted in a lot of code duplication.

The reason for this division is that coreLength can be greater than bits.Length - n. Actually, core.Slice(0, ActualLength(core)) seems to be sufficient since ActualLength(core)) is never greater than bits.Length - n.

https://github.com/kzrnm/dotnet-runtime/blob/371cc444826ab0667f5e76b8419bc621084f2fb2/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs#L322-L366

Benchmark

BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);

public class Tests
{
    public IEnumerable<object> GetMultiplyArgs()
    {
        Random random = new Random(227);
        var lengths = new int[] { 1000, 10000, 100000, 1000000 };
        for (int i = lengths.Length - 1; i >= 0; i--)
        {
            var largeBytes = MakeBytes(random, lengths[i]);
            var large = new BigInteger(largeBytes, isUnsigned: true);
            {
                var smallBytes = MakeBytes(random, lengths[i] / 2);
                var small = new BigInteger(smallBytes, isUnsigned: true);
                yield return new Data($"{largeBytes.Length:D7}-{smallBytes.Length:D7}", large, small);
            }
            for (int j = i; j >= 0; j--)
            {
                var smallBytes = MakeBytes(random, lengths[j]);
                var small = new BigInteger(smallBytes, isUnsigned: true);
                yield return new Data($"{largeBytes.Length:D7}-{smallBytes.Length:D7}", large, small);
            }
        }
        static byte[] MakeBytes(Random random, int length)
        {
            var bytes = new byte[length];
            random.NextBytes(bytes);
            return bytes;
        }
    }

    public record Data(string Name, BigInteger Large, BigInteger Small)
    {
        public override string ToString() => Name;
    }

    [Benchmark]
    [ArgumentsSource(nameof(GetMultiplyArgs))]
    public BigInteger Multiply(Data data)
    {
        return data.Large * data.Small;
    }
}

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3155/23H2/2023Update/SunValley3)
13th Gen Intel Core i5-13500, 1 CPU, 20 logical and 14 physical cores
.NET SDK 9.0.100-alpha.1.23615.4
  [Host]     : .NET 9.0.0 (9.0.23.61410), X64 RyuJIT AVX2
  Job-URSBLV : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
  Job-TQMTJC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2


Method Job Toolchain data Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
Multiply Job-URSBLV \main\corerun.exe 0001000-0000500 13.12 μs 0.154 μs 0.137 μs 1.00 0.00 0.1068 - - 1.49 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0001000-0000500 13.25 μs 0.186 μs 0.174 μs 1.01 0.01 0.1068 - - 1.49 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0001000-0001000 20.37 μs 0.290 μs 0.271 μs 1.00 0.00 0.1526 - - 1.98 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0001000-0001000 19.59 μs 0.328 μs 0.307 μs 0.96 0.03 0.1526 - - 1.98 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0010000-0001000 206.28 μs 1.561 μs 1.384 μs 1.00 0.00 0.7324 - - 10.77 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0010000-0001000 208.83 μs 1.269 μs 1.125 μs 1.01 0.01 0.7324 - - 10.77 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0010000-0005000 513.09 μs 3.657 μs 3.420 μs 1.00 0.00 0.9766 - - 14.67 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0010000-0005000 535.01 μs 7.802 μs 7.298 μs 1.04 0.02 0.9766 - - 14.67 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0010000-0010000 816.73 μs 8.030 μs 7.511 μs 1.00 0.00 0.9766 - - 19.55 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0010000-0010000 814.75 μs 10.625 μs 9.939 μs 1.00 0.01 0.9766 - - 19.55 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0100000-0001000 2,146.54 μs 20.003 μs 18.711 μs 1.00 0.00 27.3438 27.3438 27.3438 98.67 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0100000-0001000 2,164.11 μs 17.230 μs 16.117 μs 1.01 0.01 27.3438 27.3438 27.3438 98.67 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0100000-0010000 8,421.19 μs 86.137 μs 80.572 μs 1.00 0.00 31.2500 31.2500 31.2500 107.47 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0100000-0010000 8,532.63 μs 79.239 μs 74.120 μs 1.01 0.01 31.2500 31.2500 31.2500 107.48 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0100000-0050000 20,429.37 μs 235.458 μs 220.247 μs 1.00 0.00 31.2500 31.2500 31.2500 146.55 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0100000-0050000 20,870.00 μs 267.251 μs 249.986 μs 1.02 0.01 31.2500 31.2500 31.2500 146.53 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 0100000-0100000 32,232.57 μs 634.088 μs 888.903 μs 1.00 0.00 - - - 195.38 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 0100000-0100000 30,761.56 μs 261.041 μs 244.178 μs 0.94 0.02 31.2500 31.2500 31.2500 195.36 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 1000000-0001000 21,984.70 μs 260.334 μs 243.516 μs 1.00 0.00 156.2500 156.2500 156.2500 977.67 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 1000000-0001000 21,369.03 μs 225.039 μs 210.502 μs 0.97 0.01 156.2500 156.2500 156.2500 977.68 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 1000000-0010000 89,806.51 μs 255.733 μs 199.660 μs 1.00 0.00 - - - 986.36 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 1000000-0010000 88,881.07 μs 878.241 μs 821.507 μs 0.99 0.01 - - - 986.42 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 1000000-0100000 348,310.20 μs 5,719.429 μs 5,349.957 μs 1.00 0.00 - - - 1074.3 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 1000000-0100000 337,064.95 μs 3,187.292 μs 2,981.395 μs 0.97 0.01 - - - 1074.3 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 1000000-0500000 829,713.72 μs 13,968.184 μs 13,065.848 μs 1.00 0.00 - - - 1464.93 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 1000000-0500000 815,624.28 μs 10,424.667 μs 9,751.240 μs 0.98 0.02 - - - 1465.59 KB 1.00
Multiply Job-URSBLV \main\corerun.exe 1000000-1000000 1,242,537.69 μs 15,210.633 μs 14,228.035 μs 1.00 0.00 - - - 1953.26 KB 1.00
Multiply Job-TQMTJC \pr\corerun.exe 1000000-1000000 1,233,080.07 μs 11,159.819 μs 10,438.902 μs 0.99 0.02 - - - 1953.26 KB 1.00
Author: kzrnm
Assignees: -
Labels:

area-System.Numerics

Milestone: -

@kzrnm kzrnm marked this pull request as draft February 17, 2024 03:04
@kzrnm kzrnm marked this pull request as ready for review February 17, 2024 04:30
@teo-tsirpanis teo-tsirpanis added the community-contribution Indicates that the PR has been added by a community member label Feb 17, 2024
@tannergooding tannergooding merged commit 9eac175 into dotnet:main May 10, 2024
83 checks passed
@kzrnm kzrnm deleted the BigIntegerMultiplySlim branch May 11, 2024 09:37
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Jun 11, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Numerics community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants