Why is Thread.Start So Slow?

Xojo 2021r2.1 under MacOS 13.3 seems to take ≈140ms just to call Thread.Start.

Dim now As Double = Microseconds

For Each t As myThread in myThreads
  t.Start
Next

System.DebugLog "Start " + Str(myThreads.Count) + " threads: " + Str((Microseconds-now)/1000) + " ms"

Results in

12:42:44 PM : Start 7 threads: 1074.458 ms

Why so slow? ThreadState is “not running” for all of the threads.

Is this compiled or in the IDE?

I wonder what would happen if you disabled background tasks around that loop?

#pragma BackgroundTasks false
for each t ...
next
#pragma BackgroundTasks true

In the IDE of course, else I don’t think I’d be getting debuglog messages :slight_smile:

Disabling background tasks has no effect, nor does running from a build:

image

FYI, DebugLog message still show up in Console on the Mac.

Next thing to try: At the top of your Thread.Run code, insert Thread.Sleep 10 Sleep 10.

1 Like

I did not know that (obviously), thanks :slight_smile:

Brilliant! 260us now (for all 7 of them).

Great.

What happens (it seems) is that Thread.Start starts running the code in your Thread, and doesn’t yield until it hits the first loop (or wherever else Xojo yields). So the first one starts, does the first iterations on your data, then yields back to Main. Then the next one starts, also does a few iterations, then yields again, but maybe to the first Thread for a few more iterations before getting back to Main. Then the next one starts, and so on.

By sleeping the Thread immediately, you are returning to Main instantly after each one is started so your timing no longer includes any processing done by the Thread.

2 Likes

@Julia_Truchsess was kind enough to try another suggestion and reported that it’s faster. I’m guessing it’s more efficient too, so for the sake of completeness, this is the trick:

for t as Thread in threads
  Timer.CallLater 1, AddressOf t.Start
next

That will delay starting each Thread until the Main thread is idling.

5 Likes

Totally off topic, but I just wanted to say how much I appreciate it when someone starts a thread this way, indicating the version of Xojo and OS type and version.

Makes it so much easier to figure out if topic is relevant to your code and easier to troubleshoot.

10 Likes

Thank you Kem for this awesome trick.
I applied this to my app which is very thread heavy and it speeds up everything a lot. Wow!
Never knew about this. The difference is striking.

Makes me wonder if this couldn’t be implemented by default by Xojo Inc?

3 Likes

My thought exactly. It should obviously work this way by default.

IMO because in general Xojo’s priority is not optimizing execution speed - except perhaps when they need to for something they use themselves.

  • Karen
2 Likes

I don’t want the framework to offload anything to the next event loop without my knowledge. The way the framework works now, you can tell what object started a thread if something raises an exception before the thread yields. This is desirable because it can help identify errors.

I would only accept asynchronous thread starting if the name were specific about this. Something like Thread.StartInNextEventLoop would be allowable. …which you can very easily implement for yourself as detailed in this thread.

The Xojo team should be focusing on things we cannot do ourselves.

4 Likes

You may be right, but your use of the term “asynchronous” seems completely backward to me. If you write a line that says Thread.Start, you want it to start at that point. I suppose you think differently, perhaps superior, that’s okay. When I write that line, I want the thread to start there. Seems obvious to me.

A similar problem happens for other things like when working with serial ports on Windows. Writing the line that says “send this out on the serial port” doesn’t do anything at all at that point. Xojo has to get a thread return before anything happens.

So if you ask me, this kind of thing is totally madenning. The code is arranged in lines which are supposed to execute one after the other. Do this first, do that next. Not, do this, but oh not really, I meant wait until everything else finishes and then see if you can do it later. No, I meant do this now. I’m sure you’ll tell me it doesn’t work that way and never has. Oh well.

1 Like

What we can do for ourselves is not an absolute… It is very relative.

Julia is a long timer user and did the things the obvious and straightforward way … But then she found she had to use “tricks” to get good performance, and had to waste time to find them…

If Kem was not here would she even have found a way to get good performance for basic that functionality?

This type of thing is IMO a basic quality issue… Workarounds take time to find and implement and are often non obvious, even when the user can find one, and the complicate the code.

IMO A significant part of the product design (and maintenanceUpdates) should be to consider performance issues to minimize the need for non obvious tricks to get decent performance… particularly considering their target audience.

Unlike when building a specific app where one can judge the tradeoffs daily well, a tool to build other tools can (or rather should) make less assumptions about what those tradeoffs are. That is not to say it they should be optimizing forever and never release, but that it seems to me it should be a bigger factor in their design and UPDATING considerations than it is…

The fact that it’s not may point to a lack of sufficient resources.

I know workarounds have consumed a lot of my time over the years. At first I took some pride in being about to figure some out, but I have come to realize I should not have had to deal with so many for so long.

-Karen

4 Likes

Thread.StartWithoutRunningThreadCodeInMainThread :slight_smile:

Fixing the underlying root structural cause if possible would be preferable to a Timer.CallLater hack, I agree.

2 Likes

Unlikely I would have stumbled on the solution myself, it was a pretty opaque issue.

“I don’t want the framework to offload anything to the next event loop without my knowledge.”

I completely agree with this statement. It must do what we say it to do in an exact fashion. The way Kem handled the situation here, that may work in this context, would introduce random timing bugs in some contexts.

4 Likes

Agreed, but therein lies the rub: Thread.Start doesn’t just start the thread, it executes some thread code on the main thread and then when it feels like it it splits it off to the thread. This is undocumented, so it doesn’t “do what we say it to do in an exact fashion”.

No. That’s not what’s happening.

Thread.Start is starting the thread code right then and there. That happens in the thread, not the main thread. The reason you don’t see the next thread start until the first yield is because Xojo threading is cooperative. The main thread cannot run until the other thread yields.

@Aaron_Hunt does this help clarify? When you write Thread.Start, that is exactly when the thread is starting.

3 Likes