8

How can I use SetProcessAffinityMask to select more than one logical processor?

In Windows Task Manager you can do this as an example:

enter image description here

I updated my CreateProcess procedure to do this:

type
  TProcessPriority = (ptLow         = $00000040,
                      ptBelowNormal = $00004000,
                      ptNormal      = $00000020,
                      ptAboveNormal = $00008000,
                      ptHigh        = $00000080,
                      ptRealtime    = $00000100);

procedure RunProcess(FileName: string; Priority: TProcessPriority);
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  CmdLine: string;
  Done: Boolean;
begin
  FillChar(StartInfo, SizeOf(TStartupInfo), #0);
  FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
  StartInfo.cb := SizeOf(TStartupInfo);

  CmdLine := FileName;
  UniqueString(CmdLine);
  try
    Done := CreateProcess(nil, PChar(CmdLine), nil, nil, False,
                          CREATE_NEW_PROCESS_GROUP + Integer(Priority),
                          nil, nil, StartInfo, ProcInfo);
    if Done then
    begin
      // Todo: Get actual cpu core count before attempting to set affinity!
      // 0 = <All Processors>
      // 1 = CPU 0
      // 2 = CPU 1
      // 3 = CPU 2
      // 4 = CPU 3
      // 5 = CPU 5
      // 6 = CPU 6
      // 7 = CPU 6
      // 8 = CPU 7

      // this sets to CPU 0 - but how to allow multiple parameters to
      // set more than one logical processor?
      SetProcessAffinityMask(ProcInfo.hProcess, 1); 
    end else
      MessageDlg('Could not run ' + FileName, mtError, [mbOk], 0)
  finally
    CloseHandle(ProcInfo.hProcess);
    CloseHandle(ProcInfo.hThread);
  end;
end;

Note the comments I put in there. It would be good to update my procedure to include a new Affinity parameter which I can pass to SetProcessAffinityMask.

Calling any of these won't select the corresponding processors for obvious reasons, they give the idea of what I am wanting to do:

SetProcessAffinityMask(ProcInfo.hProcess, 1 + 2); 
SetProcessAffinityMask(ProcInfo.hProcess, 1 and 2);

eg, select any of the CPU's for a process, as shown in Task Manager.

How should I do this, using an Array, Set or something else? I cannot get it to work with multiple values.

Thanks.

Community
  • 1
  • 1
  • 1
    Oh, come on! A process affinity mask is a bit vector in which each bit represents the processor... – OnTheFly Jan 31 '12 at 11:39
  • Well I didn't know that sorry! I should of researched further, as David pointed out (and I forgot) to check out the MSDN where it states it is a bit vector, which I didn't know at first! –  Jan 31 '12 at 11:50
  • Although already answered, look here: http://edn.embarcadero.com/article/27267 – Jerry Dodge Aug 21 '12 at 08:23
  • @JerryDodge thanks for link, it never hurts to have extra information on your side :) –  Aug 25 '12 at 08:59

2 Answers2

13

It is a bitmask as described in the documentation.

A process affinity mask is a bit vector in which each bit represents a logical processor on which the threads of the process are allowed to run.

  • Processor 0 is $01.
  • Processor 1 is $02.
  • Processor 2 is $04.
  • Processor 3 is $08.
  • Processor 4 is $10.

And so on. You can use logical or to combine them. So processors 0 and 1 would be $01 or $02 which equals $03.

I would use the shift operator shl to create values for specific processors. Like this:

function SingleProcessorMask(const ProcessorIndex: Integer): DWORD_PTR;
begin
  //When shifting constants the compiler will force the result to be 32-bit
  //if you have more than 32 processors, `Result:= 1 shl x` will return
  //an incorrect result.
  Result := DWORD_PTR(1) shl (ProcessorIndex); 
end;

You can readily extend this to generate masks for lists of processors using logical or in a loop.

function CombinedProcessorMask(const Processors: array of Integer): DWORD_PTR;
var
  i: Integer;
begin
  Result := 0;
  for i := low(Processors) to high(Processors) do
    Result := Result or SingleProcessorMask(Processors[i]);
end;

You can test for a processor being in a bit mask like this:

function ProcessorInMask(const ProcessorMask: DWORD_PTR; 
  const ProcessorIndex: Integer): Boolean;
begin
  Result := (SingleProcessorMask(ProcessorIndex) and ProcessorMask)<>0;
end;

Note: I'm using DWORD_PTR because for 64 bit targets the bitmask is 64 bits wide. That nuance doesn't matter for you on XE but it's worth getting it right to make any future code porting easier.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I was too busy looking at Cardinal (the value passed to SetProcessAffinityMask(ProcInfo.hProcess, 1); Where did you find it being bitmask? –  Jan 31 '12 at 11:41
  • It's in the documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686223.aspx Remember my advice from your CreateProcess question. – David Heffernan Jan 31 '12 at 11:43
  • yeah, MSDN now bookmarked :) - "A process affinity mask is a bit vector in which each bit represents a logical processor on which the threads of the process are allowed to run. The value of the process affinity mask must be a subset of the system affinity mask values obtained by the GetProcessAffinityMask function. " –  Jan 31 '12 at 11:46
  • David, DWORD_PTR is infact in Delphi XE :) But that will be for me to determine if the process is x86 or x64 –  Jan 31 '12 at 12:04
  • Do you mean `Processor 4 is $16.`? – chappjc Jun 18 '14 at 00:10
8

It's a 32-bit bitmask on my XE (but it could be 64-bit on 64-bit XE2!)

Just define a set of [0..31] where 0=cpu 1 etc.

Then typemask the result to a dword.

So

var 
  cpuset  : set of 0..31;

begin
  cpuset:=[1,2]; // cpus 2 and 3
  include (cpuset,5); // add cpu 6
  SetProcessAffinityMask(ProcInfo.hProcess, dword(cpuset)); 
Marco van de Voort
  • 25,628
  • 5
  • 56
  • 89
  • This is dependent on the Delphi implementation of sets. But it does make for nice readable code. And it is 64 bit on 64 bit Windows. – David Heffernan Jan 31 '12 at 11:43
  • Afaik it works on all Delphi and non ancient FPC versions. Dangers are endianess related though, but even the ARM that windows 8 uses is LE – Marco van de Voort Jan 31 '12 at 11:59
  • Oh, I'm sure it works on all extant versions, but it's clearly implementation dependent and thus code break on some future Delphi. – David Heffernan Jan 31 '12 at 12:03
  • How does your code work for the nth bit? That was the main issue I wanted to avoid. I know pretty well what bitsets are and work, but I don' t worry about them. Usually if implementations change it only gets more configurable (e.g. $minenumsize and similar directives), to keep legacy code running. – Marco van de Voort Jan 31 '12 at 13:10
  • That's what `shl` is used for. – David Heffernan Jan 31 '12 at 13:11
  • If I look up the SHL documentation in the Delphi XE help, it says it takes an integer and returns an integer, IOW it is signed. It works, but contrary to documentation apparently ;) I wonder what D3 would do though (before int64 introduction, and the reason why I'm still careful with signedness) – Marco van de Voort Jan 31 '12 at 13:15
  • `shl` and `shr` operate at the level of the binary representation. Whether the data type is signed or unsigned is irrelevant. So `1 shl 31` assigned to a variable of type integer will set the sign bit and leave all other bits clear. Your reading of the documentation is incorrect. – David Heffernan Jan 31 '12 at 13:18
  • Shl: "The Shl keyword performs a bitwise shift left of an Integer. The number is shifted Bits to the left." http://www.delphibasics.co.uk/RTL.asp?Name=Shl –  Jan 31 '12 at 13:23
  • There are no winners (or correct vs incorrect) in a documentation<>implementation discrepancy. Just press ctrl-F1 on shl and pick the first topic. – Marco van de Voort Jan 31 '12 at 14:31
  • 1
    @Marco No, I'm afraid you are just misinterpreting it. The `integer` you refer to does not mean the type `System.Integer` that is an alias for `System.Longint`. It means all integer data types. For example Byte, Word, Shortint, Smallint, Longint, Longword, Int64, UInt64. Look at similar tables for other operators and you will see terms like "character pointer", real, etc. When a specific type is being invoked then that appears in italic. For example the result type for the = and <> operators. Note, I have the D6 OP language guide in paper form in my hands right now. – David Heffernan Jan 31 '12 at 17:34
  • @Marco contd. If you were correct you could infer that `shl` was not applicable to other types than `Longint`. And likewise for all other integral arithmetic operators. – David Heffernan Jan 31 '12 at 17:36