在一个从磁盘读取数据(数据意味着完全字符串)的函数中,我应该选择哪个?哪个更好?
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。
答案 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的第一个字符的地址,这是你打算做的。