Set of Preemptive Thread

I have to compare 2 big MemoryBlock (which contain picture and of course which have the same size because if not they can’t be identical).
I created a Set of 5 Preemptive Thread and inside each of them I have a loop:

Dim TpGdNbreDeb, TpGdNbreFin, iNbre as Int64

TpGdNbreDeb = ThCpMmBlkTab(index)
TpGdNbreFin = ThCpMmBlkTab(index + 1) - 1
For iNbre = TpGdNbreDeb to TpGdNbreFin step 8
Me.Sleep 100, False
If not(ThCpMmBlkA.Int64Value(iNbre) = ThCpMmBlkB.Int64Value(iNbre)) Then ThCpMmBlkDp = True
If ThCpMmBlkDp Then Exit ’ A pu être mis à vrai dans un autre Thread
Next iNbre

For example if the size of my MemoryBlock is 50000 then ThCpMmBlkTab contains [0, 10000, 20000, 30000, 40000, 50000]

I build my application and it work, but not quickest as my previous version without the 5 preemptive Thread.

My question is: Can we make Set of Preemptive Thread or should I make 5 Preemptive threads (ThreadA, ThreadB, ThreadC, ThreadD and ThreadE) and copy the code in each of them (and I will add a constant for the index).

So use the previous version and move on.

Not sure why you’d include a call to Sleep inside the execution code.

This is the kind of thing for which I designed ThreadPool. You define the function in a ThreadPool subclass, then send your data into it. In your case, you’d send the two MemoryBlocks with a starting index, maybe as a Pair. It would return a True or False, and you’d stop the TheadPool on the first False.

1 Like

I forgot to remove the sleep, it is not there in the built version

Thank you for yours answers. I didn’t know “ThreadPool”. The 5 preemptive threads were called from a Cooperative-thread and I forgot a Sleep 100 in this main Cooperative-thread too :face_with_open_eyes_and_hand_over_mouth: . My application runs 2 times faster now (with preemptive threads than all the process in a single cooperative-thread).
I read the Xojo documentation on preemptive-threads, I’m not sure I all understood but I had crash when all my Preemptive-Threads write in a single Flag, then I made an array of Flags (same count if Preemptive-Threads) and each one write in its own and all seems good.

The gain (2 times faster) is not as big as I wish but now I know use many Preemptive-Threads.
Thank you Xojo to allow hobbyist developer to use Preemptive-Threads :face_blowing_a_kiss: .
Note: I didn’t spend 4 days on it, I have my job to do.

1 Like

I bet you can get a 2x to 4x speedup without using threads - try this:

Public Function CompareMemoryBlocksFast(m1 as MemoryBlock, m2 as MemoryBlock) As boolean
  // Compares contentens of two MemoryBlocks, returns true iff the contents is identical
  // uses Ptrs and Pragmas for maximum speed
  if m1 = nil or m2 = nil then
    return false
  end if
  
  
  if m1.Size <> m2.size then
    return false
  end if
  
  #Pragma BackgroundTasks false
  #Pragma BoundsChecking false
  #Pragma NilObjectChecking false
  #Pragma StackOverflowChecking false
  
  var p1 as Ptr = m1
  var p2 as Ptr = m2
  var i as integer
  
  // calculate the nearest multiple of 8 bytes for the end
  // this allows us to compare uint64s which are 8 bytes each
  // for speed
  var iMax as integer = (8 * (m1.size \ 8) ) - 1
  
  for i = 0 to iMax step 8
    if p1.UInt64(i) <> p2.uint64(i) then
      return false
    end if
  next
  
  // if size was not a multiple of 8 bytes, handle remaining bytes one by one
  for i = iMax +1 to m1.size -1
    if p1.UInt8(i) <> p2.uint8(i) then
      return false
    end if
  next
  
  return true
End Function
2 Likes

Here’s a sample project - on my M4 Pro I’m seeing these results:


// Built app using Xojo 2025R3
// Build Optimization Level: Default
Fast compare of 128MB took 40 msec
Slow compare of 128MB took 272 msec

Fast version is 6.8x as fast



// Built app using Xojo 2025R3
// Build Optimization Level: Aggressive
Fast compare of 128MB took 14 msec
Slow compare of 128MB took 249 msec

Fast version is 18x as fast!

compareMemoryBlockFast.xojo_binary_project.zip (6.0 KB)

3 Likes

Thank you Mike, I never use #Pragma but I think I should in this case, I will try.
But why do you do var p1 as Ptr = m1 and then use p1.UInt64(i) and not directly (as I do) m1.UInt64(i) ?
Note1: I use INT64 instead of Uint64 but I don’t think it change anything.
Note2: I check the remaining not multiple of 8 bytes in the main thread.

Back to Pragmatique question, why use #Pragma BackgroundTasks false in a Preemptive thread as it execute the task in another Core. The backgroundTasks are down in the Main Thread then using another Core.

Once you are sure your code is not buggy, turning it on can vastly increase speeds.

In this code, m1 and m2 are MemoryBlocks, which are Xojo objects. Calling m1.uint64value(i) means the xojo code has to take a bunch of steps: Ensuring the memory block is not nil, and if it is, throw an exception, if not, call a method named uint64Value() - this method itself does a bunch of checks (is the Stack overflowing?) and finally calculates the memory address (a pointer to) the Uint64 to return.

By comparison, in my fast version of the code, p1 and p2 are Pointers.

With a Pointer, there are no intermediate steps, and the compiled code is very short, simply “get the 64 bit value at this memory address”.

Pointers are fast, but also dangerous, it’s easy to crash your app if you make a mistake

I think this has no effect at all in a preemptive thread (?). In any case, since the Fast version of my code is ~18x as fast as your version, I would not bother with preemptive threads at all. Preemptive threading is hard to do properly.

1 Like

Also, I just realized that MemoryBlock now supports the <, =, and > operators. So you can simply do this:

if m1 = m2 then
  ' contents of the two memoryBlocks are equal
end if

Memoryblocks also convert automatically to strings, so you can also do this:

var s1 as string = m1
var s2 as string = m2
if s1 = s2 then
  ' contents of the two memoryBlocks are equal (string comparison)
end if

Although these built-in Xojo methods are quite fast, both are actually slower than my fast compare version:


Fast compare of 128MB took 15 msec
Xojo m1=m2 of 128MB took 37 msec (memoryblock comparison)
Xojo s1=s2 of 128MB took 35 msec (string comparison)
Slow compare of 128MB took 250 msec

Project file v2:

compareMemoryBlockFast_v2.xojo_binary_project.zip (6.5 KB)

1 Like

That’s handy to know. :slight_smile:

That can’t be right. That’s an object reference comparison, isn’t it? That doesn’t compare contents.

2 Likes

I probably missed something. If m1 and m2 are references and they point to the same object, how could that object have two different contents at the same time ?

2 Likes

That’s gotta be a bug :beetle: in the docs. I don’t see how the compiler would ever be able to tell the difference between comparing the objects or their contents.

1 Like

I think that’s how it used to work, but MemoryBlock now implements Operator_Compare: MemoryBlock — Xojo documentation

If you want to check for object equality, use the “isa” operator:

Code
var m1,m2 as MemoryBlock

' check to make sure that < = and > all work as expected with memoryblocks:
m1 = "foo"
m2 = "bar"
log "m1 = """ + m1.StringValue(0,m1.size,encodings.UTF8) + """" + EndOfLine + "m2 = """ + m2.StringValue(0,m2.size,encodings.UTF8) + """"
log ""
if m1 = m2 then
  log "  m1 =  m2 : True"
else
  log "  m1 =  m2 : False"
end if

if m1 < m2 then
  log "  m1 <  m2 : True" 
else
  log "  m1 <  m2 : False" 
end if

if m1 > m2 then
  log "  m1 >  m2 : True" 
else
  log "  m1 >  m2 : False" 
end if


if m1 is m2 then
  log "  m1 is m2 : True" 
else
  log "  m1 is m2 : False" 
end if

log ""


' check to make sure that = and isa work
m1 = "foo"
m2 = m1
log "m1 = """ + m1.StringValue(0,m1.size,encodings.UTF8) + """" + EndOfLine + "m2 = m1"
log ""
if m1 = m2 then
  log "  m1 =  m2 : True"
else
  log "  m1 =  m2 : False"
end if

if m1 < m2 then
  log "  m1 <  m2 : True" 
else
  log "  m1 <  m2 : False" 
end if

if m1 > m2 then
  log "  m1 >  m2 : True" 
else
  log "  m1 >  m2 : False" 
end if


if m1 is m2 then
  log "  m1 is m2 : True" 
else
  log "  m1 is m2 : False" 
end if

Gives this output:

m1 = "foo"
m2 = "bar"

  m1 =  m2 : False
  m1 <  m2 : False
  m1 >  m2 : True
  m1 is m2 : False

m1 = "foo"
m2 = m1

  m1 =  m2 : True
  m1 <  m2 : False
  m1 >  m2 : False
  m1 is m2 : True

Project file:
compareMemoryBlockFast _v3.xojo_binary_project.zip (6.6 KB)

I just tested this in Xojo 2019, and it’s functioning the same as Xojo 2025 , which is consistent with the documentation So my claim that this is a “new” behavior may be suspect? Or perhaps it’s “new” as of 10+ years ago? :smiley:

Here’s an API1 version of the project if anyone wants to test older Xojo versions:

CompareMemoryBlockFastv3_Xojo2019.xojo_binary_project.zip (6.1 KB)

But are you sure it’s comparing contents I guess. I suspect that only the behavior of < and > were changed and that = still compares objects.

There’s an awful lot of code out there that could silently break with a change to how = works… and how confusing if = compares objects for everything else.

m1 = "foo"
m2 = "foo"

  m1 =  m2 : True
  m1 <  m2 : False
  m1 >  m2 : False
  m1 is m2 : False

That test clearly shows it’s comparing contents, not objects.

I had these exact same thoughts, but my memory must be faulty.

Here’s a test in RealStudio 2011R3 showing the same behavior:

Thank you all. Effectively Ptr is faster (only 2x faster if use in a thread in my tests).
But in my software, most of the CPU time is consumed by the conversion of picture in MemoryBlock then the use of Ptr instead of MemoryBlock doesn’t change.

I will continue to loop on Int64 and Int8 for the remaining as I’m not sure of the result of comparing MemoryBlock directly (MyMemoryBlock1 = MyMemoryBlock2).

I do not compare MemoryBlock.String as I read somewhere than we can’t convert more than x size of a memoryblock. And I have another doubt, what happens if I compare a Memoryblock which contains ´e and the other contains é. And somewhere else the opposite (é and ´e in the second) so the size are the same.

I enjoy these sorts of optimization puzzles - if you want to post your code for that picture conversion, I’d be happy to try to make it faster. Perhaps you should start a new thread though?