Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

add some basic parallelism to improve performance #799

Merged
merged 1 commit into from
Dec 1, 2023

Conversation

stephen-hawley
Copy link
Contributor

First cut on making aspects of BTfS parallelizable.

ErrorHandling and the *Inventory stack needed to be made thread-safe.

I made reflection and demangling async and changed how they're being invoked to run them in parallel.
Across the entire suit of unit tests, this improves performance by about 7%.

I'm fairly certain that I can make the C# code generation parallel as well - but I'll do that in a separate PR.

Copy link
Member

@mandel-macaque mandel-macaque left a comment

Choose a reason for hiding this comment

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

I won't block the code, but there are a few areas in which we could use the BCL concurrent classes to make things simpler to write.

We are also using Threads for IO bound work, while indeed will make the code async, that is not the proper ideal since we are using CPU threads for stuff that the IO can deal with better.

Doing some extra work and making the IO async, while is A LOT more effort, will probably have a much better payoff in the long run.

Maybe investing on the lower level classes to be truly async might be a good investment for the future.

@@ -11,6 +11,7 @@

namespace SwiftReflector {
public class ErrorHandling {
object messagesLock = new object ();
Copy link
Member

@mandel-macaque mandel-macaque Nov 29, 2023

Choose a reason for hiding this comment

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

I believe you can make your life easier by using https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentbag-1?view=net-8.0

You will need to make a decision, but looking at your code you can move from a List to a Bag since:

  1. You are not using an indexer.
  2. Looks like you do not care about order.
  3. You access the collections via Linq and the ConcurrentBag does provide the required interfaces to continue doing it.
  4. Provides a nice thread-safe property "IsEmpty" which you can use in some of the properties.

There is a few draw backs, one is that you won't have an AddRange method: dotnet/runtime#61291

The second one I can't think, by definition all LINQ operations are thread-safe because they only read, the diff is that the concurrent bag provides a snapshot of the collection while iterating, that will change the behavior and probably, if that must be kept, is best to use a lock.

You'll have to probably write that the class must be thread-safe for the future team.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

opened an issue for it #801

if (!values.TryGetValue (className, out contents)) {
contents = new ClassContents (formalName, sizeofMachinePointer);
values.Add (className, contents);
lock (valuesLock) {
Copy link
Member

@mandel-macaque mandel-macaque Nov 29, 2023

Choose a reason for hiding this comment

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

Same as with the ErrorHandler, we could use https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-8.0

Looking at the code on which you are locking, you could remove the need to the TryGetValue, use TryAdd which will add only if not present. If you were to update, TryUpdate would be the method you want.

I don't know the code base, but you might be in a situation in which some Invetories are thread-safe and others are not.

In this case, I cannot think of a drawback looking at your code AND will ensure that all subclasses are threadsafe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

opened an issue for it. #800

Comment on lines +25 to 32
lock (valuesLock) {
OverloadInventory overloads = null;
if (!values.TryGetValue (tlf.Name, out overloads)) {
overloads = new OverloadInventory (tlf.Name, sizeofMachinePointer);
values.Add (tlf.Name, overloads);
}
overloads.Add (tlf, srcStm);
}
Copy link
Member

Choose a reason for hiding this comment

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

Same, which also means that changing the dictionary in the base type will make all of your subclasses thread-safe.

@@ -8,6 +8,7 @@

namespace SwiftReflector.Inventory {
public abstract class Inventory<T> {
protected object valuesLock = new object ();
protected Dictionary<SwiftName, T> values = new Dictionary<SwiftName, T> ();
Copy link
Member

Choose a reason for hiding this comment

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

A ConcurrentDict will make your life easier.

Comment on lines +28 to +34
lock (valuesLock) {
ModuleContents module = null;
if (!values.TryGetValue (tld.Module, out module)) {
module = new ModuleContents (tld.Module, SizeofMachinePointer);
values.Add (tld.Module, module);
}
module.Add (tld, srcStm);
Copy link
Member

Choose a reason for hiding this comment

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

Same, all this methods are yy most of the time.

Comment on lines +127 to +129
foreach (var stm in streams) {
stm.Dispose ();
}
Copy link
Member

Choose a reason for hiding this comment

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

The Reactive Extensions (https://github.com/dotnet/reactive) provides a nice way to deal with a collection of IDisposables: https://learn.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh229918(v=vs.103)

I don't know how you feel about adding it as a dependency "System.Reactive" but will probably help.

@stephen-hawley stephen-hawley merged commit 4a609eb into main Dec 1, 2023
1 of 2 checks passed
@mandel-macaque mandel-macaque deleted the lets-async-start branch December 4, 2023 16:59
@stephen-hawley stephen-hawley restored the lets-async-start branch December 4, 2023 17:05
@stephen-hawley stephen-hawley deleted the lets-async-start branch December 4, 2023 17:06
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants