指针^与s [1]

WeG*_*ars 19 delphi

在从磁盘读取数据(数据仅指字符串)的函数中,我应该更喜欢哪种?哪个更好?

A) DiskStream.Read(Pointer(s)^, Count)
or
B) DiskStream.Read(s[1], Count)
Run Code Online (Sandbox Code Playgroud)

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


UPDATE

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;
Run Code Online (Sandbox Code Playgroud)

速度测试

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

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

对于每种变体,测试重复15次.

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

ANSWER
Variant A - 可能是一点点微小的变种B - (显然)更容易阅读,它更像是Delphi-ish.我的首选.

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

Mas*_*ler 18

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

  • @Altar:原因主要是他们认为它更快.但正如您在测试时所看到的那样,性能提升可以忽略不计.在谈论速度时,一个好的经验法则是"除非用户注意到差异,否则它不会慢." 并且没有人会注意到20毫秒,特别是在总时间不到1.5秒的情况下. (4认同)

Arn*_*hez 13

运行时请注意

1. DiskStream.Read(Pointer(s)^, Count)
2. DiskStream.Read(s[1], Count)
Run Code Online (Sandbox Code Playgroud)

1.版本会更快.

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

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

实际上,AnsiString从内容中编码此读取的最快正确方法是:

  s := '';
  SetLength(s,Count);
  DiskStream.Read(pointer(s)^,Count);
Run Code Online (Sandbox Code Playgroud)

要么

  SetString(s,nil,Count);
  DiskStream.Read(pointer(s)^,Count);
Run Code Online (Sandbox Code Playgroud)

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

设置s为''将调用FreeMem()+AllocMem()而不是ReallocMem()in SetLength(),因此将避免调用,因此会move()更快一些.

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

  • +1我不相信性能确实是这里的问题,但我对于准确处理有关"UniqueString"的正确性问题给予了很多赞誉.对我来说,答案的复杂性是选择`s [1]`的完美理由.;-) (2认同)

klu*_*udg 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]
Run Code Online (Sandbox Code Playgroud)

UPDATE

上面的代码由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]
Run Code Online (Sandbox Code Playgroud)

  • @Altar,性能差异可以忽略不计.除非(量化的)性能增益证明降低了可读性,否则总是考虑性能的可读性.软件的真正"成本"是错误修复和维护 - 性能很少是真正的问题,而在几乎所有这些情况下,我们不只是谈论几个百分点. (2认同)
  • @Mason - 如果`DiskStream.Read(Pointer(s)^,Count)`是正确的,那么`UniqueString`就是开销.当然,如果你写'指针',你应该了解正在做什么,否则你可能有副作用. (2认同)

jpf*_*ius 6

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

除此之外,第二个选项不需要强制转换,并且更易读恕我直言.


Dav*_*nan 5

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

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

  • @Altar:如果你想真正加快光盘访问速度,不要通过这样的例程从光盘传输文件.相反,编写一个在TFileStream中打开文件的例程,然后将所有内容一次性读入TMemoryStream,并使用TMemoryStream进行加载.性能提升非常惊人.(当然,这可能会导致真正庞大的文件出现问题,你需要尝试更复杂的技巧.但对于大多数东西来说,这是一个非常好的,简单的速度提升.) (2认同)