System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

Description

@speshuric

Description

System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

Main idea can be demonstrated on this part of Add method:

inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

Analysis

BigIntegerCalculator now contains 6 static metods for add and subtract:

  • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
  • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
  • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
  • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
  • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
  • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

Following statements are correct for "target loop"

  1. carry can be 1 or 0
  2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
  3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

So it can be rewritten as follows:

inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

Methods of data movement

In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

  1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
  2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

Best and worst cases

The best case of argument data for the new version is such values of argument arrays when carry == 0.
The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

Draft benchmarks

I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

  • AddOld - code in the current runtime
  • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
  • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

Two cases are tested:

  • bad - when all left[i] and right[i] are uint.MaxValue
  • good - when all left[i] and right[i] are 1

Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

Valuable draft benchmark points

  • In general new versions are noticeably faster.
  • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
  • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
  • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
  • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

To do's

  • Initial description, implement one method and make draft benchmarks
  • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
  • Implement all methods
  • Full end-to-end benchmarks
  • Pull request

Open questions

  • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

Data

Draft benchmark results are below. I will publish the code of benchmark soon.

Data of draft benchmarikng (click to expand)

MethodCaseleftSizerightSizeMeanErrorStdDevMedian
BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

    Description

    @speshuric

    Description

    System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

    Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

    Main idea can be demonstrated on this part of Add method:

    inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

    In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

    Analysis

    BigIntegerCalculator now contains 6 static metods for add and subtract:

    • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
    • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
    • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
    • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
    • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
    • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

    AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

    Following statements are correct for "target loop"

    1. carry can be 1 or 0
    2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
    3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

    So it can be rewritten as follows:

    inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

    Methods of data movement

    In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

    1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
    2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

    On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

    Best and worst cases

    The best case of argument data for the new version is such values of argument arrays when carry == 0.
    The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
    There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

    Draft benchmarks

    I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

    • AddOld - code in the current runtime
    • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
    • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

    Two cases are tested:

    • bad - when all left[i] and right[i] are uint.MaxValue
    • good - when all left[i] and right[i] are 1

    Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

    In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

    Valuable draft benchmark points

    • In general new versions are noticeably faster.
    • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
    • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
    • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
    • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

    Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

    To do's

    • Initial description, implement one method and make draft benchmarks
    • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
    • Implement all methods
    • Full end-to-end benchmarks
    • Pull request

    Open questions

    • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

    Data

    Draft benchmark results are below. I will publish the code of benchmark soon.

    Data of draft benchmarikng (click to expand)

    MethodCaseleftSizerightSizeMeanErrorStdDevMedian
    BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
    BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
    BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
    BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
    BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
    BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
    BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
    BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
    BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
    BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
    BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
    BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
    BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
    BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
    BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
    BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
    BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
    BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
    BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
    BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
    BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
    BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
    BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
    BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
    BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
    BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
    BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
    BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
    BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
    BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
    BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
    BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
    BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
    BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
    BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
    BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
    BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
    BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
    BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
    BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
    BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
    BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
    BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
    BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
    BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
    BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
    BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
    BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
    BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
    BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
    BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
    BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
    BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
    BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
    BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
    BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
    BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
    BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
    BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
    BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
    BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
    BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
    BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
    BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
    BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
    BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
    BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
    BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
    BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
    BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
    BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
    BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
    BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
    BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
    BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
    BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
    BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
    BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
    BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
    BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
    BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
    BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
    BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
    BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
    BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
    BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
    BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
    BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
    BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
    BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
    BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
    BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
    BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
    BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
    BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
    BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
    BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
    BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
    BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
    BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
    BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
    BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
    BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
    BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
    BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
    BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
    BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
    BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
    BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
    BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
    BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
    BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
    BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
    BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
    BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
    BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
    BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
    BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
    BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
    BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
    BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
    BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
    BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
    BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
    BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
    BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
    BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
    BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
    BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
    BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
    BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
    BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
    BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
    BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
    BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
    BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
    BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
    BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
    BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
    BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
    BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
    BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
    BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
    BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
    BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
    BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
    BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
    BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
    BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
    BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
    BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
    BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
    BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
    BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
    BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
    BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
    BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
    BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
    BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
    BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
    BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
    BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
    BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
    BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
    BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
    BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
    BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
    BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
    BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
    BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
    BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
    BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
    BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
    BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
    BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
    BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
    BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
    BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
    BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
    BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
    BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
    BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
    BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
    BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
    BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
    BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
    BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
    BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
    BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
    BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
    BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
    BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
    BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
    BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
    BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
    BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
    BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
    BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
    BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
    BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
    BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
    BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
    BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
    BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
    BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
    BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
    BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
    BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
    BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
    BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
    BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
    BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
    BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
    BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
    BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
    BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

    Metadata

    Metadata

    Assignees

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

      Description

      @speshuric

      Description

      System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

      Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

      Main idea can be demonstrated on this part of Add method:

      inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

      In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

      Analysis

      BigIntegerCalculator now contains 6 static metods for add and subtract:

      • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
      • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
      • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
      • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
      • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
      • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

      AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

      Following statements are correct for "target loop"

      1. carry can be 1 or 0
      2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
      3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

      So it can be rewritten as follows:

      inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

      Methods of data movement

      In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

      1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
      2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

      On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

      Best and worst cases

      The best case of argument data for the new version is such values of argument arrays when carry == 0.
      The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
      There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

      Draft benchmarks

      I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

      • AddOld - code in the current runtime
      • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
      • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

      Two cases are tested:

      • bad - when all left[i] and right[i] are uint.MaxValue
      • good - when all left[i] and right[i] are 1

      Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

      In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

      Valuable draft benchmark points

      • In general new versions are noticeably faster.
      • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
      • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
      • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
      • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

      Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

      To do's

      • Initial description, implement one method and make draft benchmarks
      • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
      • Implement all methods
      • Full end-to-end benchmarks
      • Pull request

      Open questions

      • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

      Data

      Draft benchmark results are below. I will publish the code of benchmark soon.

      Data of draft benchmarikng (click to expand)

      MethodCaseleftSizerightSizeMeanErrorStdDevMedian
      BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
      BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
      BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
      BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
      BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
      BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
      BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
      BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
      BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
      BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
      BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
      BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
      BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
      BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
      BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
      BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
      BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
      BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
      BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
      BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
      BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
      BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
      BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
      BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
      BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
      BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
      BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
      BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
      BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
      BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
      BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
      BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
      BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
      BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
      BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
      BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
      BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
      BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
      BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
      BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
      BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
      BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
      BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
      BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
      BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
      BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
      BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
      BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
      BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
      BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
      BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
      BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
      BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
      BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
      BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
      BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
      BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
      BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
      BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
      BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
      BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
      BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
      BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
      BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
      BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
      BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
      BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
      BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
      BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
      BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
      BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
      BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
      BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
      BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
      BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
      BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
      BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
      BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
      BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
      BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
      BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
      BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
      BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
      BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
      BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
      BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
      BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
      BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
      BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
      BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
      BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
      BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
      BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
      BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
      BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
      BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
      BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
      BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
      BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
      BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
      BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
      BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
      BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
      BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
      BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
      BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
      BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
      BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
      BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
      BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
      BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
      BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
      BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
      BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
      BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
      BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
      BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
      BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
      BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
      BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
      BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
      BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
      BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
      BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
      BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
      BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
      BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
      BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
      BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
      BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
      BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
      BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
      BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
      BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
      BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
      BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
      BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
      BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
      BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
      BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
      BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
      BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
      BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
      BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
      BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
      BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
      BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
      BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
      BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
      BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
      BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
      BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
      BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
      BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
      BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
      BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
      BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
      BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
      BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
      BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
      BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
      BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
      BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
      BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
      BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
      BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
      BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
      BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
      BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
      BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
      BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
      BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
      BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
      BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
      BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
      BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
      BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
      BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
      BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
      BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
      BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
      BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
      BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
      BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
      BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
      BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
      BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
      BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
      BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
      BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
      BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
      BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
      BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
      BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
      BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
      BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
      BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
      BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
      BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
      BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
      BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
      BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
      BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
      BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
      BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
      BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
      BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
      BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
      BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
      BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
      BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
      BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
      BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
      BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
      BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
      BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

      Metadata

      Metadata

      Assignees

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

        Description

        @speshuric

        Description

        System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

        Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

        Main idea can be demonstrated on this part of Add method:

        inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

        In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

        Analysis

        BigIntegerCalculator now contains 6 static metods for add and subtract:

        • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
        • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
        • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
        • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
        • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
        • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

        AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

        Following statements are correct for "target loop"

        1. carry can be 1 or 0
        2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
        3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

        So it can be rewritten as follows:

        inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

        Methods of data movement

        In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

        1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
        2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

        On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

        Best and worst cases

        The best case of argument data for the new version is such values of argument arrays when carry == 0.
        The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
        There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

        Draft benchmarks

        I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

        • AddOld - code in the current runtime
        • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
        • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

        Two cases are tested:

        • bad - when all left[i] and right[i] are uint.MaxValue
        • good - when all left[i] and right[i] are 1

        Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

        In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

        Valuable draft benchmark points

        • In general new versions are noticeably faster.
        • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
        • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
        • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
        • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

        Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

        To do's

        • Initial description, implement one method and make draft benchmarks
        • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
        • Implement all methods
        • Full end-to-end benchmarks
        • Pull request

        Open questions

        • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

        Data

        Draft benchmark results are below. I will publish the code of benchmark soon.

        Data of draft benchmarikng (click to expand)

        MethodCaseleftSizerightSizeMeanErrorStdDevMedian
        BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
        BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
        BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
        BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
        BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
        BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
        BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
        BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
        BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
        BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
        BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
        BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
        BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
        BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
        BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
        BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
        BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
        BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
        BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
        BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
        BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
        BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
        BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
        BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
        BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
        BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
        BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
        BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
        BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
        BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
        BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
        BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
        BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
        BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
        BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
        BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
        BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
        BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
        BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
        BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
        BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
        BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
        BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
        BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
        BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
        BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
        BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
        BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
        BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
        BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
        BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
        BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
        BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
        BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
        BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
        BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
        BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
        BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
        BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
        BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
        BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
        BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
        BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
        BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
        BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
        BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
        BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
        BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
        BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
        BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
        BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
        BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
        BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
        BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
        BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
        BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
        BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
        BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
        BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
        BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
        BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
        BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
        BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
        BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
        BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
        BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
        BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
        BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
        BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
        BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
        BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
        BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
        BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
        BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
        BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
        BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
        BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
        BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
        BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
        BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
        BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
        BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
        BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
        BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
        BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
        BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
        BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
        BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
        BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
        BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
        BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
        BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
        BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
        BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
        BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
        BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
        BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
        BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
        BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
        BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
        BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
        BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
        BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
        BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
        BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
        BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
        BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
        BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
        BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
        BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
        BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
        BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
        BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
        BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
        BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
        BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
        BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
        BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
        BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
        BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
        BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
        BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
        BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
        BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
        BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
        BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
        BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
        BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
        BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
        BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
        BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
        BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
        BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
        BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
        BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
        BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
        BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
        BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
        BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
        BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
        BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
        BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
        BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
        BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
        BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
        BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
        BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
        BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
        BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
        BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
        BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
        BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
        BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
        BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
        BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
        BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
        BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
        BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
        BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
        BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
        BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
        BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
        BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
        BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
        BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
        BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
        BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
        BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
        BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
        BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
        BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
        BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
        BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
        BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
        BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
        BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
        BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
        BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
        BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
        BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
        BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
        BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
        BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
        BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
        BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
        BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
        BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
        BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
        BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
        BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
        BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
        BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
        BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
        BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
        BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
        BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

        Metadata

        Metadata

        Assignees

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

          Description

          @speshuric

          Description

          System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

          Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

          Main idea can be demonstrated on this part of Add method:

          inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

          In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

          Analysis

          BigIntegerCalculator now contains 6 static metods for add and subtract:

          • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
          • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
          • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
          • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
          • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
          • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

          AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

          Following statements are correct for "target loop"

          1. carry can be 1 or 0
          2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
          3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

          So it can be rewritten as follows:

          inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

          Methods of data movement

          In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

          1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
          2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

          On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

          Best and worst cases

          The best case of argument data for the new version is such values of argument arrays when carry == 0.
          The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
          There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

          Draft benchmarks

          I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

          • AddOld - code in the current runtime
          • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
          • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

          Two cases are tested:

          • bad - when all left[i] and right[i] are uint.MaxValue
          • good - when all left[i] and right[i] are 1

          Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

          In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

          Valuable draft benchmark points

          • In general new versions are noticeably faster.
          • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
          • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
          • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
          • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

          Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

          To do's

          • Initial description, implement one method and make draft benchmarks
          • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
          • Implement all methods
          • Full end-to-end benchmarks
          • Pull request

          Open questions

          • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

          Data

          Draft benchmark results are below. I will publish the code of benchmark soon.

          Data of draft benchmarikng (click to expand)

          MethodCaseleftSizerightSizeMeanErrorStdDevMedian
          BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
          BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
          BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
          BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
          BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
          BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
          BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
          BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
          BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
          BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
          BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
          BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
          BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
          BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
          BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
          BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
          BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
          BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
          BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
          BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
          BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
          BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
          BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
          BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
          BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
          BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
          BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
          BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
          BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
          BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
          BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
          BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
          BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
          BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
          BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
          BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
          BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
          BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
          BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
          BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
          BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
          BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
          BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
          BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
          BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
          BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
          BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
          BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
          BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
          BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
          BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
          BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
          BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
          BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
          BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
          BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
          BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
          BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
          BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
          BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
          BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
          BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
          BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
          BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
          BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
          BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
          BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
          BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
          BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
          BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
          BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
          BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
          BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
          BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
          BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
          BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
          BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
          BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
          BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
          BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
          BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
          BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
          BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
          BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
          BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
          BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
          BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
          BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
          BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
          BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
          BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
          BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
          BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
          BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
          BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
          BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
          BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
          BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
          BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
          BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
          BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
          BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
          BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
          BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
          BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
          BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
          BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
          BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
          BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
          BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
          BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
          BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
          BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
          BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
          BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
          BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
          BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
          BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
          BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
          BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
          BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
          BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
          BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
          BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
          BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
          BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
          BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
          BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
          BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
          BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
          BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
          BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
          BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
          BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
          BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
          BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
          BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
          BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
          BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
          BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
          BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
          BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
          BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
          BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
          BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
          BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
          BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
          BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
          BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
          BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
          BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
          BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
          BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
          BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
          BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
          BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
          BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
          BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
          BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
          BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
          BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
          BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
          BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
          BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
          BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
          BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
          BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
          BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
          BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
          BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
          BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
          BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
          BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
          BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
          BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
          BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
          BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
          BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
          BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
          BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
          BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
          BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
          BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
          BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
          BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
          BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
          BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
          BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
          BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
          BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
          BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
          BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
          BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
          BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
          BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
          BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
          BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
          BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
          BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
          BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
          BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
          BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
          BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
          BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
          BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
          BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
          BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
          BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
          BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
          BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
          BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
          BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
          BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
          BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
          BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
          BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

          Metadata

          Metadata

          Assignees

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

            Description

            @speshuric

            Description

            System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

            Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

            Main idea can be demonstrated on this part of Add method:

            inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

            In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

            Analysis

            BigIntegerCalculator now contains 6 static metods for add and subtract:

            • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
            • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
            • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
            • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
            • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
            • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

            AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

            Following statements are correct for "target loop"

            1. carry can be 1 or 0
            2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
            3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

            So it can be rewritten as follows:

            inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

            Methods of data movement

            In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

            1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
            2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

            On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

            Best and worst cases

            The best case of argument data for the new version is such values of argument arrays when carry == 0.
            The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
            There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

            Draft benchmarks

            I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

            • AddOld - code in the current runtime
            • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
            • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

            Two cases are tested:

            • bad - when all left[i] and right[i] are uint.MaxValue
            • good - when all left[i] and right[i] are 1

            Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

            In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

            Valuable draft benchmark points

            • In general new versions are noticeably faster.
            • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
            • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
            • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
            • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

            Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

            To do's

            • Initial description, implement one method and make draft benchmarks
            • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
            • Implement all methods
            • Full end-to-end benchmarks
            • Pull request

            Open questions

            • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

            Data

            Draft benchmark results are below. I will publish the code of benchmark soon.

            Data of draft benchmarikng (click to expand)

            MethodCaseleftSizerightSizeMeanErrorStdDevMedian
            BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
            BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
            BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
            BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
            BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
            BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
            BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
            BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
            BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
            BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
            BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
            BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
            BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
            BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
            BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
            BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
            BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
            BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
            BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
            BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
            BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
            BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
            BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
            BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
            BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
            BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
            BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
            BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
            BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
            BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
            BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
            BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
            BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
            BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
            BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
            BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
            BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
            BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
            BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
            BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
            BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
            BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
            BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
            BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
            BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
            BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
            BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
            BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
            BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
            BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
            BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
            BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
            BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
            BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
            BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
            BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
            BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
            BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
            BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
            BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
            BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
            BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
            BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
            BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
            BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
            BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
            BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
            BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
            BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
            BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
            BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
            BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
            BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
            BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
            BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
            BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
            BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
            BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
            BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
            BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
            BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
            BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
            BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
            BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
            BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
            BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
            BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
            BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
            BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
            BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
            BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
            BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
            BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
            BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
            BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
            BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
            BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
            BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
            BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
            BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
            BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
            BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
            BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
            BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
            BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
            BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
            BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
            BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
            BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
            BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
            BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
            BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
            BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
            BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
            BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
            BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
            BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
            BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
            BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
            BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
            BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
            BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
            BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
            BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
            BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
            BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
            BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
            BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
            BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
            BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
            BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
            BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
            BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
            BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
            BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
            BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
            BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
            BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
            BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
            BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
            BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
            BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
            BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
            BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
            BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
            BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
            BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
            BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
            BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
            BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
            BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
            BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
            BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
            BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
            BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
            BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
            BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
            BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
            BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
            BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
            BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
            BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
            BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
            BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
            BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
            BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
            BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
            BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
            BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
            BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
            BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
            BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
            BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
            BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
            BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
            BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
            BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
            BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
            BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
            BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
            BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
            BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
            BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
            BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
            BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
            BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
            BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
            BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
            BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
            BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
            BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
            BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
            BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
            BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
            BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
            BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
            BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
            BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
            BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
            BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
            BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
            BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
            BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
            BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
            BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
            BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
            BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
            BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
            BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
            BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
            BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
            BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
            BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
            BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
            BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
            BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

            Metadata

            Metadata

            Assignees

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

              Description

              @speshuric

              Description

              System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

              Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

              Main idea can be demonstrated on this part of Add method:

              inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

              In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

              Analysis

              BigIntegerCalculator now contains 6 static metods for add and subtract:

              • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
              • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
              • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
              • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
              • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
              • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

              AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

              Following statements are correct for "target loop"

              1. carry can be 1 or 0
              2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
              3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

              So it can be rewritten as follows:

              inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

              Methods of data movement

              In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

              1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
              2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

              On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

              Best and worst cases

              The best case of argument data for the new version is such values of argument arrays when carry == 0.
              The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
              There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

              Draft benchmarks

              I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

              • AddOld - code in the current runtime
              • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
              • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

              Two cases are tested:

              • bad - when all left[i] and right[i] are uint.MaxValue
              • good - when all left[i] and right[i] are 1

              Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

              In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

              Valuable draft benchmark points

              • In general new versions are noticeably faster.
              • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
              • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
              • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
              • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

              Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

              To do's

              • Initial description, implement one method and make draft benchmarks
              • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
              • Implement all methods
              • Full end-to-end benchmarks
              • Pull request

              Open questions

              • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

              Data

              Draft benchmark results are below. I will publish the code of benchmark soon.

              Data of draft benchmarikng (click to expand)

              MethodCaseleftSizerightSizeMeanErrorStdDevMedian
              BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
              BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
              BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
              BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
              BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
              BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
              BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
              BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
              BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
              BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
              BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
              BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
              BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
              BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
              BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
              BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
              BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
              BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
              BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
              BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
              BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
              BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
              BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
              BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
              BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
              BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
              BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
              BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
              BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
              BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
              BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
              BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
              BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
              BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
              BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
              BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
              BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
              BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
              BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
              BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
              BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
              BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
              BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
              BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
              BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
              BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
              BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
              BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
              BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
              BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
              BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
              BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
              BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
              BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
              BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
              BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
              BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
              BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
              BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
              BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
              BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
              BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
              BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
              BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
              BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
              BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
              BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
              BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
              BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
              BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
              BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
              BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
              BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
              BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
              BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
              BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
              BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
              BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
              BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
              BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
              BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
              BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
              BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
              BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
              BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
              BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
              BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
              BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
              BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
              BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
              BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
              BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
              BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
              BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
              BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
              BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
              BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
              BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
              BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
              BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
              BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
              BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
              BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
              BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
              BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
              BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
              BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
              BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
              BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
              BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
              BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
              BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
              BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
              BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
              BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
              BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
              BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
              BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
              BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
              BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
              BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
              BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
              BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
              BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
              BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
              BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
              BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
              BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
              BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
              BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
              BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
              BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
              BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
              BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
              BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
              BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
              BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
              BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
              BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
              BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
              BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
              BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
              BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
              BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
              BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
              BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
              BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
              BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
              BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
              BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
              BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
              BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
              BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
              BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
              BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
              BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
              BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
              BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
              BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
              BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
              BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
              BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
              BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
              BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
              BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
              BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
              BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
              BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
              BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
              BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
              BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
              BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
              BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
              BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
              BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
              BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
              BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
              BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
              BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
              BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
              BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
              BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
              BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
              BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
              BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
              BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
              BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
              BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
              BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
              BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
              BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
              BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
              BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
              BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
              BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
              BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
              BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
              BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
              BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
              BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
              BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
              BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
              BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
              BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
              BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
              BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
              BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
              BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
              BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
              BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
              BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
              BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
              BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
              BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
              BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
              BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

              Metadata

              Metadata

              Assignees

              Type

              No type

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                System.Numerics.BigInteger: Add/Subtract performance can be improved when size of arguments are different #83457

                Description

                @speshuric

                Description

                System.Numerics.BigInteger add and subtract operations for non-trivial cases are implemented in Add/Subtract static methods of internal class BigIntegerCalculator. Current implementation can be improved by special handling the case of carry==0 when the current position being processed goes beyond the length of the right (short) argument, but does not exceed the length of the left (long) argument.

                Reproducing the issue is possible in most environments. This is not a regression but a new optimization.

                Main idea can be demonstrated on this part of Add method:

                inti=0;longcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++){longdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;i<left.Length;i++){// "target loop"longdigit=left[i]+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}Unsafe.Add(refresultPtr,i)=(uint)carry;

                In the second loop (marked as // "target loop") once carry is set to 0 it can not be 1 anymore. So the tail of the loop is just the movement of argument values to result span.

                Analysis

                BigIntegerCalculator now contains 6 static metods for add and subtract:

                • public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits); - used when right argument length is 1
                • public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for addition
                • private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop
                • public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits) - used when right argument length is 1
                • public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) - default algorithm for subtraction
                • private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right) - checks carry and breaks the loop

                AddSelf and SubtractSelf used in internals of SquMul part of BigIntegerCalculator, cannot be optimized this way and are not considered below. Add and Subtract can be optimized almost identically so only case of Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits) will be used to describe.

                Following statements are correct for "target loop"

                1. carry can be 1 or 0
                2. If income carry is 1 then outcome carry can be 1 if and only if left[i] == uint.MaxValue and result[i]==0.
                3. If income carry is 0 then result[i] = left[i] for every next i. This assignment can be optimized by remove any arithmetic and use of special platform-optimized methods of copying data.

                So it can be rewritten as follows:

                inti=0;ulongcarry=0L;// ...refuintleftPtr=refMemoryMarshal.GetReference(left);refuintresultPtr=refMemoryMarshal.GetReference(bits);// ...for(;i<right.Length;i++)// this loop was not modified{ulongdigit=(Unsafe.Add(refleftPtr,i)+carry)+right[i];Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}for(;carry!=0&&i<left.Length;i++)// carry != 0 is checked{ulongdigit=Unsafe.Add(refleftPtr,i)+carry;Unsafe.Add(refresultPtr,i)=unchecked((uint)digit);carry=digit>>32;}if(i<left.Length){// only move data from left argument to resultdo{Unsafe.Add(refresultPtr,i)= Unsafe.Add(refleftPtr,i);i++;}while(i<left.Length);// Note: if left part to copy is large then special method CopyTo is better//left.Slice(i).CopyTo(bits.Slice(i));i=left.Length;carry=0;}Unsafe.Add(refresultPtr,i)=unchecked((uint)carry);

                Methods of data movement

                In this variant, second loop checks when carry become 0 and then special case (plain movement) is triggered. Two possible ways to copy data can be considered:

                1. Loop with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++;
                2. Copy with platform-dependent Span.CopyTo(): left.Slice(i).CopyTo(bits.Slice(i));. This internally calls high optimized Buffer.Memmove() but Span.CopyTo() do some redundant checks and thus can be slower on short slices.

                On my PC (CPU Ryzen 5700G) in my draft benchmarks the second is faster when approximately left.Length - right.Length >= 16

                Best and worst cases

                The best case of argument data for the new version is such values of argument arrays when carry == 0.
                The worst case is when carry is always 1, i.e. all left[i] == uint.MaxValue.
                There should be more difference when left.Length - right.Length is large and shouldn't difference when they are equals.

                Draft benchmarks

                I have written some draft microbenchmarks to measure the difference. These benchmarks test 3 versions of Add method:

                • AddOld - code in the current runtime
                • AddNew - with Unsafe.Add(ref resultPtr, i) = Unsafe.Add(ref leftPtr, i); i++; loop
                • AddNewMemmove with left.Slice(i).CopyTo(bits.Slice(i)); without loop

                Two cases are tested:

                • bad - when all left[i] and right[i] are uint.MaxValue
                • good - when all left[i] and right[i] are 1

                Tests cover left.Length from 2 to 256 (doubles on each step, i.e. 2, 4, 8, 16, 32, 64, 128, 256) and right.Length from 2 to left.Length (doubles each step too).

                In draft benchmark attribute [InvocationCount(10000000)] was used, hence there can be some inaccuracies on small lengths.

                Valuable draft benchmark points

                • In general new versions are noticeably faster.
                • In the worst cases for new versions, slowest of new version has max regression about 0,5-1 ns per invocation. But should be noted that whole BigInteger Add operation contains some extra memcopies and much more logic than internal Add method. It needs some more analysis, but at first look it seems that regressions are insignificant.
                • Max speedup of AddNew version (as expected) is observed in case of good arrays with right.Length 2 uints and left.Length 256 uints. Old version mean is 122.771 ns, AddNew mean is 65.197 ns.
                • AddNewMemmove version is in the same test (good, 256, 2) is even faster and it's mean is 12.797 ns. Almost 10x faster than old version!
                • AddNewMemmove version faster than AddNew when left.Length - right.Length >= 16

                Important note: Add method takes only part time of whole operation. End-to-end results will be comparable in absolute time difference but relative difference will differ much less.

                To do's

                • Initial description, implement one method and make draft benchmarks
                • Choose new implementation ("ref to ref" or "Slice.CopyTo(Slice)")
                • Implement all methods
                • Full end-to-end benchmarks
                • Pull request

                Open questions

                • Which implementation is preferable: "ref to ref" or "Slice.CopyTo(Slice)" or size-dependent?

                Data

                Draft benchmark results are below. I will publish the code of benchmark soon.

                Data of draft benchmarikng (click to expand)

                MethodCaseleftSizerightSizeMeanErrorStdDevMedian
                BenchAddOldbad222.585 ns0.0269 ns0.0225 ns2.577 ns
                BenchAddNewbad222.903 ns0.0597 ns0.0558 ns2.895 ns
                BenchAddNewMemmovebad223.204 ns0.0942 ns0.1548 ns3.175 ns
                BenchAddOldbad423.520 ns0.0629 ns0.0588 ns3.485 ns
                BenchAddNewbad423.603 ns0.0857 ns0.0760 ns3.613 ns
                BenchAddNewMemmovebad423.996 ns0.0945 ns0.0789 ns3.954 ns
                BenchAddOldbad443.331 ns0.0771 ns0.0721 ns3.312 ns
                BenchAddNewbad443.560 ns0.0967 ns0.0950 ns3.512 ns
                BenchAddNewMemmovebad444.239 ns0.0714 ns0.0668 ns4.199 ns
                BenchAddOldbad825.434 ns0.1023 ns0.0957 ns5.422 ns
                BenchAddNewbad825.008 ns0.1292 ns0.1327 ns4.914 ns
                BenchAddNewMemmovebad825.871 ns0.0992 ns0.0879 ns5.888 ns
                BenchAddOldbad845.394 ns0.0892 ns0.0834 ns5.395 ns
                BenchAddNewbad845.240 ns0.1256 ns0.1113 ns5.224 ns
                BenchAddNewMemmovebad846.094 ns0.1163 ns0.1087 ns6.069 ns
                BenchAddOldbad885.094 ns0.0092 ns0.0072 ns5.093 ns
                BenchAddNewbad885.579 ns0.0370 ns0.0346 ns5.585 ns
                BenchAddNewMemmovebad885.996 ns0.0217 ns0.0181 ns5.995 ns
                BenchAddOldbad1628.732 ns0.0075 ns0.0062 ns8.732 ns
                BenchAddNewbad1628.359 ns0.0644 ns0.0571 ns8.338 ns
                BenchAddNewMemmovebad1628.802 ns0.0741 ns0.0619 ns8.782 ns
                BenchAddOldbad1648.774 ns0.0564 ns0.0527 ns8.751 ns
                BenchAddNewbad1648.539 ns0.0758 ns0.0672 ns8.535 ns
                BenchAddNewMemmovebad1649.685 ns0.0628 ns0.0587 ns9.651 ns
                BenchAddOldbad1688.776 ns0.0288 ns0.0256 ns8.768 ns
                BenchAddNewbad1688.455 ns0.0524 ns0.0437 ns8.452 ns
                BenchAddNewMemmovebad1689.643 ns0.0213 ns0.0166 ns9.640 ns
                BenchAddOldbad16169.011 ns0.0209 ns0.0163 ns9.015 ns
                BenchAddNewbad16169.230 ns0.0147 ns0.0115 ns9.232 ns
                BenchAddNewMemmovebad16169.495 ns0.0252 ns0.0210 ns9.488 ns
                BenchAddOldbad32215.952 ns0.0244 ns0.0228 ns15.953 ns
                BenchAddNewbad32216.422 ns0.1061 ns0.0941 ns16.413 ns
                BenchAddNewMemmovebad32216.917 ns0.0140 ns0.0124 ns16.918 ns
                BenchAddOldbad32416.022 ns0.0115 ns0.0090 ns16.023 ns
                BenchAddNewbad32416.499 ns0.0428 ns0.0401 ns16.490 ns
                BenchAddNewMemmovebad32416.875 ns0.0601 ns0.0533 ns16.882 ns
                BenchAddOldbad32816.153 ns0.0617 ns0.0547 ns16.148 ns
                BenchAddNewbad32816.063 ns0.1146 ns0.1016 ns16.035 ns
                BenchAddNewMemmovebad32816.960 ns0.0484 ns0.0404 ns16.947 ns
                BenchAddOldbad321616.665 ns0.0260 ns0.0243 ns16.659 ns
                BenchAddNewbad321616.928 ns0.0542 ns0.0452 ns16.929 ns
                BenchAddNewMemmovebad321617.373 ns0.0623 ns0.0583 ns17.376 ns
                BenchAddOldbad323217.253 ns0.0250 ns0.0221 ns17.260 ns
                BenchAddNewbad323217.515 ns0.0730 ns0.0570 ns17.513 ns
                BenchAddNewMemmovebad323218.008 ns0.0164 ns0.0153 ns18.007 ns
                BenchAddOldbad64230.824 ns0.0548 ns0.0458 ns30.817 ns
                BenchAddNewbad64230.854 ns0.0239 ns0.0187 ns30.858 ns
                BenchAddNewMemmovebad64231.479 ns0.0456 ns0.0404 ns31.481 ns
                BenchAddOldbad64430.796 ns0.0182 ns0.0161 ns30.793 ns
                BenchAddNewbad64429.935 ns0.0929 ns0.0869 ns29.923 ns
                BenchAddNewMemmovebad64430.630 ns0.1631 ns0.1526 ns30.653 ns
                BenchAddOldbad64830.775 ns0.0837 ns0.0783 ns30.784 ns
                BenchAddNewbad64831.110 ns0.0434 ns0.0385 ns31.101 ns
                BenchAddNewMemmovebad64831.391 ns0.0681 ns0.0603 ns31.402 ns
                BenchAddOldbad641631.845 ns0.0911 ns0.0852 ns31.856 ns
                BenchAddNewbad641632.334 ns0.0686 ns0.0641 ns32.316 ns
                BenchAddNewMemmovebad641632.012 ns0.0781 ns0.0730 ns32.003 ns
                BenchAddOldbad643234.869 ns0.0747 ns0.0698 ns34.873 ns
                BenchAddNewbad643235.400 ns0.1425 ns0.1333 ns35.426 ns
                BenchAddNewMemmovebad643234.899 ns0.0755 ns0.0707 ns34.914 ns
                BenchAddOldbad646437.207 ns0.1676 ns0.1568 ns37.125 ns
                BenchAddNewbad646437.827 ns0.0352 ns0.0312 ns37.828 ns
                BenchAddNewMemmovebad646438.326 ns0.0473 ns0.0442 ns38.324 ns
                BenchAddOldbad128264.390 ns0.0895 ns0.0747 ns64.372 ns
                BenchAddNewbad128264.430 ns0.1261 ns0.1117 ns64.400 ns
                BenchAddNewMemmovebad128261.757 ns0.0785 ns0.0696 ns61.758 ns
                BenchAddOldbad128464.489 ns0.0966 ns0.0856 ns64.508 ns
                BenchAddNewbad128461.862 ns0.1050 ns0.0982 ns61.818 ns
                BenchAddNewMemmovebad128464.728 ns0.0337 ns0.0299 ns64.733 ns
                BenchAddOldbad128859.975 ns0.0802 ns0.0711 ns59.958 ns
                BenchAddNewbad128860.545 ns0.0964 ns0.0902 ns60.567 ns
                BenchAddNewMemmovebad128860.686 ns0.0538 ns0.0477 ns60.691 ns
                BenchAddOldbad1281660.303 ns0.1191 ns0.1056 ns60.333 ns
                BenchAddNewbad1281661.590 ns0.7334 ns0.6860 ns61.163 ns
                BenchAddNewMemmovebad1281661.623 ns0.0646 ns0.0573 ns61.618 ns
                BenchAddOldbad1283263.400 ns0.2310 ns0.2048 ns63.358 ns
                BenchAddNewbad1283264.186 ns0.2440 ns0.2282 ns64.057 ns
                BenchAddNewMemmovebad1283264.191 ns0.0389 ns0.0304 ns64.195 ns
                BenchAddOldbad1286469.698 ns0.1385 ns0.1228 ns69.638 ns
                BenchAddNewbad1286470.742 ns0.2145 ns0.2006 ns70.772 ns
                BenchAddNewMemmovebad1286470.199 ns0.2155 ns0.1800 ns70.201 ns
                BenchAddOldbad12812881.541 ns0.0562 ns0.0526 ns81.522 ns
                BenchAddNewbad12812881.757 ns0.0175 ns0.0163 ns81.761 ns
                BenchAddNewMemmovebad12812882.336 ns0.0408 ns0.0362 ns82.331 ns
                BenchAddOldbad2562122.853 ns0.2910 ns0.2579 ns122.735 ns
                BenchAddNewbad2562122.601 ns0.0401 ns0.0335 ns122.603 ns
                BenchAddNewMemmovebad2562118.404 ns0.4241 ns0.3967 ns118.360 ns
                BenchAddOldbad2564122.572 ns0.0952 ns0.0844 ns122.575 ns
                BenchAddNewbad2564123.188 ns0.0382 ns0.0358 ns123.190 ns
                BenchAddNewMemmovebad2564118.899 ns0.3367 ns0.3150 ns119.049 ns
                BenchAddOldbad2568122.766 ns0.0753 ns0.0629 ns122.782 ns
                BenchAddNewbad2568118.796 ns0.4198 ns0.3927 ns118.989 ns
                BenchAddNewMemmovebad2568121.238 ns0.2532 ns0.2369 ns121.260 ns
                BenchAddOldbad25616123.677 ns0.2106 ns0.1970 ns123.688 ns
                BenchAddNewbad25616125.204 ns0.0515 ns0.0482 ns125.195 ns
                BenchAddNewMemmovebad25616125.459 ns0.0575 ns0.0537 ns125.466 ns
                BenchAddOldbad25632123.549 ns0.2009 ns0.1879 ns123.557 ns
                BenchAddNewbad25632127.413 ns0.2725 ns0.2549 ns127.377 ns
                BenchAddNewMemmovebad25632128.176 ns0.1296 ns0.1149 ns128.191 ns
                BenchAddOldbad25664129.709 ns0.1311 ns0.1226 ns129.692 ns
                BenchAddNewbad25664132.365 ns0.0854 ns0.0757 ns132.396 ns
                BenchAddNewMemmovebad25664133.685 ns0.2328 ns0.2177 ns133.636 ns
                BenchAddOldbad256128142.283 ns0.0672 ns0.0595 ns142.284 ns
                BenchAddNewbad256128145.236 ns0.2122 ns0.1985 ns145.283 ns
                BenchAddNewMemmovebad256128145.719 ns0.2226 ns0.2082 ns145.670 ns
                BenchAddOldbad256256165.340 ns0.3852 ns0.3603 ns165.212 ns
                BenchAddNewbad256256165.619 ns0.0412 ns0.0365 ns165.631 ns
                BenchAddNewMemmovebad256256165.315 ns0.0680 ns0.0568 ns165.320 ns
                BenchAddOldgood222.463 ns0.0765 ns0.0880 ns2.472 ns
                BenchAddNewgood222.693 ns0.0574 ns0.0509 ns2.685 ns
                BenchAddNewMemmovegood223.429 ns0.0930 ns0.0870 ns3.445 ns
                BenchAddOldgood423.343 ns0.0698 ns0.0618 ns3.342 ns
                BenchAddNewgood423.247 ns0.0871 ns0.1593 ns3.213 ns
                BenchAddNewMemmovegood425.360 ns0.0828 ns0.0734 ns5.321 ns
                BenchAddOldgood443.344 ns0.0888 ns0.0830 ns3.366 ns
                BenchAddNewgood443.596 ns0.0934 ns0.0873 ns3.618 ns
                BenchAddNewMemmovegood444.481 ns0.0619 ns0.0579 ns4.493 ns
                BenchAddOldgood825.399 ns0.0864 ns0.0809 ns5.401 ns
                BenchAddNewgood824.347 ns0.0988 ns0.0876 ns4.331 ns
                BenchAddNewMemmovegood825.410 ns0.0615 ns0.0545 ns5.429 ns
                BenchAddOldgood845.218 ns0.1080 ns0.1010 ns5.201 ns
                BenchAddNewgood844.667 ns0.0568 ns0.0503 ns4.640 ns
                BenchAddNewMemmovegood846.658 ns0.0953 ns0.0845 ns6.659 ns
                BenchAddOldgood885.094 ns0.0144 ns0.0112 ns5.089 ns
                BenchAddNewgood885.715 ns0.0724 ns0.0677 ns5.700 ns
                BenchAddNewMemmovegood886.075 ns0.0402 ns0.0336 ns6.067 ns
                BenchAddOldgood1628.978 ns0.0386 ns0.0361 ns8.976 ns
                BenchAddNewgood1625.883 ns0.0141 ns0.0125 ns5.883 ns
                BenchAddNewMemmovegood1625.717 ns0.1207 ns0.1129 ns5.753 ns
                BenchAddOldgood1648.796 ns0.0898 ns0.0796 ns8.763 ns
                BenchAddNewgood1646.440 ns0.0853 ns0.0798 ns6.394 ns
                BenchAddNewMemmovegood1646.898 ns0.1508 ns0.1410 ns6.891 ns
                BenchAddOldgood1688.804 ns0.0696 ns0.0543 ns8.797 ns
                BenchAddNewgood1687.118 ns0.0780 ns0.0729 ns7.095 ns
                BenchAddNewMemmovegood1688.060 ns0.0123 ns0.0103 ns8.057 ns
                BenchAddOldgood16168.765 ns0.0132 ns0.0117 ns8.766 ns
                BenchAddNewgood16169.316 ns0.0177 ns0.0148 ns9.311 ns
                BenchAddNewMemmovegood16169.472 ns0.0136 ns0.0120 ns9.476 ns
                BenchAddOldgood32215.976 ns0.0149 ns0.0132 ns15.976 ns
                BenchAddNewgood32210.003 ns0.0572 ns0.0477 ns9.994 ns
                BenchAddNewMemmovegood3226.473 ns0.1306 ns0.1222 ns6.482 ns
                BenchAddOldgood32416.029 ns0.0226 ns0.0211 ns16.021 ns
                BenchAddNewgood32410.277 ns0.0319 ns0.0283 ns10.268 ns
                BenchAddNewMemmovegood3247.188 ns0.0847 ns0.0792 ns7.169 ns
                BenchAddOldgood32816.301 ns0.3195 ns0.3803 ns16.152 ns
                BenchAddNewgood32812.358 ns0.2750 ns0.4670 ns12.136 ns
                BenchAddNewMemmovegood3288.976 ns0.0233 ns0.0182 ns8.983 ns
                BenchAddOldgood321616.970 ns0.0710 ns0.0630 ns16.966 ns
                BenchAddNewgood321613.146 ns0.1049 ns0.0819 ns13.162 ns
                BenchAddNewMemmovegood321612.155 ns0.0423 ns0.0353 ns12.152 ns
                BenchAddOldgood323217.361 ns0.0498 ns0.0442 ns17.359 ns
                BenchAddNewgood323217.444 ns0.0156 ns0.0145 ns17.443 ns
                BenchAddNewMemmovegood323217.859 ns0.0275 ns0.0229 ns17.859 ns
                BenchAddOldgood64230.810 ns0.0510 ns0.0452 ns30.802 ns
                BenchAddNewgood64217.049 ns0.0184 ns0.0164 ns17.042 ns
                BenchAddNewMemmovegood6427.111 ns0.1680 ns0.1571 ns7.145 ns
                BenchAddOldgood64430.790 ns0.0456 ns0.0404 ns30.803 ns
                BenchAddNewgood64417.728 ns0.0312 ns0.0276 ns17.732 ns
                BenchAddNewMemmovegood6448.134 ns0.0892 ns0.0834 ns8.144 ns
                BenchAddOldgood64830.834 ns0.0719 ns0.0673 ns30.846 ns
                BenchAddNewgood64819.482 ns0.0262 ns0.0205 ns19.482 ns
                BenchAddNewMemmovegood6489.674 ns0.0432 ns0.0361 ns9.662 ns
                BenchAddOldgood641631.804 ns0.0847 ns0.0792 ns31.775 ns
                BenchAddNewgood641620.717 ns0.0970 ns0.0907 ns20.698 ns
                BenchAddNewMemmovegood641612.849 ns0.0284 ns0.0222 ns12.846 ns
                BenchAddOldgood643234.382 ns0.1602 ns0.1498 ns34.429 ns
                BenchAddNewgood643227.406 ns0.0531 ns0.0443 ns27.407 ns
                BenchAddNewMemmovegood643220.663 ns0.0403 ns0.0377 ns20.662 ns
                BenchAddOldgood646437.511 ns0.0232 ns0.0217 ns37.512 ns
                BenchAddNewgood646437.314 ns0.0940 ns0.0879 ns37.339 ns
                BenchAddNewMemmovegood646438.143 ns0.0108 ns0.0090 ns38.143 ns
                BenchAddOldgood128264.610 ns0.0514 ns0.0480 ns64.607 ns
                BenchAddNewgood128235.626 ns0.0914 ns0.0810 ns35.602 ns
                BenchAddNewMemmovegood12828.729 ns0.1174 ns0.1041 ns8.726 ns
                BenchAddOldgood128464.728 ns0.1634 ns0.1448 ns64.763 ns
                BenchAddNewgood128436.102 ns0.0315 ns0.0263 ns36.106 ns
                BenchAddNewMemmovegood12849.951 ns0.0793 ns0.0662 ns9.953 ns
                BenchAddOldgood128859.980 ns0.0317 ns0.0297 ns59.987 ns
                BenchAddNewgood128833.452 ns0.1252 ns0.1171 ns33.407 ns
                BenchAddNewMemmovegood128810.903 ns0.1075 ns0.0953 ns10.847 ns
                BenchAddOldgood1281660.148 ns0.0566 ns0.0473 ns60.139 ns
                BenchAddNewgood1281636.152 ns0.0371 ns0.0329 ns36.146 ns
                BenchAddNewMemmovegood1281614.780 ns0.0253 ns0.0197 ns14.776 ns
                BenchAddOldgood1283262.632 ns0.1169 ns0.1037 ns62.623 ns
                BenchAddNewgood1283241.202 ns0.1629 ns0.1444 ns41.133 ns
                BenchAddNewMemmovegood1283222.425 ns0.0440 ns0.0368 ns22.414 ns
                BenchAddOldgood1286469.786 ns0.2435 ns0.2278 ns69.657 ns
                BenchAddNewgood1286453.662 ns0.0841 ns0.0787 ns53.662 ns
                BenchAddNewMemmovegood1286441.252 ns0.0296 ns0.0262 ns41.247 ns
                BenchAddOldgood12812881.869 ns0.0553 ns0.0462 ns81.866 ns
                BenchAddNewgood12812882.525 ns0.0267 ns0.0250 ns82.525 ns
                BenchAddNewMemmovegood12812882.764 ns0.0255 ns0.0226 ns82.761 ns
                BenchAddOldgood2562122.771 ns0.1330 ns0.1179 ns122.739 ns
                BenchAddNewgood256265.197 ns0.1967 ns0.1840 ns65.169 ns
                BenchAddNewMemmovegood256212.797 ns0.1026 ns0.0960 ns12.776 ns
                BenchAddOldgood2564122.580 ns0.1142 ns0.0891 ns122.547 ns
                BenchAddNewgood256467.334 ns1.3588 ns1.9487 ns66.053 ns
                BenchAddNewMemmovegood256412.118 ns0.1328 ns0.1243 ns12.057 ns
                BenchAddOldgood2568122.775 ns0.0513 ns0.0455 ns122.769 ns
                BenchAddNewgood256867.161 ns0.1645 ns0.1538 ns67.102 ns
                BenchAddNewMemmovegood256815.188 ns0.1121 ns0.1049 ns15.137 ns
                BenchAddOldgood25616123.086 ns0.2531 ns0.2244 ns123.022 ns
                BenchAddNewgood2561669.501 ns0.0621 ns0.0551 ns69.495 ns
                BenchAddNewMemmovegood2561618.547 ns0.0834 ns0.0780 ns18.528 ns
                BenchAddOldgood25632124.622 ns0.1848 ns0.1729 ns124.577 ns
                BenchAddNewgood2563274.259 ns0.0878 ns0.0778 ns74.234 ns
                BenchAddNewMemmovegood2563226.275 ns0.1607 ns0.1425 ns26.231 ns
                BenchAddOldgood25664129.737 ns0.1050 ns0.0931 ns129.750 ns
                BenchAddNewgood2566486.767 ns0.1327 ns0.1242 ns86.743 ns
                BenchAddNewMemmovegood2566444.936 ns0.1495 ns0.1249 ns44.929 ns
                BenchAddOldgood256128144.031 ns0.1669 ns0.1561 ns144.024 ns
                BenchAddNewgood256128115.261 ns0.0782 ns0.0693 ns115.239 ns
                BenchAddNewMemmovegood25612889.670 ns0.2114 ns0.1977 ns89.623 ns
                BenchAddOldgood256256165.619 ns0.1193 ns0.1057 ns165.604 ns
                BenchAddNewgood256256165.271 ns0.2068 ns0.1934 ns165.296 ns
                BenchAddNewMemmovegood256256165.865 ns0.4035 ns0.3774 ns165.766 ns

                Metadata

                Metadata

                Assignees

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions