CharInSet比IN慢得多,我应该修复W1050警告提示吗?

5

我在项目中经常使用IN关键字,但是我遇到了很多这样的警告:

[DCC Warning] Unit1.pas(40): W1050 WideChar reduced to byte char in set expressions. Consider using CharInSet function in SysUtils unit.

我进行了一个快速测试,发现使用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

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

以下是两个测试的代码,一个通过循环较短的字符串进行操作,另一个则一次循环较长的字符串:

在表单上添加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;

And this for 1 long string:

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;

为什么他们建议使用更慢的选项,或者我如何在不影响性能的情况下解决这个警告?

2
请查看https://dev59.com/q3RC5IYBdhLWcg3wVvYt - RBA
1
有人能解释一下在这种情况下这个警告是否相关吗?该集合仅包含ASCII字符,如果s1[i]是宽字符,则Delphi Win32编译器会为该比较生成正确的代码。我尝试了s1:='Ł'; if(s1[1] in ['A']) then ...,因为Byte('Ł')=65=Byte('A')。但是对于这个比较,编译器生成了正确的代码。 - ventiseis
2
遵循DRY原则非常重要。DRY代表"Don't Repeat Yourself",即不要重复自己的代码。 - David Heffernan
1
@DavidHeffernan ord('Ł') = 321。我查看了CPU窗口,发现完整的字符代码被加载到了EAX中,没有缩短。 - ventiseis
2
CharInSet函数受到一个问题的困扰,即_set_必须存储在内存中,因为它被指定为函数参数。因此,编译器无法生成在CPU寄存器上操作的快速算术指令。它必须使用更慢的内存位测试指令。因此,在每次迭代中会有多次内存访问(非内联:函数调用、位测试、函数返回;内联:2次栈调整、位测试),而与之相比则是没有任何内存访问。 - Andreas Hausladen
显示剩余4条评论
1个回答

10

警告提示您的代码可能存在缺陷。因为集合只能基于256或更低阶数的类型,所以基础类型将被截断为该大小。现在,CharWideChar的别名,并且具有阶数65536。因此,警告是为了告诉您,您的程序可能不会按照您的期望行事。例如,有人可能会问这个表达式的求值结果是什么:

['A', chr(256)] = ['A']

或许你期望它会评估为假,但事实上它评估为真。因此我认为当编译器发出这个警告时,你应该一定要注意。

现在,碰巧你的集合可以并且应该更简洁地写成['A'..'Z'],这个集合完全由ASCII字符组成。而且(感谢评论者Andreas和ventiseis),在这种情况下,编译器会为这样的集合生成正确的代码,不管in操作符左边字符的序号值是多少。因此

if s1[i] in ['A'..'Z'] then

尽管会出现警告,但这将产生正确的代码。编译器能够检测到集合元素是连续的,并生成高效的代码。

请注意,这取决于集合是否为文字集合,因此编译器可以执行优化。这就是为什么它比CharInSet表现得更好的原因。由于CharInSet是一个函数,而Delphi优化器的能力有限,CharInSet无法利用这个特定的集合文字的连续性。

尽管警告很烦人,但你真的想依赖记住何时可以安全地忽略这个警告的非常特定的细节吗?实施测试并回避此警告的另一种方法是使用不等运算符:

if (c >= 'A') and (c <= 'Z') then
  ....

你可能会把这个代码包装在一个内联函数中,以使代码更易于阅读。

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

你还应该问自己这段代码是否会成为性能瓶颈。你需要计时真正的程序而不是这个人工程序。我敢打赌这段代码不是瓶颈,如果确实如此,那么你就不应该将性能视为关键驱动因素。


正确,不是瓶颈,但如果它使执行时间加倍,那就可能成为瓶颈了。顺便说一下,我测试过了,使用您的不等运算符的性能差异很小,2384->2355,975->959毫秒。 - Mike Torrettinni
1
即使它将执行时间加倍,也不太可能成为瓶颈。我相信你的程序做得比这更多。 - David Heffernan
3
“Sets are far less efficient”:在这种情况下并不是这样。编译器足够智能,可以将长元素列表自己改为 ['A'..'Z'],然后使用快速的 if (c >= 'A') and (c <= 'Z') 实现 in 运算符。只要集合元素满足 Ord(x)<=#127,就可以正确处理 WideChar 代码。 - Andreas Hausladen
@AndreasHausladen 是的,你说得对,编译器确实能够很好地优化这个。即使是64位编译器。 - David Heffernan
非常有价值的补充细节,David! - Mike Torrettinni
使用 AnsiStrings 而不是 Strings。Unicode 字符串效率低下。 - Andy k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接