Skip to content

Conversation

jnm2
Copy link
Contributor

@jnm2 jnm2 commented Jun 1, 2025

Running ExampleTester on my machine takes 8 minutes. With the changes in this PR, it takes 19 seconds. That's a 25x speedup!
That's not even parallelizing- parallelized together with #1337, the entire suite of example tests runs in 8 seconds.

The goal is twofold:

  • to not have to wait ~9 minutes for the GitHub actions to finish when an update is pushed to a PR
  • to be able to run the full test suite in seconds locally while editing examples, without fiddling with filtering the tester to a given single example.

This PR speeds up all tests except the six or so multi-csproj examples which include project references. The way it speeds up the tests is by reading and interpreting the csproj XML directly. Hopefully, the new code is straightforward enough to extend as needed.

It's important to preserve the fidelity of the testing. Because of this, the fast path will bail if it sees any csproj element that it does not fully understand, falling back to the existing MSBuildWorkspace code which invokes full MSBuild out-of-process. When an example starts using an unrecognized csproj property or item, the tester will quietly fall back to the slow path without logging any notices about this. If you're running the tester with a debugger attached which has opted in to stopping on handled exceptions, you'll hit a handled NotImplementedException that will be the only clue that you're falling back to the slow path.

@jnm2
Copy link
Contributor Author

jnm2 commented Jun 1, 2025

I'd like to add the ExampleTester and ExampleExtractor projects to the filter for the GitHub Actions .yaml that runs ExampleExtractor and ExampleTester, so that we can see how the run is affected by code changes and not just by changes under the standard folder.

(Update: this is handled in #1336)

@jnm2 jnm2 marked this pull request as draft June 1, 2025 19:31
@jnm2
Copy link
Contributor Author

jnm2 commented Jun 1, 2025

Also, I need to investigate a failing example. The reason I didn't investigate this example before pushing is that this example also fails for me on my local machine with the latest commit on draft-v8, but had not been failing in GitHub actions on the same commit. I had put this down to the version of MSBuild being located, but it's clearly coming up again now, so it's time to investigate.

❌Example tester-ExampleTester::file=tools/conversions.md:771-809,line=771:: Mismatched errors: Expected CS1593, CS1661, CS1678, CS8030, CS1688, CS1661, CS1676, CS1643, CS0126, CS0029, CS1662, CS1670, CS0029, CS1662; Was CS1593, CS1661, CS1678, CS8030, CS1688, CS1676, CS1643, CS0126, CS0029, CS1662, CS1670, CS0029, CS1662

(Update: this is handled in #1336)

@Nigel-Ecma
Copy link
Contributor

Also, I need to investigate a failing example. The reason I didn't investigate this example before pushing is that this example also fails for me on my local machine with the latest commit on draft-v8, but had not been failing in GitHub actions on the same commit. I had put this down to the version of MSBuild being located, but it's clearly coming up again now, so it's time to investigate.

❌Example tester-ExampleTester::file=tools/conversions.md:771-809,line=771:: Mismatched errors: Expected CS1593, CS1661, CS1678, CS8030, CS1688, CS1661, CS1676, CS1643, CS0126, CS0029, CS1662, CS1670, CS0029, CS1662; Was CS1593, CS1661, CS1678, CS8030, CS1688, CS1676, CS1643, CS0126, CS0029, CS1662, CS1670, CS0029, CS1662

CS1661 is listed twice in the Expected list, otherwise the lists are the same – cause of problem?

@jnm2
Copy link
Contributor Author

jnm2 commented Jun 2, 2025

@Nigel-Ecma Interesting, thank you. Sounds likely to be a change in compiler behavior. Investigating at #1336 (comment), since I realized that is the PR that will first bring on the change.

(Update: this is handled in #1336)

@jnm2 jnm2 marked this pull request as ready for review June 2, 2025 01:52
@jskeet
Copy link
Contributor

jskeet commented Jun 4, 2025

Can I check, is the intention that this replaces #1337? I'd personally rather avoid parallelization unless we actually need it.

@jnm2
Copy link
Contributor Author

jnm2 commented Jun 4, 2025

I would want to keep #1337 since it allows working with each example as a unit test in addition to running via the CLI. Then, parallelization across the unit tests could be disabled if we didn't want it to be possible. What's the nature of your concern about parallelizing?

@jskeet
Copy link
Contributor

jskeet commented Jun 4, 2025

What's the nature of your concern about parallelizing?

Only that my experience is it can make life more complicated (and harder to diagnose) for very little benefit.

I'll try to review both PRs in the next couple of days.

@jnm2
Copy link
Contributor Author

jnm2 commented Jun 4, 2025

I'm happy to make life easier by removing anything worrisome. 👍

Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

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

I'm good with this as well. Tagging @jskeet if we can review and merge offline.

@BillWagner BillWagner requested a review from jskeet July 29, 2025 20:03
@BillWagner BillWagner closed this Aug 8, 2025
@BillWagner BillWagner reopened this Aug 8, 2025
@jnm2
Copy link
Contributor Author

jnm2 commented Sep 4, 2025

@jskeet Would you be willing to take a look?

Copy link
Contributor

@jskeet jskeet left a comment

Choose a reason for hiding this comment

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

Looks good. Obviously it means slightly higher maintenance costs, but being so much faster sounds like it's worth it...

csproj.CompilationOptions);
}

private static readonly FrozenDictionary<string, OutputKind> SupportedOutputKinds = new KeyValuePair<string, OutputKind>[]
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason for using FrozenDictionary instead of ImmutableDictionary? Mostly out of curiosity, really. (I haven't used the frozen collections, but have used immutable collections a fair amount. From what I've just read, I suspect that most of the time I possibly should be using frozen collections...)

Copy link
Contributor Author

@jnm2 jnm2 Sep 5, 2025

Choose a reason for hiding this comment

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

Stephen Toub assures me that the performance of ImmutableDictionary is irredeemably bad 😄 My understanding is that FrozenDictionary does optimization passes to lay out the memory for super fast lookups, whereas ImmutableDictionary retains an internal structure that compromises with a need for future copy-on-update reusing substructures.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fascinating, thanks. Over the weekend I shall try converting everything in election209.uk from immutable to frozen. Is there any reason not to just use frozen collections everywhere, in a wholesale shift from immutable, if they're "create once and then only read" (rather than using the pseudo-mutators)?

Copy link
Contributor

Choose a reason for hiding this comment

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

(Oh, I see it's only dictionary and set - so I don't need to move away from ImmutableList...)

Copy link
Contributor Author

@jnm2 jnm2 Sep 5, 2025

Choose a reason for hiding this comment

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

I don't believe these is a reason not to use frozen collections in those never-update, read-many-times scenarios. I still use ImmutableDictionary plenty in places where the extra speed just doesn't matter, just for consistency. Switching over does sound potentially helpful for a web app.
ImmutableList's internal structure doesn't permit random access, yet the class has a indexer which thus performs pretty slowly if you have a loop with repeated indexer usage. Unless there will be frequent updates, I usually stick with ImmutableArray which I've been told is even more favorable than arrays for performance because there's no variance checking needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wonderful, thanks :)

@jnm2 jnm2 merged commit 16870d3 into draft-v8 Sep 5, 2025
9 checks passed
@jnm2 jnm2 deleted the jnm2/tools3 branch September 5, 2025 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants