指针^与s [1]

时间:2011-06-20 12:37:43

标签: delphi

在一个从磁盘读取数据(数据意味着完全字符串)的函数中,我应该选择哪个?哪个更好?

A) DiskStream.Read(Pointer(s)^, Count)
or
B) DiskStream.Read(s[1], Count)

注意:
我知道两者都有相同的结果。
我知道在调用Read之前我必须使用S的SetLength。


更新

S是AnsiString。

这是完整的功能:

{从文件中读取一堆字符。为什么'ReadChars'而不是'ReadString'?此函数读取C ++字符串(字符串的长度也未写入磁盘)。所以,我必须给出作为参数读取的字符数。 }

function TMyStream.ReadChars(out s: AnsiString; CONST Count: Longint): Boolean; 
begin
 SetLength(s, Count);
 Result:= Read(s[1], Count)= Count;
end;

速度测试

在我的速度测试中,第一种方法比第二种方法快一点。我使用了一个400MB的文件,我从中读取了大约200000次字符串。该过程设置为高优先级。

有史以来最好的阅读时间是:
  变体B为1.35,变体A为1.37 平均:
  平均而言,B得分也比A好20ms。

每种变体重复测试15次。

差别很小。它可能落入测量误差范围。 如果我更频繁地从更大的文件中读取字符串,那么可能会很重要。 但就目前而言,我们可以说两行代码都表现相同。

ANSWER
变体A - 可能要快一点点 变体B - (显然)更容易阅读,它更像是Delphi-ish。我的首选。

注意:
我在TStreamReadBuffer示例中看到了Embarcadero使用变量A,但是使用TBytes而不是String。

7 个答案:

答案 0 :(得分:18)

绝对是数组符号。 Delphi风格的一部分是使您的代码易于阅读,并且当您详细说明您正在做的事情时,更容易分辨出正在发生的事情。将字符串转换为指针然后取消引用它看起来很混乱;你为什么要?除非读者对字符串内部结构有很多了解,否则没有意义。

答案 1 :(得分:13)

运行时请注意

1. DiskStream.Read(Pointer(s)^, Count)
2. DiskStream.Read(s[1], Count)

1.版本会更快。

但您必须确保s变量显式本地,或者您在循环之前调用了自己UniqueString(s)

由于pointer(s)^不会调用UniqueString?()低级隐藏RTL通话,它会比s[1] 更快,但您可能会覆盖某些现有数据如果s字符串变量在当前上下文和其他上下文之间共享(例如,如果从属性值中的函数检索到s的最后一个内容,或者s作为参数发送另一种方法)。

事实上,从内容中对此阅读AnsiString进行编码的最快正确方法是:

  s := '';
  SetLength(s,Count);
  DiskStream.Read(pointer(s)^,Count);

  SetString(s,nil,Count);
  DiskStream.Read(pointer(s)^,Count);

第二个版本等于第一个版本,但少了一行。

s设置为''会在FreeMem()+AllocMem()中拨打ReallocMem()而不是SetLength(),这样就可以避免拨打move(),因此会更快。

事实上,UniqueString?()生成的s[1] RTL调用速度非常快,因为您在调用它之前已调用SetLength():因此,s已经是唯一的,UniqueString?() RTL呼叫几乎立即返回。在分析之后,两个版本之间没有太大的速度差异:几乎所有的时间花在字符串分配和内容从磁盘移动。也许s[1]被发现更“邋”“。

答案 2 :(得分:7)

如果您关心优化,您应该更喜欢第一个变体。只需查看编译器生成的代码:

Unit7.pas.98: Stream.Read(Pointer(S)^, 10);
00470EA9 8B55FC           mov edx,[ebp-$04]
00470EAC B90A000000       mov ecx,$0000000a
00470EB1 8BC6             mov eax,esi
00470EB3 8B18             mov ebx,[eax]
00470EB5 FF530C           call dword ptr [ebx+$0c]

Unit7.pas.99: Stream.Read(s[1], 10);
00470EB8 8B5DFC           mov ebx,[ebp-$04]
00470EBB 85DB             test ebx,ebx
00470EBD 7418             jz $00470ed7
00470EBF 8BC3             mov eax,ebx
00470EC1 83E80A           sub eax,$0a
00470EC4 66833802         cmp word ptr [eax],$02
00470EC8 740D             jz $00470ed7
00470ECA 8D45FC           lea eax,[ebp-$04]
00470ECD 8B55FC           mov edx,[ebp-$04]
00470ED0 E8CB3FF9FF       call @InternalUStrFromLStr
00470ED5 8BD8             mov ebx,eax
00470ED7 8D45FC           lea eax,[ebp-$04]
00470EDA E89950F9FF       call @UniqueStringU
00470EDF 8BD0             mov edx,eax
00470EE1 B90A000000       mov ecx,$0000000a
00470EE6 8BC6             mov eax,esi
00470EE8 8B18             mov ebx,[eax]
00470EEA FF530C           call dword ptr [ebx+$0c]

<强>更新

以上代码由Delphi 2009编译器生成。您可以使用{$ STRINGCHECKS OFF}指令改进代码,但仍然有UniqueStringU函数调用开销:

Unit7.pas.100: Stream.Read(s[1], 10);
00470EB8 8D45FC           lea eax,[ebp-$04]
00470EBB E8B850F9FF       call @UniqueStringU
00470EC0 8BD0             mov edx,eax
00470EC2 B90A000000       mov ecx,$0000000a
00470EC7 8BC3             mov eax,ebx
00470EC9 8B18             mov ebx,[eax]
00470ECB FF530C           call dword ptr [ebx+$0c]

答案 3 :(得分:6)

第二个选项肯定更像是“Delphi风格”(如果你看一下Windows API头文件的Delphi版本,你会看到大多数指针参数已经转换为var参数)。

除此之外,第二个选项不需要强制转换,而且可读性更高。

答案 4 :(得分:5)

我总是使用第二个保持类型安全的。我真的没有购买性能参数,因为你最糟糕的情况是打磁盘,或文件缓存或主内存,所有这些都会使一些CPU操作看起来有些微不足道。正确性应优先于绩效。

但是,我想补充一点,这不应该让你烦恼太多,因为你应该只编写一次这段特殊的代码。把它放在一个帮助器类中并将其包好。随意关注优化,将其重新编写为汇编程序,无论您喜欢什么。但 d on r epeat y 我们自己。

答案 5 :(得分:3)

如果有可能使用Count 0来调用您的函数,那么A)将使用Pointer(s)^简单地评估为nil,而B)将因范围检查异常而崩溃

如果你想使用B)并仍然优雅地处理0,你应该使用:

function TMyStream.ReadChars(out s: AnsiString; const Count: Integer): Boolean; 
begin
 SetLength(s, Count);
 Result := (Count = 0)  or (Read(s[1], Count) = Count);
end;

答案 6 :(得分:1)

第二个(DiskStream.Read(s [1],Count))。无论何时遇到无类型的var参数,它都会读取“将传递的内容作为参数的地址”。所以在这种情况下,你传递字符串s的第一个字符的地址,这是你打算做的。