3

I've tried to get CPU cache information by using WMI and it works great, but only for Level 2 and Level 3 cache, so my question is, how to get CPU Level 1 cache information ?

TLama
  • 75,147
  • 17
  • 214
  • 392
Giacomo King Patermo
  • 829
  • 3
  • 13
  • 25

2 Answers2

9

Here's the WinAPI way, which uses the GetLogicalProcessorInformation function. If you have at least Delphi XE2, you won't need the following definitions, they are already in the Windows unit:

type
  TLogicalProcessorRelationship = (
    RelationProcessorCore = 0,
    RelationNumaNode = 1,
    RelationCache = 2,
    RelationProcessorPackage = 3,
    RelationGroup = 4,
    RelationAll = $FFFF
  );
  TProcessorCacheType = (
    CacheUnified,
    CacheInstruction,
    CacheData,
    CacheTrace
  );
  TCacheDescriptor = record
    Level: Byte;
    Associativity: Byte;
    LineSize: Word;
    Size: DWORD;
    pcType: TProcessorCacheType;
  end;
  PSystemLogicalProcessorInformation = ^TSystemLogicalProcessorInformation;
  TSystemLogicalProcessorInformation = record
    ProcessorMask: ULONG_PTR;
    Relationship: TLogicalProcessorRelationship;
    case Integer of
      0: (Flags: Byte);
      1: (NodeNumber: DWORD);
      2: (Cache: TCacheDescriptor);
      3: (Reserved: array [0..1] of ULONGLONG);
  end;

function GetLogicalProcessorInformation(
  Buffer: PSystemLogicalProcessorInformation;
  var ReturnLength: DWORD): BOOL; stdcall;
  external kernel32 name 'GetLogicalProcessorInformation';

And the example of how to show the cache type, level and size for all level 1 cache entries:

procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
  I: Integer;
  ReturnLength: DWORD;
  Buffer: array of TSystemLogicalProcessorInformation;
begin
  SetLength(Buffer, 1);
  if not GetLogicalProcessorInformation(@Buffer[0], ReturnLength) then
  begin
    if GetLastError = ERROR_INSUFFICIENT_BUFFER then
    begin
      SetLength(Buffer, 
        ReturnLength div SizeOf(TSystemLogicalProcessorInformation) + 1);
      if not GetLogicalProcessorInformation(@Buffer[0], ReturnLength) then
        RaiseLastOSError;
    end
    else
      RaiseLastOSError;
  end;

  for I := 0 to High(Buffer) do
  begin
    if (Buffer[I].Relationship = RelationCache) and
      (Buffer[I].Cache.Level = 1) then
    begin
      S := 'Type: ';
      case Buffer[I].Cache.pcType of
        CacheUnified: S := S + 'Unified cache';
        CacheInstruction: S := S + 'Instruction cache';
        CacheData: S := S + 'Data cache';
        CacheTrace: S := S + 'Trace cache';
      end;
      S := S + sLineBreak;
      S := S + 'Level: ' + IntToStr(Buffer[I].Cache.Level) + sLineBreak;
      S := S + 'Size: ' + IntToStr(Buffer[I].Cache.Size) + ' B';
      ShowMessage(S);
    end;
  end;
end;
TLama
  • 75,147
  • 17
  • 214
  • 392
  • 1
    +1 This is the right way to do it from Delphi. Note that Windows.pas defines GetLogicalProcessorInformation and the various types needed. At least it does in XE3. I'm not sure when it was added. – David Heffernan Dec 09 '12 at 16:32
  • @David, not in XE, but exists in XE2. – LU RD Dec 09 '12 at 17:01
  • Or... Use Jedi WinApi library instead of converting your own! – Remko Dec 09 '12 at 17:09
  • 2
    @Remko, that's what I usually do, but I always check their translations against the reference. At this time there's a wrong definition in [`JwaWinNT.pas`](http://jedi-apilib.svn.sourceforge.net/viewvc/jedi-apilib/jwapi/trunk/Win32API/JwaWinNT.pas?view=markup) in the `_SYSTEM_LOGICAL_PROCESSOR_INFORMATION` record at the union part, there's no `Cache` member, which is the most wanted for this case. – TLama Dec 09 '12 at 17:45
  • 2
    @TLama you should file a bug for us ;-) I'll have alook at it! – Remko Dec 09 '12 at 21:13
  • @Remko, yes, but I'm sooo lazy these days and I knew you're the one who can take care of it :-) – TLama Dec 09 '12 at 21:19
  • @TLama I get an error: First chance exception at $00464CCD. Exception class $C0000005 with message 'access violation at 0x00464ccd: read of address 0x00001158'. Process Project1.exe (5088) – Giacomo King Patermo Dec 10 '12 at 18:28
5

You can use the Win32_CacheMemory WMI class, try this code:

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ActiveX,
  ComObj,
  Variants;



procedure  GetWin32_CacheMemoryInfo;
const
  WbemUser            ='';
  WbemPassword        ='';
  WbemComputer        ='localhost';
  wbemFlagForwardOnly = $00000020;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObjectSet:= FWMIService.ExecQuery('SELECT MaxCacheSize, Purpose  FROM Win32_CacheMemory','WQL',wbemFlagForwardOnly);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Writeln(Format('MaxCacheSize    %d',[Integer(FWbemObject.MaxCacheSize)]));
    Writeln(Format('Purpose         %s',[String(FWbemObject.Purpose)]));
    FWbemObject:=Unassigned;
  end;
end;


begin
 try
    CoInitialize(nil);
    try
      GetWin32_CacheMemoryInfo;
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException do
        Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.
RRUZ
  • 134,889
  • 20
  • 356
  • 483