如何使用 delphi 提高 WMI 性能?

Posted

技术标签:

【中文标题】如何使用 delphi 提高 WMI 性能?【英文标题】:How can I improve the WMI performance using delphi? 【发布时间】:2012-04-29 06:46:56 【问题描述】:

我编写了一个简单的函数来使用 WMI 检索系统信息,将类和属性名称作为参数传递。当我执行这样的功能时

  Writeln('Procesor Id '+GetWMIInfo('Win32_Processor','Name'));
  Writeln('Mother Board Serial '+GetWMIInfo('Win32_BaseBoard','SerialNumber'));
  Writeln('Bios Version '+GetWMIInfo('Win32_BIOS','Version'));

执行时间约为 1300 毫秒。

我需要检索很多额外的信息,那么可以减少这个函数的执行时间吗?

这是一个带有函数的示例应用程序

$APPTYPE CONSOLE

uses
  Diagnostics,
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

function  GetWMIInfo(const WMIClass, WMIProperty:string): string;
var
  sWbemLocator  : OLEVariant;
  sWMIService   : OLEVariant;
  sWbemObjectSet: OLEVariant;
  sWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  Result:='';
  sWbemLocator  := CreateOleObject('WbemScripting.SWbemLocator');
  sWMIService   := sWbemLocator.ConnectServer('', 'root\CIMV2', '', '');
  sWbemObjectSet:= sWMIService.ExecQuery('SELECT * FROM '+WMIClass,'WQL');
  oEnum         := IUnknown(sWbemObjectSet._NewEnum) as IEnumVariant;
  if oEnum.Next(1, sWbemObject, iValue) = 0 then
    Result:=sWbemObject.Properties_.Item(WMIProperty).Value;
end;

var
 SW : TStopwatch;

begin
 try
    CoInitialize(nil);
    try
      SW.Reset;
      SW.Start;
      Writeln('Procesor Id '+GetWMIInfo('Win32_Processor','Name'));
      Writeln('Mother Board Serial '+GetWMIInfo('Win32_BaseBoard','SerialNumber'));
      Writeln('BIOS Version '+GetWMIInfo('Win32_BIOS','Version'));
      SW.Stop;
      Writeln('Elapsed ms '+FormatFloat('#,0.000',SW.Elapsed.TotalMilliseconds));
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.

【问题讨论】:

【参考方案1】:

这是一般的经验法则。

当您说要检索大量附加信息时,我假设这意味着您将多次调用此函数,可能是在循环中。对于性能调整,您只需将那些花费大量时间并且可以重复使用的东西排除在循环之外,即缓存它们。

在这种情况下,CreateOleObject 可能会花费您大部分时间,作为第一遍,我会将其放在循环(或多次调用)之外并将您的 sWebLocator 传递给函数,作为第二遍,您可能会也喜欢将 ConnectServer 调用从函数中取出,并将 sWMIService 对象也传入。

【讨论】:

【参考方案2】:

这些是提高 WMI 性能的一些技巧

1.) 重新调用CreateOleObject

2.) 重用 WMI 连接

更昂贵的任务之一是连接到 WMI 服务,因此请重新利用该连接,而不是每次调用该函数时都创建一个连接。

3.) 仅检索您要使用的列

检索 WMI 的每个属性都有不同的来源,如 Windows 注册表、WinAPi 等,限制列将提高性能。阅读本文了解更多信息How obtain the source of the WMI Data

4.) 在执行 WQL 语句时使用WBEM_FLAG_FORWARD_ONLY 标志。

按照上述提示,我重写了您的示例应用

$APPTYPE CONSOLE

uses
  Diagnostics,
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;

function  GetWMIInfo(const WMIClass, WMIProperty:string): string;
const
  wbemFlagForwardOnly = $00000020;
var
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  Result:='';
  FWbemObjectSet:= FWMIService.ExecQuery(Format('Select %s from %s',[WMIProperty, WMIClass]),'WQL',wbemFlagForwardOnly);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  if oEnum.Next(1, FWbemObject, iValue) = 0 then
    Result:=FWbemObject.Properties_.Item(WMIProperty).Value;
end;

var
 SW : TStopwatch;

begin
 try
    CoInitialize(nil);
    try
      SW.Reset;
      SW.Start;
      FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
      FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
      Writeln('Procesor Id '+GetWMIInfo('Win32_Processor','Name'));
      Writeln('Mother Board Serial '+GetWMIInfo('Win32_BaseBoard','SerialNumber'));
      Writeln('BIOS Version '+GetWMIInfo('Win32_BIOS','Version'));
      SW.Stop;
      Writeln('Elapsed ms '+FormatFloat('#,0.000',SW.Elapsed.TotalMilliseconds));
    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;
 Readln;
end.

执行时间从 1245 到 180 毫秒(在我的笔记本电脑上)。

【讨论】:

+1。很好,你又让我删除了我的劣质答案。 :) 除了您所说的之外,发帖人可能还想看看 MagWMI,这是一套免费的 Delphi 包装器,可以让事情变得更容易。它在内部进行一些信息缓存,使多个查询变得更加容易。包装器是免费的,并带有一个非常广泛的演示应用程序,可能会有所帮助。 是的,这与我所说的几乎相同......但是我个人认为更好的通用模式是将缓存的服务变量传递给函数,这允许 DI 和模拟测试框架中的这些变量。 当然更好的是创建一个对象来封装连接和功能。这个示例应用程序只是一个概念证明。第 3 点也从未被提及,而且非常重要。 能否修改上述函数,使其可以在一次调用中检索多个参数?例如:GetWMIInfo('Win32_LogicalDisk','Caption,FreeSpace,Size')。如果它可以像 cmd 对应的那样检索所有现有分区的所有信息,那就太好了: wmic logicaldisk where mediatype=12 get caption,size,freespace /format:LIST

以上是关于如何使用 delphi 提高 WMI 性能?的主要内容,如果未能解决你的问题,请参考以下文章

如何提高表视图中的 UILabel 性能?

如何使delphi VCL应用程序从命令行运行

如何使delphi2007在打开的时候不打开默认的网页?

如何提高数据库查询的性能?

如何使自定义 GUI 控件对屏幕阅读器可见?

Delphi 10.2 JSON与对象/结构体序列化性能提高100多倍