如果我修复W1050警告提示,CharInSet比IN慢得多?

Mik*_*nni 5 delphi performance delphi-xe7

我在我的项目中使用了很多IN,并且我有很多这些警告:

[DCC警告] Unit1.pas(40):W1050 WideChar在集合表达式中减少为字节char.考虑在SysUtils单元中使用CharInSet函数.

我做了一个快速测试,使用CharInSet而不是IN从65%-100%慢:

if s1[i] in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] then
Run Code Online (Sandbox Code Playgroud)

VS

if CharInSet(s1[i], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']) then
Run Code Online (Sandbox Code Playgroud)

这是2个测试的代码,一个用于循环通过较短的字符串,一个循环通过一个大字符串:

在表单上添加2个按钮我测试了这个短字符串:

procedure TForm1.Button1Click(Sender: TObject);
var s1: string;
  t1, t2: TStopWatch;
  a, i, cnt, vMaxLoop: Integer;
begin
  s1 := '[DCC Warning] Unit1.pas(40): W1050 WideChar reduced to byte char in set expressions.  Consider using CharInSet function in SysUtils unit.';
  vMaxLoop := 10000000;

  cnt := 0;
  t1 := TStopWatch.Create;
  t1.Start;
  for a := 1 to vMaxLoop do
    for i := 1 to Length(s1) do
      if s1[i] in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] then
        inc(cnt);
  t1.Stop;

  cnt := 0;
  t2 := TStopWatch.Create;
  t2.Start;
  for a := 1 to vMaxLoop do
    for i := 1 to Length(s1) do
      if CharInSet(s1[i], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']) then
        inc(cnt);
  t2.Stop;

  Button1.Caption := inttostr(t1.ElapsedMilliseconds) + ' - ' + inttostr(t2.ElapsedMilliseconds);
end;
Run Code Online (Sandbox Code Playgroud)

这个1长字符串:

procedure TForm1.Button2Click(Sender: TObject);
var s1: string;
  t1, t2: TStopWatch;
  a, i, cnt, vMaxLoop: Integer;
begin

  s1 := '[DCC Warning] Unit1.pas(40): W1050 WideChar reduced to byte char in set expressions.  Consider using CharInSet function in SysUtils unit.';
  s1 := DupeString(s1, 1000000);
  s1 := s1 + s1 + s1 + s1; // DupeString is limited, use this to create longer string

  cnt := 0;
  t1 := TStopWatch.Create;
  t1.Start;
  for i := 1 to Length(s1) do
    if s1[i] in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] then
      inc(cnt);
  t1.Stop;

  cnt := 0;
  t2 := TStopWatch.Create;
  t2.Start;
  for i := 1 to Length(s1) do
    if CharInSet(s1[i], ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']) then
      inc(cnt);
  t2.Stop;

  Button2.Caption := inttostr(t1.ElapsedMilliseconds) + ' - ' + inttostr(t2.ElapsedMilliseconds);
end;
Run Code Online (Sandbox Code Playgroud)

为什么他们建议使用较慢的选项,或者如何在不降低性能的情况下修复此警告?

Dav*_*nan 9

警告告诉您您的代码可能有缺陷.由于集合只能基于常数为256或更小的类型,因此基本类型将截断为该大小.现在,Char是别名,WideChar并且具有通常65536.所以警告是告诉您,您的程序可能不会按预期运行.例如,有人可能会问这个表达式的评估结果如下:

['A', chr(256)] = ['A']
Run Code Online (Sandbox Code Playgroud)

人们可能会期望它评估错误,但事实上它评估为真.所以我认为你应该在发出这个警告时注意编译器.

现在,碰巧你的集合可以而且应该更简洁地写成['A'..'Z'],完全由ASCII字符组成.并且它发生了(感谢评论员Andreas和ventiseis),在这种情况下,编译器为这样的集合生成正确的代码,而不管in运算符左边的字符的序数值.所以

if s1[i] in ['A'..'Z'] then
Run Code Online (Sandbox Code Playgroud)

尽管有警告,但仍会产生正确的代码.编译器能够检测到集合的元素是连续的并生成有效的代码.

请注意,这确实取决于集合是文字,因此优化可以由编译器执行.这就是为什么它的表现要好得多CharInSet.因为CharInSet是一个函数,并且Delphi优化器的功率有限,CharInSet所以无法利用这个特定集合文字的连续性.

这个警告很烦人,你真的想依靠记住这个警告何时可以安全忽略的具体细节.另一种实现测试的方法,并回避此警告是使用不等运算符:

if (c >= 'A') and (c <= 'Z') then
  ....
Run Code Online (Sandbox Code Playgroud)

您可能将其包装在内联函数中,以使代码更易于阅读.

function IsUpperCaseEnglishLetter(c: Char): Boolean; inline;
begin
  Result := (c >= 'A') and (c <= 'Z');
end;
Run Code Online (Sandbox Code Playgroud)

您还应该问自己这个代码是否是性能瓶颈.你应该为真正的计划而不是这样一个人工计划.我敢打赌,这段代码不是瓶颈,如果是这样,你不应该将性能视为关键驱动因素.

  • "集合效率低得多":不是这种情况.编译器足够聪明,可以将长元素列表更改为['A'..'Z']本身,然后使用快速`if(c> ='A')和(c <='Z')`来实施运营商.只要设置元素是Ord(x)<=#127,那么使用正确的WideChar代码也是如此. (3认同)