2

I am new to ILNumerics and am investigating using it to replace a .Net assembly we created using the MATLAB compiler SDK.

I wrote a small sample app that uses some of the MATLAB code converted to ILNumerics. The process was relatively simple, and I was able to get up and running pretty quickly.

The app I wrote is a very simple WinForms app (still my goto when I want something quick and dirty - don't judge). It loads a data file and performs a couple of different optimizations - one simple and the other fairly complex. The optimizations run and the results are close to the results I get with the same optimization in MATLAB. The optimization is a bit slower, but I blame that on there not being an implementation of constrained least squares in ILNumerics.

When I run the more complicated optimization directly from the windows thread, my memory usage skyrockets pretty quickly. I know this type of thing is really bad form, but this is just a test application. Still, I wanted to make sure that the problem was caused by running in the UI thread, and not something else in ILNumerics. So I wrote an async version of the function that I was calling from the UI thread, and started calling that instead. This fixed the memory explosion, but it has caused me another problem. After hitting this async function a few times (with a button press on the UI), calls to any ILNumerics functions started to throw exceptions related to heap corruption or writing to protected memory. I won't include all of the code, but here is a snippet showing the difference between my async call and the standard call:

      public static Task<CharacterizationData> CharacterizeAsync(this Component comp, Substrate charSubstrate, double assortmentViscosity)
      {
         return Task.Run(() =>
         {
            return comp.Characterize(charSubstrate, assortmentViscosity);
         });
      }

      public static CharacterizationData Characterize(this Component comp, Substrate charSubstrate, double assortmentViscosity)
      {
         if (comp is null)
         {
            throw new ArgumentNullException(nameof(comp));
         }

         // Capture all reflectances in a single array.
         Array<double> OW = comp.OW;
         Array<double> OB = comp.OB;

         var hasOB = OB.Length > 0;
         Array<double> refSamples = OW;

All of the ILNumerics code is embedded in the synchronous call, so I can't see how calling the async version would cause any cross-thread referencing issues that would lead to heap corruption. The Component and Substrate classes use System.Array objects to store the data, which is why you see some of the members being copied to Array objects within the code.

I've read as much as I can find about the rules around creating and manipulating Array objects and I believe that I am following all rules correctly. The fact that the code runs fine within the UI thread leads me to believe that I have not violated any rules, and I just have some missing knowledge about multithreading with ILNumerics.

Does anyone have any insight into what I might be doing wrong here? Has anyone used ILNumerics in an async call like this?

I wrote a number of additional test functions that perform very simple tasks (no optimizations, just straight Array manipulation), and I'm finding the same thing with these simple functions. If I run them in the UI thread, no problem as long as I haven't run any async functions. But running any of these functions asynchronously will eventually lead to heap corruption or protected memory access violations, no matter where I call the functions (UI thread or asynchronously).

SiscoKid
  • 49
  • 5
  • You are sure that you press button next time only after first operation is completed? Because otherwise you are running this code in parallel and that of course can cause anything including heap corruption if code is not thread-safe – Evk Nov 30 '22 at 09:04
  • What you mean by 'skyrocket'? Can you quantify, please? It might be intended and not an issue. – user492238 Nov 30 '22 at 10:47
  • Contrary to your description the part of your code shown does not implement the [ILNumerics function rules](https://ilnumerics.net/general-usage_v5.html). This might be another cause of more memory being used ? – user492238 Nov 30 '22 at 10:50
  • @Evk I am handling "synchronization" by not pressing the button until the previous call completes. I even put in code to disable the event handler until the response function completes to be extra sure. I am definitely not seeing reentrancy issues. – SiscoKid Dec 01 '22 at 04:36
  • "Skyrocket" means the stack jumps up to over 3GB in the 10 seconds it takes for the synchronous call to finish. This doesn't happen with the asynchronous call, leading me to believe that the extra memory usage is caused by not pumping the Windows message queue. – SiscoKid Dec 01 '22 at 04:36
  • @user492238 can you point out where I'm not following the function rules? Technically this is not an ILNumerics function, in that it takes no ILNumerics parameters and does not return an ILNumerics array. The code does call an ILNumerics function internally, but I don't think that fact makes this an ILNumerics function. If that were the case, then wouldn't my entire application have to follow the ILNumerics function rules? Within my non-ILNumerics class, I call ILNumerics functions, but only after converting all of my System.Array based variable to ILNumerics variables. – SiscoKid Dec 01 '22 at 04:36
  • _"Skyrocket" means the stack jumps up to over 3GB_ - assuming you mean: the heap? – Haymo Kutschbach Dec 02 '22 at 11:21
  • Technically, the only indication for a true _issue_ is when you get OOM exceptions (without obvious reason). The managed heap is made to allocate until the GC kicks in. ILNumerics arrays introduces a few rules which - when followed - clean array memory up deterministically and immediately, without relying on the GC. What you are seeing corresponds to the 'normal' behavior without ILNumerics memory management. One reason is likely that your function does not seem to implement these rules. – Haymo Kutschbach Dec 02 '22 at 11:26

2 Answers2

2

There is nothing that prevents comp.Characterize from being called concurrently. This might not be allowed by the framework. If nothing else is documented you should assume that objects are not thread safe.

When using multi threading you have to ensure all objects used are thread safe. Some are completely thread safe, some disallow concurrent usage, and some only allow the creating thread to call methods on the object (like most UI objects).

One way to avoid such situations might be to disable the button that starts a computation before the method is called, and only enable it when the computation is finished. Another alternative would be to show a modal dialog while the operation is in progress, preventing any interaction with the UI.

You should probably also check that the parameters to the method cannot change while the method is running. Ideally by making them immutable.

user492238
  • 4,094
  • 1
  • 20
  • 26
JonasH
  • 28,608
  • 2
  • 10
  • 23
  • I am handling "synchronization" by not pressing the button until the previous call completes. I even put in code to disable the event handler until the response function completes to be extra sure. I am definitely not seeing reentrancy issues. – SiscoKid Dec 01 '22 at 04:38
0

@JonasH is right in assuming the cause of the issue in the objects not being thread safe. ILNumerics Array (up to version 6) are not thread safe! This is by design. To make them thread safe would introduce some price to be paid to performance. Things may change in version 7 though.

The best bet for now is to let ILNumerics handle parallelization and to use it from one thread only. Alternatively, you will have to apply the same precautions as common for multithreaded code: use locks (easiest) or other synchronization options to make sure that access to arrays happens not concurrently.

user492238
  • 4,094
  • 1
  • 20
  • 26
  • In no case is any ILNumerics array being shared between threads. In the case of the synchronous call, everything is happening on the UI thread. In the case of the asynchronous call, all ILNumerics variables are being created within the context of Task.Run(), and all results are copied back to the System.Array-based Component object before control is returned to the Windows thread. Am I missing something? – SiscoKid Dec 01 '22 at 04:42