📄 wwwtc2answer.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>answer What's wrong with this code? Volume #2</TITLE>
<META content="text/html; charset=gb2312" http-equiv=Content-Type>
<META content="Harold Howe" name=Author>
<META content="MSHTML 5.00.2614.3500" name=GENERATOR></HEAD>
<BODY>
<CENTER>
<TABLE border=0 cellPadding=0 cellSpacing=0 width=640>
<TBODY>
<TR>
<TD>
<H2>Answer What's wrong with this code? Volume #2 </H2>
<H4>A benchmark gone bad </H4>
<P>Before we begin, let's post the code for the <TT>ClassNameIs</TT>
routine again. </P><PRE><FONT color=navy>// Benchmark for ClassNameIs</FONT>
<B>void</B> <B>__fastcall</B> TForm1<B>:</B><B>:</B>Button2Click<B>(</B>TObject <B>*</B>Sender<B>)</B>
<B>{</B>
<B>int</B> nCount <B>=</B> ComponentCount<B>;</B>
DWORD dwStart <B>=</B> GetTickCount<B>(</B><B>)</B><B>;</B>
<B>for</B> <B>(</B><B>int</B> j<B>=</B><FONT color=blue>0</FONT><B>;</B> j<<FONT color=blue>1000000</FONT><B>;</B> j<B>++</B><B>)</B>
<B>{</B>
<B>for</B> <B>(</B><B>int</B> i<B>=</B><FONT color=blue>0</FONT><B>;</B> i<nCount<B>;</B> i<B>++</B><B>)</B>
<B>{</B>
<B>if</B><B>(</B>Components<B>[</B>i<B>]</B><B>-></B>ClassNameIs<B>(</B><FONT color=blue>"TEdit"</FONT><B>)</B><B>)</B>
<B>(</B><B>(</B>TEdit <B>*</B><B>)</B>Components<B>[</B>i<B>]</B><B>)</B><B>-></B>Color <B>=</B> clBtnFace<B>;</B>
<B>}</B>
<B>}</B>
DWORD dwEnd <B>=</B> GetTickCount<B>(</B><B>)</B><B>;</B>
DWORD dwDiff <B>=</B> dwEnd <B>-</B> dwStart<B>;</B>
Label2<B>-></B>Caption <B>=</B> IntToStr<B>(</B>dwDiff<B>)</B><B>;</B>
<B>}</B>
</PRE>
<P>The flaw in this benchmark is the way that we call the
<TT>ClassNameIs</TT> function. <TT>ClassNameIs</TT> takes an
<TT>AnsiString</TT> object by value as its only argument. The benchmark
code passes <TT>char *</TT>. The compiler inserts code to construct a
temporary <TT>AnsiString</TT> object from the <TT>char *</TT>. The
temporary is constructed by using the <TT>char *</TT> conversion
constructor of <TT>AnsiString</TT>. </P><PRE><FONT color=navy>// inside class declaration for AnsiString</FONT>
<B>__fastcall</B> AnsiString<B>(</B><B>const</B> <B>char</B><B>*</B> src<B>)</B><B>;</B>
</PRE>
<P>This constructor works by allocating a buffer large enough to hold the
string. It then calls the equivalent of <TT>strcpy</TT> to copy the string
from the source. If the code for this constructor was written in C++, it
might look something like this: <PRE><B>__fastcall</B> AnsiString<B>:</B><B>:</B>AnsiString<B>(</B><B>const</B> <B>char</B><B>*</B> str<B>)</B>
<B>{</B>
Data <B>=</B> <B>new</B> <B>char</B><B>[</B>strlen<B>(</B><B>char</B><B>)</B> <B>+</B> <FONT color=blue>1</FONT><B>]</B><B>;</B>
strcpy<B>(</B>Data<B>,</B>str<B>)</B><B>;</B>
refcnt <B>=</B> <FONT color=blue>1</FONT><B>;</B>
<B>}</B>
</PRE>
<P>Every time we pass a <TT>char *</TT> to <TT>ClassNameIs</TT>, this
constructor runs. Since we call <TT>ClassNameIs</TT> 16 million times in
the benchmark, we also call <TT>new</TT>, <TT>strcpy</TT>, and
<TT>strlen</TT> 16 million times. The overhead of this constructor affects
the benchmark. We are not just benchmarking <TT>ClassNameIs</TT>, we are
also benchmarking the string manipulation and the memory allocation
routines. </P>
<P>If we eliminate the overhead of the memory allocation and string
routines, we can obtain a more accurate benchmark for
<TT>ClassNameIs</TT>. We accomplish this by creating an
<TT>AnsiString</TT> constant and passing the contant to
<TT>ClassNameIs</TT>. The new code lookes like this. </P><PRE><B>void</B> <B>__fastcall</B> TForm1<B>:</B><B>:</B>Button2Click<B>(</B>TObject <B>*</B>Sender<B>)</B>
<B>{</B>
<B>const</B> AnsiString strEdit<B>(</B><FONT color=blue>"TEdit"</FONT><B>)</B><B>;</B>
<B>int</B> nCount <B>=</B> ComponentCount<B>;</B>
DWORD dwStart <B>=</B> GetTickCount<B>(</B><B>)</B><B>;</B>
<B>for</B> <B>(</B><B>int</B> j<B>=</B><FONT color=blue>0</FONT><B>;</B> j<<FONT color=blue>1000000</FONT><B>;</B> j<B>++</B><B>)</B>
<B>{</B>
<B>for</B> <B>(</B><B>int</B> i<B>=</B><FONT color=blue>0</FONT><B>;</B> i<nCount<B>;</B> i<B>++</B><B>)</B>
<B>{</B>
<B>if</B><B>(</B>Components<B>[</B>i<B>]</B><B>-></B>ClassNameIs<B>(</B>strEdit<B>)</B><B>)</B>
<B>(</B><B>(</B>TEdit <B>*</B><B>)</B>Components<B>[</B>i<B>]</B><B>)</B><B>-></B>Color <B>=</B> clBtnFace<B>;</B>
<B>}</B>
<B>}</B>
DWORD dwEnd <B>=</B> GetTickCount<B>(</B><B>)</B><B>;</B>
DWORD dwDiff <B>=</B> dwEnd <B>-</B> dwStart<B>;</B>
Label2<B>-></B>Caption <B>=</B> IntToStr<B>(</B>dwDiff<B>)</B><B>;</B>
<B>}</B>
</PRE>
<P>So what does this accomplish? Instead of calling the conversion
constructor for <TT>char *</TT>, the improved code calls the copy
constructor to initialize the temporary <TT>AnsiString</TT> that is passed
to <TT>ClassNameIs</TT>. Is the copy constructor any better than the
conversion constructor for <TT>char *</TT>? As it turns out, yes, it is
much better. Remember that the <TT>AnsiString</TT> class employs a
reference counting scheme. When you create an <TT>AnsiString</TT> object
using the copy constructor, the VCL simply copies the internal
<TT>Data</TT> pointer of the source object, and increments its reference
count. This is much faster than allocating new memory and copying the
string. </P>
<P>The <TT>AnsiString</TT> copy constructor works effectively like this:
</P><PRE><B>__fastcall</B> AnsiString<B>:</B><B>:</B>AnsiString<B>(</B><B>const</B> AnsiString <B>&</B>rhs<B>)</B>
<B>{</B>
Data <B>=</B> rhs<B>.</B>Data<B>;</B>
refcnt <B>=</B> <B>++</B>rhs<B>.</B>refcnt<B>;</B>
<B>}</B>
</PRE>
<P>This constructor is much faster than the conversion constructor. When
we use this constructor, we get a more accurate portrayal of how well
<TT>ClassNameIs</TT> runs. After making this change, the result for
<TT>ClassNameIs</TT> dropped from 67,000 milli-seconds to 29,000
milli-seconds. Using the <TT>AnsiString</TT> constant cut the time by more
than half. </P>
<P>So now we have to ask the question, which benchmark accurately reflects
the perforamance of <TT>ClassNameIs</TT>? If you are going to pass
<TT>char *</TT> arguments to <TT>ClassNameIs</TT>, then the first
benchmark is better because it includes the overhead of creating the
<TT>AnsiString</TT> temporary using the conversion constructor. This
overhead is a real factor when you pass <TT>char *</TT> arguments to
<TT>ClassNameIs</TT>. Another thing to keep in mind is that creating the
<TT>AnsiString</TT> constant requires some overhead too, because the
constant is created with the <TT>char *</TT> conversion constructor. If
you only need to call <TT>ClassNameIs</TT> one time, then there is no
point messing with a constant. If you do need to call <TT>ClassNameIs</TT>
dozens of times, and you are going to be passing the same class name each
time, then it makes sense to create a constant instead of passing a
<TT>char *</TT>. </P>
<P>In reality, you probably won't need to call <TT>ClassNameIs</TT> very
much anyway since <TT>dynamic_cast</TT> and <TT>__classid</TT> beat it so
convincingly. There really isn't much point using <TT>ClassNameIs</TT>
when the other two options work so much more efficiently. Nonetheless, the
lesson from this benchmark is one worth remembering. Be aware of temporary
objects and how much processor time is spent creating and destroying them.
</P>
<P><B>Note:</B> The <TT>AnsiString</TT> constructor code that is listed in
this edition is C++ pseudo-code. The <TT>AnsiString</TT> class does not
use <TT>new</TT>, <TT>strlen</TT>, and <TT>strcpy</TT>. It uses the pascal
equivalents. Also, <TT>refcnt</TT> is not a member variable of the class.
The reference count is maintained by pascal code in <TT>SYSTEM.PAS</TT>.
</P>
<P><B>Note:</B> The <TT>ClassNameIs</TT> function takes a <TT>const
AnsiString</TT> object by value. The function would be more effecient if
it took a reference instead of an object by value. I would guess that
passing the object by value has something to do with the fact that the
underlying code is in pascal.
</P></TD></TR></TBODY></TABLE></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -