1

I have the following code running on Windows 10.

  function SingleProcessorMask(const ProcessorIndex: Integer): DWORD_PTR;
  begin
    Result:= 1; Result:= Result shl (ProcessorIndex);  //Make sure it works on processor 33 and up.
  end;

procedure TForm2.BtnCreateLookup5x5to3x3UsingSpeculativeExplorationClick(Sender: TObject);
var
  ThreadCount: integer;
  Threads: TArray<TThread>;
  CurrentProcessor: integer;
  i,a: integer;
  Done: boolean;
begin
  ThreadCount:= System.CpuCount;
  SetLength(Threads, ThreadCount);
  CurrentProcessor:= GetCurrentProcessorNumber;
  a:= 0;
  for i:= 1 to ThreadCount-1 do begin
    Threads[i]:= TThread.CreateAnonymousThread(procedure begin
      CreateLookupUsingGridSolver(i, ThreadCount);
    end);
    Threads[i].FreeOnTerminate:= false;
    if (CurrentProcessor = a) then Inc(a);  //Skip the current processor.
    Inc(a);
    //if (SetThreadAffinityMask(Threads[i].handle, SingleProcessorMask(a))) = 0 then RaiseLastOSError;  << fails here as well. 
    Threads[i].Start;
    if (SetThreadAffinityMask(Threads[i].handle, SingleProcessorMask(a))) = 0 then RaiseLastOSError;
  end; {for i}
  CreateLookupUsingGridSolver(0, ThreadCount, NewLookup);
  {Wait for all threads to finish}
  .....
  //Rest of the proc omitted to save space. 
end;

I keep getting error 87, Incorrect parameter. I'm fairly sure the SingleProcessorMask is correct. Is there perhaps an issue with the TThread.Handle?

It does not matter if I call this code running as Administrator, running on a laptop or running on a i9. The result is always the same.

And yes, I really do need to force the threads, otherwise they all bunch up on the same core.

UPDATE
Once I fix the process affinity to match the system affinity, there is no need to muck around with assigning each thread to a specific core. In that case the automatic handling works. This is done using:

GetProcessAffinityMask(GetCurrentProcess(), ProcessAffinityMask, SystemAffinityMask);
SetProcessAffinityMask(GetCurrentProcess(), SystemAffinityMask);
//Error checking omitted for brevity 
Johan
  • 74,508
  • 24
  • 191
  • 319
  • 1
    _"If the thread affinity mask requests a processor that is not selected for the process affinity mask, the last error code is `ERROR_INVALID_PARAMETER`"_. Perhaps that is what is happening. Perhaps that's the real problem that needs to be solved! If your process wasn't affinitised to a reduced set of processors, then you wouldn't feel compelled to commit this particular attrocity! ;-) – David Heffernan Nov 08 '18 at 15:39
  • @DavidHeffernan, Aha, that might explain it. How do I force the process affinity mask to include all processors? – Johan Nov 08 '18 at 15:41
  • `SetProcessAffinityMask`. But why is this happening in the first place? – David Heffernan Nov 08 '18 at 15:42
  • No idea, I would assume my process is enabled for all cores. – Johan Nov 08 '18 at 15:55
  • Perhaps it is, and perhaps you are trying to affinitise to a processor that doesn't exist. Have you debugged this at all? Can you affinitise with a mask of `1`? – David Heffernan Nov 08 '18 at 15:57
  • `Can you affinitise with a mask of 1?`, Yes, but that's it. – Johan Nov 08 '18 at 16:06
  • Once you fix the process affinity you won't need to set thread affinity. – David Heffernan Nov 08 '18 at 17:10

2 Answers2

5

It looks like you are trying to create a separate thread for every CPU other than the "current" CPU that is running your OnClick handler. But, you never use CPU 0 in your affinity masks, because you increment a too soon. But more importantly, a thread's affinity mask must be a subset of the process's affinity mask, which specifies the CPUs the process is allowed to run on:

A thread can only run on the processors its process can run on. Therefore, the thread affinity mask cannot specify a 1 bit for a processor when the process affinity mask specifies a 0 bit for that processor.

The process affinity mask is itself a subset of the system affinity mask, which specifies which CPUs are installed.

So, the likely cause of your error is that you are calculating thread affinity masks that the OS rejects as invalid for your process.

Try something more like this instead (note: this doesn't take CPU processor groups into account, if the OS has more than 64 CPUs installed):

procedure TForm2.BtnCreateLookup5x5to3x3UsingSpeculativeExplorationClick(Sender: TObject);
var
  ThreadCount, MaxThreadCount: integer;
  Threads: TArray<TThread>;
  i, CurrentProcessor: integer;
  ProcessAffinityMask, SystemAffinityMask, AllowedThreadMask, NewThreadMask: DWORD_PTR;      
  Thread: TThread;
  ...
begin
  if not GetProcessAffinityMask(GetCurrentProcess(), ProcessAffinityMask, SystemAffinityMask) then RaiseLastOSError;

  // optional: up the CPUs this process can run on, if needed...
  {
  if not SetProcessAffinityMask(GetCurrentProcess(), SystemAffinityMask) then RaiseLastOSError;
  ProcessAffinityMask := SystemAffinityMask;
  }

  AllowedThreadMask := DWORD_PTR(-1) and ProcessAffinityMask;
  CurrentProcessor := GetCurrentProcessorNumber;

  ThreadCount := 0;
  MaxThreadCount := System.CpuCount;
  NewThreadMask := 1;

  SetLength(Threads, MaxThreadCount);
  try
    for i := 0 to MaxThreadCount-1 do
    begin
      if (i <> CurrentProcessor) and //Skip the current processor.
         ((AllowedThreadMask and NewThreadMask) <> 0) then // is this CPU allowed?
      begin
        Thread := TThread.CreateAnonymousThread(
          procedure
          begin
            CreateLookupUsingGridSolver(...);
          end
        );
        try
          Thread.FreeOnTerminate := false;
          if not SetThreadAffinityMask(Thread.Handle, NewThreadMask) then RaiseLastOSError;
          Thread.Start;
        except
          Thread.Free;
          raise;
        end;
        Threads[ThreadCount] := Thread;
        Inc(ThreadCount);
      end;
      NewThreadMask := NewThreadMask shl 1;
    end;
    CreateLookupUsingGridSolver(...);

    // Wait for all threads to finish...
    // ...

  finally
    for i := 0 to ThreadCount-1 do
      Threads[i].Free;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
4

There are two arguments to SetThreadAffinityMask, the thread handle and the mask. It's pretty clear from the code that the thread handle is valid. Which leaves the mask. The documentation clearly states the following:

If the thread affinity mask requests a processor that is not selected for the process affinity mask, the last error code is ERROR_INVALID_PARAMETER.

It is rather hard to see what else could explain the behaviour that you report.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490