CharInSet 比 IN 慢很多,我应该修复 W1050 警告提示吗?
CharInSet is much slower than IN, should I fix W1050 warning hint?
我在我的项目中经常使用 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
对
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 个测试的代码,一个通过较短的字符串循环,一个通过大字符串循环一次:
在表单上添加 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;
这是 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;
为什么他们推荐较慢的选项,或者我如何解决这个警告而不影响性能?
该警告告诉您您的代码可能有缺陷。因为集合只能基于序数为 256 或更少的类型,所以基类型被截断为该大小。现在,Char
是 WideChar
的别名,序数为 65536。因此警告是告诉您您的程序可能不会按您预期的方式运行。例如,有人可能会问这个表达式的计算结果是什么:
['A', chr(256)] = ['A']
人们可能期望它的计算结果为 false,但实际上它的计算结果为 true。所以我认为当编译器发出此警告时,您一定要注意它。
现在,碰巧你的集合完全由 ASCII 字符组成,可以而且应该更简洁地写成 ['A'..'Z']
。碰巧(感谢评论员 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;
您还应该问问自己这段代码是否是性能瓶颈。您应该为您的真实程序计时,而不是为这样的人工程序计时。我敢打赌这段代码不是瓶颈,如果是这样,您不应将性能视为关键驱动因素。
我在我的项目中经常使用 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
对
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 个测试的代码,一个通过较短的字符串循环,一个通过大字符串循环一次:
在表单上添加 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;
这是 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;
为什么他们推荐较慢的选项,或者我如何解决这个警告而不影响性能?
该警告告诉您您的代码可能有缺陷。因为集合只能基于序数为 256 或更少的类型,所以基类型被截断为该大小。现在,Char
是 WideChar
的别名,序数为 65536。因此警告是告诉您您的程序可能不会按您预期的方式运行。例如,有人可能会问这个表达式的计算结果是什么:
['A', chr(256)] = ['A']
人们可能期望它的计算结果为 false,但实际上它的计算结果为 true。所以我认为当编译器发出此警告时,您一定要注意它。
现在,碰巧你的集合完全由 ASCII 字符组成,可以而且应该更简洁地写成 ['A'..'Z']
。碰巧(感谢评论员 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;
您还应该问问自己这段代码是否是性能瓶颈。您应该为您的真实程序计时,而不是为这样的人工程序计时。我敢打赌这段代码不是瓶颈,如果是这样,您不应将性能视为关键驱动因素。