Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Span: Add BinarySearch(...) extension methods for ReadOnlySpan<T> (and Span<T>) #25777

Merged
merged 32 commits into from
Dec 13, 2017

Conversation

nietras
Copy link

@nietras nietras commented Dec 7, 2017

Implements https://github.com/dotnet/corefx/issues/15818

This is currently an early work-in-progress with the current status:

  • Basic binary search implemented based on coreclr implementation.
  • Basic tests
  • Can we remove the try/catch in the implementation? Why is it there? REMOVED, per review comment.
  • Tests for all overloads.
  • Add overflow test (Q: Not in OuterLoop currently, since pretty fast, should it be still?)
  • Compute median in uint with single add and shift
  • Revise API to use in on e.g. TComparer, TComparable. Commented on initial proposal to change this.
  • Undo use of in since it will always pass by ref which will not be good for small types.
  • Fix xml comments, currently quick-copy-paste (Q: Where are the XML docs for Array.BinarySearch? couldn't find them, copy from VS F12)
  • Tests, tests, tests. (Q: coreclr has a lot of "weird" tests, guidance on tests needed here would be good? Not using these... seem unnecessary)
  • SKIPPED Performance tests. (Q: again coreclr has a lot of "weird" tests, guidance on tests needed here would be good? What scenarios? Also see code comments.)
  • Investigate assembly and do quick benchmark, see comments.
  • Address review issues.
  • Finalize and clean up.

cc: @karelz a heads up, since I started preliminary work on this. Hopefully after this I can continue with Sort.
cc: @jkotas if you could review and perhaps answer some questions, thank you.

@jkotas jkotas requested a review from ahsonkhan December 7, 2017 15:29
// TODO: We probably need to add `ref readonly`/`in` overloads or `AddReadOnly`to unsafe,
// if this will be available in language
// TODO: Revise all Unsafe APIs for `readonly` applicability...
c = comparable.CompareTo(Unsafe.Add(ref s, i));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really need to use unsafe code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkotas could use indexer, but this way the code is more general (only needs a ref) and bounds checking won't be an issue. Perhaps the jit can elide the bounds checks but not sure if it could given the pattern here.

}
catch (Exception e)
{
// TODO: Is this correct? Don't like the try/catch, why should we have this??
Copy link
Member

@jkotas jkotas Dec 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think that this try/catch is needed.

IIRC, tt was in Sort as well and it was removed there. I think it can be removed in BinarySearch as well (even in the array-based BinarySearch).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would be great, it would simplify the PR greatly, I'll remove it.

@janvorli
Copy link
Member

janvorli commented Dec 7, 2017

@mmitche my new Alpine 3.6 CI leg has failed here with error:
ERROR: /jenkins/workspace/dotnet_corefx/master/alpine-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_false_prtest@script/buildpipeline/alpine.3.6.groovy not found
I am not sure what's going on since the file was part of my PR. Any idea?

@mmitche
Copy link
Member

mmitche commented Dec 7, 2017

@janvorli This branch has conflicts so it's building the unmerged code rather than the merged code.

@nietras
Copy link
Author

nietras commented Dec 7, 2017

@janvorli @mmitche merged master.


[Theory, MemberData(nameof(UIntCases))]
public static void BinarySearch_UInt(
(uint[] Array, uint Value, int ExpectedIndex) c)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rename c to something more descriptive, maybe testData

@@ -9,6 +9,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="AllocationHelper.cs" />
<Compile Include="ReadOnlySpan\BinarySearch.cs" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can you please move this into the ReadOnlySpan test property group?

Also consider splitting the tests into separate files for Span and ReadOnlySpan tests, but up to you.

@nietras
Copy link
Author

nietras commented Dec 9, 2017

@ahsonkhan thanks for the great feedback. I think I have addressed everything except splitting unit tests into two files since I think this will lead to more code duplication, if you have a way that doesn't let me know. Also did not change value tuple property names, since not sure correct, and not specifically addressed in the coding style (PS: Consider adding to code style.).

@nietras nietras changed the title [WIP] Span: Add BinarySearch(...) extension methods for ReadOnlySpan<T> (and Span<T>) Span: Add BinarySearch(...) extension methods for ReadOnlySpan<T> (and Span<T>) Dec 9, 2017
{
using (iteration.StartMeasurement())
{
index |= span.BinarySearch(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You aren't using InnerIterationCount here (or elsewhere). If it isn't needed (i.e. your test results are already > 1 ms), feel free to remove it. Otherwise, I think you need to add a loop within the measured block, using the InnerIterationCount.
See the following for an example - https://github.com/Microsoft/xunit-performance#authoring-benchmarks

As an aside, can you please share the results of the perf tests? I am curious to see them.

Copy link
Author

@nietras nietras Dec 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahsonkhan fixed to use InnerIterationCount, however, testResults.xml does not contain any results for these, maybe I am not executing it correctly:

<?xml version="1.0" encoding="utf-8"?>
<assemblies>
  <assembly name="System.Memory.Performance.Tests.dll" environment="64-bit .NET (unknown version) [collection-per-class, parallel (4 threads)]" test-framework="xUnit.net 2.2.0.3300" run-date="2017-12-11" run-time="21:08:22" total="2" passed="2" failed="0" skipped="0" time="0.221" errors="0">
    <errors />
    <collection total="2" passed="2" failed="0" skipped="0" name="Test collection for System.Buffers.Text.Tests.Base64TestHelper" time="0.014">
      <test name="System.Buffers.Text.Tests.Base64TestHelper.GenerateEncodingMapAndVerify" type="System.Buffers.Text.Tests.Base64TestHelper" method="GenerateEncodingMapAndVerify" time="0.0112869" result="Pass" />
      <test name="System.Buffers.Text.Tests.Base64TestHelper.GenerateDecodingMapAndVerify" type="System.Buffers.Text.Tests.Base64TestHelper" method="GenerateDecodingMapAndVerify" time="0.0028464" result="Pass" />
    </collection>
  </assembly>
</assemblies>

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/dotnet/corefx/blob/master/Documentation/project-docs/performance-tests.md does not seem to be correct, at least running:

msbuild /t:BuildAndTest /p:Performance=true /p:ConfigurationGroup=Release /p:TargetOS=Windows_NT

does not work. What am I doing wrong? 🙄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The performance results should be somewhere in this directory: "corefx\bin\tests\System.Memory.Performance.Tests\netcoreapp-Windows_NT-Release-x64", not in testResults.xml

Go to "\corefx\src\System.Memory\tests\Performance" and run the command you listed above:
msbuild /t:BuildAndTest /p:Performance=true /p:ConfigurationGroup=Release /p:TargetOS=Windows_NT

Are you getting any errors when you do this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahsonkhan no I do not get any errors when running the msbuild command, but no tests are run.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahsonkhan didn't notice this at first but the msbuild run says:

Skipping target "RunTestsForProject" because all output files are up-to-date with respect to the input files.

I thought that weird, and since the testResults.xml file was incorrect, only had two tests, that actually do not seem to be proper perf tests, I deleted the file and ran the msbuild command again. Then the tests are run and I noted that:

[13-12-2017 10:24:05][WRN] Skipping 2 Xunit [Fact]s because they are not [Benchmark]s

Therefore I think there are two tests that are wrongly marked as Benchmark or something which mess the whole process up.

There is an issue with the string perf tests, so results will have to come later... I think I made a mistake when doing the int to string stuff:

[13-12-2017 10:27:55][ERR] System.Memory.Tests.Perf_Span_BinarySearch.SpanBinarySearch_String_NotFoundBefore(size: 100): Assert.Equal() Failure
  Expected: -1
  Actual:   -3
  [13-12-2017 10:27:55][ERR]    at Xunit.Assert.Equal[T](T expected, T actual, IEqualityComparer`1 comparer) in C:\BuildAgent\work\cb37e9acf085d108\src\xunit.assert\Asserts\EqualityAsserts.cs:line 35
     at System.Memory.Tests.Perf_Span_BinarySearch.BenchmarkAndAssert(Int32 size, String value, Int32 expectedIndex) in D:\oss\corefx\src\System.Memory\tests\Performance\Perf.Span.BinarySearch.cs:line 157
     at System.Memory.Tests.Perf_Span_BinarySearch.SpanBinarySearch_String_NotFoundBefore(Int32 size) in D:\oss\corefx\src\System.Memory\tests\Performance\Perf.Span.BinarySearch.cs:line 103

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followup, weird the actual perf test run does not produce a testResults.xml file at the end, maybe an intermediate? Anyway, seems I can run the perf tests after fixing that.

Regarding the possible wrong tests, these are defined as:

        [Fact]
        public static void GenerateEncodingMapAndVerify()
        {
            var data = new byte[64]; // Base64
            for (int i = 0; i < s_characters.Length; i++)
            {
                data[i] = (byte)s_characters[i];
            }
            Assert.True(s_encodingMap.AsSpan().SequenceEqual(data));
        }

        [Fact]
        public static void GenerateDecodingMapAndVerify()
        {
            var data = new sbyte[256]; // 0 to byte.MaxValue (255)
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = s_invalidByte;
            }
            for (int i = 0; i < s_characters.Length; i++)
            {
                data[s_characters[i]] = (sbyte)i;
            }
            Assert.True(s_decodingMap.AsSpan().SequenceEqual(data));
        }

these are the ones populating the testResults.xml. These are static methods on a static class, perhaps there is an issue around that here.

@nietras
Copy link
Author

nietras commented Dec 13, 2017

After fixing the string perf test these are the results I am getting. This shows that I made a mistake there with how I defined the MiddleIndex test, because clearly it does not find it on first compare... forgot to -1 🙄

Test Name Metric Iterations AVERAGE STDEV.S MIN MAX
SpanBinarySearch_Int_FirstIndex(size: 1) Duration 100 0.353 0.014 0.334 0.385
SpanBinarySearch_Int_FirstIndex(size: 10) Duration 100 0.706 0.022 0.668 0.749
SpanBinarySearch_Int_FirstIndex(size: 100) Duration 100 1.143 0.044 1.054 1.253
SpanBinarySearch_Int_FirstIndex(size: 1000) Duration 100 1.545 0.072 1.344 1.681
SpanBinarySearch_Int_LastIndex(size: 1) Duration 100 0.364 0.011 0.352 0.403
SpanBinarySearch_Int_LastIndex(size: 10) Duration 100 1.106 0.047 1.039 1.414
SpanBinarySearch_Int_LastIndex(size: 100) Duration 100 1.680 0.037 1.596 1.749
SpanBinarySearch_Int_LastIndex(size: 1000) Duration 100 2.393 0.060 2.255 2.621
SpanBinarySearch_Int_MiddleIndex(size: 1) Duration 100 0.360 0.007 0.353 0.388
SpanBinarySearch_Int_MiddleIndex(size: 10) Duration 100 0.896 0.019 0.768 0.923
SpanBinarySearch_Int_MiddleIndex(size: 100) Duration 100 1.238 0.032 1.169 1.293
SpanBinarySearch_Int_MiddleIndex(size: 1000) Duration 100 1.717 0.041 1.624 1.795
SpanBinarySearch_Int_NotFoundAfter(size: 1) Duration 100 0.360 0.007 0.353 0.377
SpanBinarySearch_Int_NotFoundAfter(size: 10) Duration 100 0.954 0.041 0.865 1.039
SpanBinarySearch_Int_NotFoundAfter(size: 100) Duration 100 1.504 0.060 1.393 1.831
SpanBinarySearch_Int_NotFoundAfter(size: 1000) Duration 100 2.055 0.057 1.894 2.174
SpanBinarySearch_Int_NotFoundBefore(size: 1) Duration 100 0.305 0.016 0.294 0.402
SpanBinarySearch_Int_NotFoundBefore(size: 10) Duration 100 0.595 0.016 0.557 0.644
SpanBinarySearch_Int_NotFoundBefore(size: 100) Duration 100 0.942 0.027 0.891 1.005
SpanBinarySearch_Int_NotFoundBefore(size: 1000) Duration 100 1.279 0.034 1.226 1.353
SpanBinarySearch_String_FirstIndex(size: 1) Duration 100 22.322 0.268 21.813 23.783
SpanBinarySearch_String_FirstIndex(size: 10) Duration 100 65.349 0.622 63.778 67.350
SpanBinarySearch_String_FirstIndex(size: 100) Duration 80 125.622 0.660 124.502 127.369
SpanBinarySearch_String_FirstIndex(size: 1000) Duration 55 182.223 1.828 180.263 192.331
SpanBinarySearch_String_LastIndex(size: 1) Duration 100 22.402 0.217 21.983 23.208
SpanBinarySearch_String_LastIndex(size: 10) Duration 100 86.999 0.429 85.929 88.306
SpanBinarySearch_String_LastIndex(size: 100) Duration 68 147.798 2.714 146.150 164.688
SpanBinarySearch_String_LastIndex(size: 1000) Duration 50 202.906 1.240 201.222 206.832
SpanBinarySearch_String_MiddleIndex(size: 1) Duration 100 22.135 0.225 21.697 23.223
SpanBinarySearch_String_MiddleIndex(size: 10) Duration 100 66.433 0.477 65.516 67.695
SpanBinarySearch_String_MiddleIndex(size: 100) Duration 79 128.011 1.164 126.733 135.441
SpanBinarySearch_String_MiddleIndex(size: 1000) Duration 55 183.528 1.137 181.470 188.344
SpanBinarySearch_String_NotFoundAfter(size: 1) Duration 100 22.923 0.168 22.590 23.301
SpanBinarySearch_String_NotFoundAfter(size: 10) Duration 100 74.666 0.638 73.493 77.112
SpanBinarySearch_String_NotFoundAfter(size: 100) Duration 82 122.753 5.963 119.323 171.207
SpanBinarySearch_String_NotFoundAfter(size: 1000) Duration 63 161.095 1.377 159.472 165.815
SpanBinarySearch_String_NotFoundBefore(size: 1) Duration 100 10.192 0.151 9.910 10.551
SpanBinarySearch_String_NotFoundBefore(size: 10) Duration 100 30.357 0.534 29.410 34.040
SpanBinarySearch_String_NotFoundBefore(size: 100) Duration 100 58.668 0.512 57.352 59.693
SpanBinarySearch_String_NotFoundBefore(size: 1000) Duration 100 89.428 0.476 88.475 91.640

so I fixed that and reran the perf tests yielding:

Test Name Metric Iterations AVERAGE STDEV.S MIN MAX
SpanBinarySearch_Int_FirstIndex(size: 1) Duration 100 0.414 0.012 0.385 0.442
SpanBinarySearch_Int_FirstIndex(size: 10) Duration 100 0.754 0.025 0.696 0.809
SpanBinarySearch_Int_FirstIndex(size: 100) Duration 100 1.157 0.039 1.068 1.235
SpanBinarySearch_Int_FirstIndex(size: 1000) Duration 100 1.588 0.067 1.454 1.729
SpanBinarySearch_Int_LastIndex(size: 1) Duration 100 0.416 0.012 0.385 0.448
SpanBinarySearch_Int_LastIndex(size: 10) Duration 100 1.143 0.027 1.091 1.216
SpanBinarySearch_Int_LastIndex(size: 100) Duration 100 1.731 0.041 1.629 1.806
SpanBinarySearch_Int_LastIndex(size: 1000) Duration 100 2.409 0.068 2.185 2.546
SpanBinarySearch_Int_MiddleIndex(size: 1) Duration 100 0.405 0.016 0.362 0.443
SpanBinarySearch_Int_MiddleIndex(size: 10) Duration 100 0.416 0.011 0.385 0.446
SpanBinarySearch_Int_MiddleIndex(size: 100) Duration 100 0.421 0.018 0.384 0.522
SpanBinarySearch_Int_MiddleIndex(size: 1000) Duration 100 0.417 0.011 0.390 0.438
SpanBinarySearch_Int_NotFoundAfter(size: 1) Duration 100 0.391 0.020 0.362 0.457
SpanBinarySearch_Int_NotFoundAfter(size: 10) Duration 100 1.023 0.034 0.893 1.094
SpanBinarySearch_Int_NotFoundAfter(size: 100) Duration 100 1.555 0.061 1.409 1.773
SpanBinarySearch_Int_NotFoundAfter(size: 1000) Duration 100 2.134 0.067 1.996 2.386
SpanBinarySearch_Int_NotFoundBefore(size: 1) Duration 100 0.353 0.012 0.325 0.385
SpanBinarySearch_Int_NotFoundBefore(size: 10) Duration 100 0.639 0.021 0.592 0.694
SpanBinarySearch_Int_NotFoundBefore(size: 100) Duration 100 0.988 0.030 0.937 1.076
SpanBinarySearch_Int_NotFoundBefore(size: 1000) Duration 100 1.374 0.043 1.279 1.484
SpanBinarySearch_String_FirstIndex(size: 1) Duration 100 22.365 0.273 21.779 23.053
SpanBinarySearch_String_FirstIndex(size: 10) Duration 100 65.433 0.466 64.493 66.747
SpanBinarySearch_String_FirstIndex(size: 100) Duration 81 124.718 0.644 123.416 127.479
SpanBinarySearch_String_FirstIndex(size: 1000) Duration 56 180.603 0.987 177.493 182.683
SpanBinarySearch_String_LastIndex(size: 1) Duration 100 22.291 0.257 21.808 23.027
SpanBinarySearch_String_LastIndex(size: 10) Duration 100 86.551 0.364 85.697 87.442
SpanBinarySearch_String_LastIndex(size: 100) Duration 69 146.942 0.674 145.611 150.091
SpanBinarySearch_String_LastIndex(size: 1000) Duration 50 203.106 1.264 201.354 208.601
SpanBinarySearch_String_MiddleIndex(size: 1) Duration 100 22.169 0.220 21.781 22.998
SpanBinarySearch_String_MiddleIndex(size: 10) Duration 100 22.232 0.245 21.772 23.038
SpanBinarySearch_String_MiddleIndex(size: 100) Duration 100 21.589 0.187 21.197 22.194
SpanBinarySearch_String_MiddleIndex(size: 1000) Duration 100 22.070 0.171 21.686 22.458
SpanBinarySearch_String_NotFoundAfter(size: 1) Duration 100 22.386 0.229 21.997 23.104
SpanBinarySearch_String_NotFoundAfter(size: 10) Duration 100 74.667 0.433 73.646 76.292
SpanBinarySearch_String_NotFoundAfter(size: 100) Duration 84 120.100 0.546 119.008 121.641
SpanBinarySearch_String_NotFoundAfter(size: 1000) Duration 64 158.425 0.749 156.745 160.805
SpanBinarySearch_String_NotFoundBefore(size: 1) Duration 100 10.619 0.184 10.345 11.720
SpanBinarySearch_String_NotFoundBefore(size: 10) Duration 100 30.594 0.291 30.111 31.924
SpanBinarySearch_String_NotFoundBefore(size: 100) Duration 100 59.180 0.399 58.392 60.858
SpanBinarySearch_String_NotFoundBefore(size: 1000) Duration 100 88.428 0.434 87.537 89.793

@nietras
Copy link
Author

nietras commented Dec 13, 2017

@ahsonkhan except for the value tuple PascalCase or camelCase issue, I think this is ready now.

@nietras
Copy link
Author

nietras commented Dec 13, 2017

Ok from what I gather the issue with perf tests is that there have been added to [Fact] test in Base64TestHelper.cs which causes there to be "unit tests" in the "performance tests" project, so when running unit tests it will run these two tests e.g. via ..\..\build.cmd . -release and then subsequently performance tests will not run since it thinks the performance tests have been run.

The question is whether there should be normal unit tests in a performance tests project or if this is an issue with the build tools that should figure out that running unit tests in a performance tests project does not mean performance tests have been run etc. Not sure what. And perhaps a different issue should be filed for this.

@@ -10,6 +10,7 @@ namespace System.Memory.Tests
public class Perf_Span_BinarySearch
{
private const int InnerCount = 100000;
private const string NumberFormat = "D9";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain why this is needed?

@ahsonkhan
Copy link
Member

Ok from what I gather the issue with perf tests is that there have been added to [Fact] test in Base64TestHelper.cs which causes there to be "unit tests" in the "performance tests" project, so when running unit tests it will run these two tests e.g. via ..\..\build.cmd . -release and then subsequently performance tests will not run since it thinks the performance tests have been run.
The question is whether there should be normal unit tests in a performance tests project or if this is an issue with the build tools that should figure out that running unit tests in a performance tests project does not mean performance tests have been run etc. Not sure what. And perhaps a different issue should be filed for this.

I am not exactly sure if that is the issue since I am not able to reproduce the behavior you see yet, but we could consider moving the GenerateEncodingMapAndVerify / GenerateDecodingMapAndVerify tests out of Base64TestHelpers and into Base64DecoderUnitTests.cs / Base64EncoderUnitTests.cs.

Can you please try removing those tests locally and see if it fixed the issue of running performance tests you were seeing to confirm that is the cause?

If it is the issue, I can file an issue to fix it (feel free to do it yourself as well).

@ahsonkhan
Copy link
Member

How come SpanBinarySearch_String_NotFoundBefore is faster than SpanBinarySearch_String_NotFoundAfter?

@ahsonkhan
Copy link
Member

One thing that would be really helpful, if you have time, is to add Array.BinarySearch baseline performance tests so we can get comparison of the performance. Otherwise, looks good to me :)

I am curious, why does the SpanBinarySearch_String_MiddleIndex performs so well? Is it because it only runs one iteration of the loop and the CompareTo already returns 0?

int c = comparable.CompareTo(Unsafe.Add(ref spanStart, i)); // c = 0 right away, and we return i?

{
if (comparable == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparable);
// TODO: Make `ref readonly`/`in` when Unsafe.Add(ReadOnly) supports it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahsonkhan, @nietras , do we have workitems filed for these Unsafe APIs and to fix the TODOs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrzysztofCwalina My proposal to change Unsafe APIs to ref readonly was rejected a while ago. A few things have changed since then (such as the rename to in), so maybe it's time the proposal could be revisited?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably just remove the TODOs since the Unsafe fix was to "cast" away the readonly and then there is no reason as such to change the impl, if DangerousGetPinnableReference() will still return a ref for ReadOnlySpan<T> otherwise the readonly simply has to be cast away. This will show up if any such change comes to ReadOnlySpan<T> so deleting the TODOs should be fine.

@KrzysztofCwalina
Copy link
Member

Looks good! Thanks for the great PR!

@KrzysztofCwalina KrzysztofCwalina merged commit c362f30 into dotnet:master Dec 13, 2017
@nietras
Copy link
Author

nietras commented Dec 14, 2017

GenerateEncodingMapAndVerify / GenerateDecodingMapAndVerify tests out of Base64TestHelpers and into Base64DecoderUnitTests.cs / Base64EncoderUnitTests.cs.
Can you please try removing those tests locally and see if it fixed the issue of running performance tests you were seeing to confirm that is the cause?

To reproduce I run:

D:\oss\corefx\src\System.Memory [span-binarysearch ≡ +0 ~1 -0 !]> ..\..\build.cmd . -release

Which then shows:

 Running tests... Start time: 12:25:39.25
  xUnit.net console test runner (64-bit .NET Core)
  Copyright (C) 2014 Outercurve Foundation.

  Discovering: System.Memory.Performance.Tests
  Discovered:  System.Memory.Performance.Tests
  Starting:    System.Memory.Performance.Tests
  Finished:    System.Memory.Performance.Tests

  === TEST EXECUTION SUMMARY ===
     System.Memory.Performance.Tests  Total: 2, Errors: 0, Failed: 0, Skipped: 0, Time: 0.340s

After that I then run:

D:\oss\corefx\src\System.Memory\tests\Performance [span-binarysearch ≡ +0 ~1 -0 !]> msbuild /t:BuildAndTest /p:Performance=true /p:ConfigurationGroup=Release /p:TargetOS=Windows_NT

which then says it skips the performance tests as previously mentioned.

Removing the two tests aka uncommenting the two [Fact]s AND deleting the previous testResults.xml file when run:

D:\oss\corefx\src\System.Memory [span-binarysearch ≡ +0 ~1 -0 !]> ..\..\build.cmd . -release

yields:

Using D:\oss\corefx\bin\testhost\netcoreapp-Windows_NT-Release-x64\ as the test runtime folder.
  Executing in D:\oss\corefx\bin\tests\System.Memory.Performance.Tests\netcoreapp-Windows_NT-Release-x64\
  Running tests... Start time: 12:42:58.41
  xUnit.net console test runner (64-bit .NET Core)
  Copyright (C) 2014 Outercurve Foundation.

  Discovering: System.Memory.Performance.Tests
  Discovered:  System.Memory.Performance.Tests
  Info:        System.Memory.Performance.Tests has no tests to run
  Finished running tests.  End time=12:42:58.75, Exit code = 0

And a new testResults.xml file:

<?xml version="1.0" encoding="utf-8"?>
<assemblies>
  <assembly>
    <errors />
  </assembly>
</assemblies>

Then running:

D:\oss\corefx\src\System.Memory\tests\Performance [span-binarysearch ≡ +0 ~1 -0 !]> msbuild /t:BuildAndTest /p:Performance=true /p:ConfigurationGroup=Release /p:TargetOS=Windows_NT

and it still skips the performance tests, so you are right that disabling/uncommenting the two [Fact] tests does not help. I can still delete the testResults.xml file and then performance test will run. So why is this created? Why is it an issue?

@nietras
Copy link
Author

nietras commented Dec 14, 2017

Can you please explain why this is needed?

@ahsonkhan the NumberFormat is needed in the perf tests to format all numbers as e.g. "000000999" so an increasing integer sequence generates a sorted set of strings. Why "D9"? I wanted something longer to have something with a higher compare cost. As the tests show that is clearly the case.

How come SpanBinarySearch_String_NotFoundBefore is faster than SpanBinarySearch_String_NotFoundAfter?

String_NotFoundBefore uses a single character string "/" which is just before "0" and thus before "00000000" as test, thus the compare is much faster than the after, which would be say "000001001". I thought this could show difference between a fast and slower compare when searching at the "ends".

add Array.BinarySearch baseline performance tests so we can get comparison of the performance

I will try to see if I can add this in a separate PR, perhaps with a few comments for some of the questions raised.

why does the SpanBinarySearch_String_MiddleIndex performs so well? Is it because it only runs one iteration of the loop and the CompareTo already returns 0?

Yes, the MiddleIndex is then first index checked in the binary search so it returns on first compare no matter what. It is the same for the int perf tests.

Looks good! Thanks for the great PR!

Thanks I will see if I can make a PR for some of the remaining small nits. Otherwise, next up is Sort for span.

@nietras nietras deleted the span-binarysearch branch December 19, 2017 11:36
@karelz karelz added this to the 2.1.0 milestone Dec 28, 2017
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Span: Add BinarySearch(...) extension methods for ReadOnlySpan<T> (and Span<T>)

Commit migrated from dotnet/corefx@c362f30
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants