📄 ch20.htm
字号:
To put this all into perspective, imagine if programmers had always
resorted to optimizing the traditional bubble sort algorithm and
had never thought twice about the algorithm itself. The quick
sort algorithm, which is orders of magnitude faster than bubble
sort without any optimization, would never have come about.
<H3><A NAME="UseNativeMethods"><B>Use Native Methods</B></A></H3>
<P>
I kind of hate to recommend them, but the truth is that native
methods (methods written in C or C++ that can be called from Java
code) are typically much faster than Java methods. The reason
I'm reluctant to promote their use is that they blow the platform
independence benefit of using Java, therefore limiting your game
to a particular platform. If platform independence isn't high
on your list, however, by all means look into rewriting problem
methods in C.
<H3><A NAME="UseInlineMethods"><B>Use Inline Methods</B></A></H3>
<P>
Inline methods, whose bodies appear in place of each method call,
are a fairly effective means of improving performance. Because
the Java compiler already inlines final, static, and private methods
when you have the optimization switch turned on, your best bet
is to try to make as many methods as possible final, static, or
private. If this isn't possible and you still want the benefits
of inlined code, you can always inline methods by hand: just paste
the body of the method at each place where it is called. This
is one of those cases in which you are sacrificing both maintainability
and size for speed. The things we do for speed!
<H3><A NAME="ReplaceSlowJavaAPIClassesandMethod"><B>Replace Slow
Java API Classes and Methods</B></A></H3>
<P>
There might be times when you are using a standard Java API class
for a few of its features, but the extra baggage imposed by the
class is slowing you down. In situations like this, you might
be better off writing your own class that performs the exact functionality
you need and no more. This streamlined approach can pay off big,
even though it comes at the cost of rewriting code.
<P>
Another similar situation is when you are using a Java API class
and you isolate a particular method in it that is dragging down
performance. In this situation, instead of rewriting the entire
class, just derive from it and override the troublesome method.
This is a good middle-of-the-road solution because you leverage
code reuse against performance in a reasonable manner.
<H3><A NAME="UseLookUpTables"><B>Use Look-Up Tables</B></A></H3>
<P>
An established trick up the sleeve of every experienced game programmer
is the look-up table. Look-up tables are tables of constant integer
values that are used in place of time-consuming calculations.
For example, a very popular type of look-up table is one containing
values for trigonometric functions, such as sine. The use of trigonometric
functions is a necessity when you are working with rotational
objects in games. If you haven't noticed, trigonometric functions
are all floating-point in nature, which is a bad thing. The solution
is to write an integer version of the desired function using a
look-up table of values. This relatively simple change is practically
a necessity considering the performance hit you take by using
floating-point math.
<H3><A NAME="EliminateUnnecessaryEvaluations"><B>Eliminate Unnecessary
Evaluations</B></A></H3>
<P>
Moving along into more detailed optimizations, you can often find
unnecessary evaluations in your code that are serving only to
eat up extra processor time. The following is an example of some
code that unnecessarily performs an evaluation that acts effectively
as a constant:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">for (int i = 0; i < size(); i++)<BR>
a = (b + c) / i;</FONT></TT>
</BLOCKQUOTE>
<P>
The addition of <TT><FONT FACE="Courier">b + c</FONT></TT>, although
itself a pretty efficient piece of code, is better off being calculated
before the loop, like this:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">int tmp = b + c;<BR>
for (int i = 0; i < size(); i++)<BR>
a = tmp / i;</FONT></TT>
</BLOCKQUOTE>
<P>
This simple change could have fairly dramatic effects, depending
on how many times the loop is iterated. Speaking of the loop,
there's another optimization you might have missed. Notice that
<TT><FONT FACE="Courier">size()</FONT></TT> is a method call,
which might bring to mind the costs involved in calling a method
that you learned about earlier today. You might not realize it,
but <TT><FONT FACE="Courier">size()</FONT></TT> is being called
every time through the loop as part of the conditional loop expression.
The same technique used to eliminate the unnecessary addition
operation can be used to fix this problem. Check out the resulting
code:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">int s = size;<BR>
int tmp = b + c;<BR>
for (int i = 0; i < s; i++)<BR>
a = tmp / i;</FONT></TT>
</BLOCKQUOTE>
<H3><A NAME="EliminateCommonSubexpressions"><B>Eliminate Common
Subexpressions</B></A></H3>
<P>
Sometimes you might be reusing a costly subexpression without
even realizing it. In the heat of programming, it's easy to reuse
common subexpressions instead of storing them in a temporary variable,
like this:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">b = Math.abs(a) * c;<BR>
d = e / (Math.abs(a) + b);</FONT></TT>
</BLOCKQUOTE>
<P>
The multiple calls to <TT><FONT FACE="Courier">Math.abs()</FONT></TT>
are costly compared to calling it once and using a temporary variable,
like this:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">int tmp = Math.abs(a);<BR>
b = tmp * c;<BR>
d = e / (tmp + b);</FONT></TT>
</BLOCKQUOTE>
<H3><A NAME="ExpandLoops"><B>Expand Loops</B></A></H3>
<P>
One optimization that is popular among C/C++ game programmers
is loop expansion, or loop unrolling, which is the process of
expanding a loop to get rid of the overhead involved in maintaining
the loop. You might be wondering exactly what overhead I'm talking
about. Well, even a simple counting loop has the overhead of performing
a comparison and an increment each time through. This might not
seem like much, but with game programming you could well end up
in the position of clawing for anything you can get!
<P>
<I>Loop expansion</I>, or <I>loop unrolling</I>, is the process
of expanding a loop to get rid of the inherent overhead involved
in maintaining the loop.
<P>
Loop expansion basically involves replacing a loop with the brute-force
equivalent. To better understand it, check out the following piece
of code:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">for (int i = 0; i < 1000; i++)<BR>
a[i] = 25;</FONT></TT>
</BLOCKQUOTE>
<P>
That probably looks like some pretty efficient code, and in fact
it is. But if you want to go the extra distance and perform a
loop expansion on it, here's one approach:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">int i = 0;<BR>
for (int j = 0; j < 100; j++) {<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
a[i++] = 25;<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
In this code, you've reduced the loop overhead by an order of
magnitude, but you've introduced some new overhead by having to
increment the new index variable inside the loop. Overall, this
code does outperform the original code, but don't expect any miracles.
Loop expansion can be effective at times, but I don't recommend
placing it too high on your list of optimization tricks.
<H2><A NAME="Summary"><B><FONT SIZE=5 COLOR=#FF0000>Summary</FONT></B></A>
</H2>
<P>
Today you learned about a somewhat murky area of Java game development:
code optimization. You began the lesson by learning about the
fundamental types of optimization, including the type that game
programmers are mostly concerned with: speed optimization. You
then learned about the optimizations (or lack thereof) provided
by the JDK compiler. From there, you got a little dose of realism
by looking into the timing costs of common Java operations. Finally,
you finished off the lesson with an in-depth look at some practical
code optimizations you can apply to your own games.
<P>
Incidentally, after going through today's lesson, you might be
wondering how well the sample code throughout the book is optimized.
I'm sorry to report that it is optimized very little, mainly for
the sake of keeping it easier to follow. It ends up that optimized
code is often much harder to understand, so I opted to err on
the side of clarity. Now that you're disillusioned with my coding
practices, prepare to turn your attention toward tomorrow's lesson,
which is putting together a Java game programming toolkit.
<H2><A NAME="QA"><B><FONT SIZE=5 COLOR=#FF0000>Q&A</FONT></B></A>
<BR>
</H2>
<TABLE>
<TR VALIGN=TOP><TD WIDTH=50><B>Q</B></TD><TD><B>Do all games require lots of code optimization to run at acceptable speeds?</B>
</TD></TR>
<TR VALIGN=TOP><TD WIDTH=50><B>A</B></TD><TD>No. First, many games simply aren't speed-intensive, which immediately eliminates the need for any optimization. Second, even those games that could benefit from optimization will often run at reasonable speeds
without it. The sample games you've studied in this book are very good examples of this fact.
</TD></TR>
<TR VALIGN=TOP><TD WIDTH=50><B>Q</B></TD><TD><B>I keep hearing about just-in-time compilers. How will they impact the whole optimization issue?</B>
</TD></TR>
<TR VALIGN=TOP><TD WIDTH=50><B>A</B></TD><TD>Just-in-time compilers (Java compilers that turn bytecodes into platform-dependent code at runtime) are music to the ears of Java game programmers, because they will undoubtedly increase the speed of all Java
code by an order of magnitude. Even so, Java game programmers will likely use the new speeds afforded by just-in-time compilers to add more complexity to their games. When this happens, you will still be left optimizing your code. Our greed seems to keep
us from winning!
</TD></TR>
<TR VALIGN=TOP><TD WIDTH=50><B>Q</B></TD><TD><B>I really enjoy hacking through cryptic bytecodes; is there anything else I can do to speed up my Java code?</B>
</TD></TR>
<TR VALIGN=TOP><TD WIDTH=50><B>A</B></TD><TD>But of course, the Java class file disassembler is the tool for you. The disassembler (<TT><FONT FACE="Courier">javap</FONT></TT>) comes standard with the JDK, and it enables you to see the bytecodes generated
for a class. Just use the <TT><FONT FACE="Courier">-c</FONT></TT> option, and you'll get complete bytecode listings for each method. You can then use these listings to study the intricate results of your source code optimizations.
</TD></TR>
</TABLE>
<P>
<H2><A NAME="Workshop"><B><FONT SIZE=5 COLOR=#FF0000>Workshop</FONT></B></A>
</H2>
<P>
The Workshop section provides questions and exercises to help
you with the material you learned today. Try to answer the questions
and at least go over the exercises before moving on to tomorrow's
lesson. You'll find the answers to the questions in appendix A,
"Quiz Answers."
<H3><A NAME="Quiz"><B>Quiz</B></A></H3>
<OL>
<LI>What are the three major areas of code optimization?
<LI>What type of speed optimization does the JDK compiler perform?
<LI>What is the significance of using a profiler?
<LI>When should you use a look-up table?
</OL>
<H3><A NAME="Exercises"><B>Exercises</B></A></H3>
<OL>
<LI>Run the Java profiler on the Traveling Gecko sample game and
see whether you can isolate any methods for performing optimizations.
<LI>Try your hand at making a few optimizations to Traveling Gecko;
then run the profiler again to see whether your changes helped.
Hint: There is an unnecessary evaluation in <TT><FONT FACE="Courier">SpriteVector::testCollision</FONT></TT>
just waiting for you to fix it.
</OL>
<P>
<HR WIDTH="100%"></P>
<CENTER><P><A HREF="ch19.htm"><IMG SRC="pc.gif" BORDER=0 HEIGHT=88 WIDTH=140></A><A HREF="index.htm"><IMG SRC="hb.gif" BORDER=0 HEIGHT=88 WIDTH=140></A><A HREF="#CONTENTS"><IMG SRC="cc.gif" BORDER=0 HEIGHT=88 WIDTH=140></A><A HREF="ch21.htm"><IMG
SRC="nc.gif" BORDER=0 HEIGHT=88 WIDTH=140></A></P></CENTER>
<P>
<HR WIDTH="100%"></P>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -