-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPerformance.html
More file actions
340 lines (313 loc) · 19.5 KB
/
Performance.html
File metadata and controls
340 lines (313 loc) · 19.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
<!DOCTYPE html><html lang="de"><head>
<title>Variationen zum Thema: Android</title>
<meta name="title" content="Variationen zum Thema: Android">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta charset="UTF-8">
<meta name="description" content="Eine Einführung in mobile Anwendungen">
<meta name="keywords" content="Android,Java,Einführung,Mobile Anwendungen">
<meta name="author" content="Ralph P. Lano">
<meta name="robots" content="index,follow">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="book.css">
</head>
<body><center>
<div id="wrap">
<ul class="sidenav">
<p><a href="index.html">Variationen zum Thema</a><a href="index.html">Android</a></p>
<li><a href="Intro.html">Intro</a></li>
<li><a href="UI.html">UI</a></li>
<li><a href="Graphics.html">Graphics</a></li>
<li><a href="Persistence.html">Persistence</a></li>
<li><a href="Sensors.html">Sensors</a></li>
<li><a href="Threading.html">Concurrency</a></li>
<li><a href="Networking.html">Networking</a></li>
<li><a href="Multimedia.html">Multimedia</a></li>
<li><a href="Performance.html">Performance</a></li>
<li><a href="Library.html">Library</a></li>
<li><a href="Services.html">Services</a></li>
<li><a href="Cryptography.html">Cryptography</a></li>
<li><a href="Addenda.html">Addenda</a></li>
</ul>
<div class="content"><p>
<img src="images/ST1_Mandelbrot.png" style="display: block; margin-left: auto; margin-right: auto;width: 229px; height: 362px;" /></p>
<h1>
Special Topic: Graphics Performance</h1>
<p>
Eine Frage, die sich oft bei der Grafikprogrammierung stellt: geht's schneller? Obwohl es keine allgemeine Antwort gibt, wollen wir einige der zugrunde liegenden Regeln kennen lernen, die im Allgemeinen gültig sind. Dazu sehen wir uns vier verschiedenen Methoden an, wie man 50.000 GRects zeichnen kann.</p>
<p>
<img alt="" src="images/FastGRectActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />In allen vier Fällen verwenden wir die folgende Aktivität,</p>
<pre style="margin-left: 40px;">
public class FastGRectActivity extends Activity {
private final int SIZE = 40;
private final int DELAY = 40;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View gv = new <span style="color:#0000ff;">FastGRectView</span>(this);
setContentView(gv);
}
}
</pre>
<p>
Lediglich in den Views unterscheiden sie sich. Alle vier Views habe das gleiche Gerüst:</p>
<pre style="margin-left: 40px;">
class FastGRectView extends View {
private Paint paint;
private Random rgen = new Random();
public FastGRectView(Context context) {
super(context);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(1);
paint.setTextSize(48f);
}
@Override
protected void <span style="color:#0000ff;">onDraw</span>(Canvas canvas) {
long startTime = System.currentTimeMillis();
for (int k = 0; k < 50000; k++) {
...
}
long time = System.currentTimeMillis() - startTime;
paint.setColor(Color.WHITE);
canvas.drawRect(0, 0, 250, 70, paint);
paint.setColor(Color.BLACK);
canvas.drawText("time:" + time, 20, 50, paint);
}
}</pre>
<p>
Der Konstruktor initialisiert alle Instanzvariablen und das Zeichnen erfolgt in der onDraw() Methode. Um die Zeit zu messen, verwenden wir die Methode <em>System.currentTimeMillis()</em>. Generell sollte so wenig wie möglich in der onDraw() Methode passieren, denn das kostet ja Zeit. Deswegen haben wir das <em>Paint</em> Objekt bereits im Konstuktor erzeugt.</p>
<h3>
Reference Case: Simple Canvas Drawing Methods</h3>
<p>
Wir beginnen mit dem einfachsten Art und Weise Rechtecke zu zeichnen, mit der Methode <em>canvas.drawRect()</em>:</p>
<pre style="margin-left: 40px;">
for (int k = 0; k < 50000; k++) {
int w = rgen.nextInt(SIZE);
int x = rgen.nextInt(getWidth());
int y = rgen.nextInt(getHeight());
paint.setColor(rgen.nextInt());
canvas.drawRect(new RectF(x, y, x + w, y + w), paint);
}</pre>
<p>
Das ist unser Referenzpunkt. Auf meinem Motorola G4 benötigt dieser Code ca. 1570ms.</p>
<h3>
Simple Canvas Drawing with a GRect</h3>
<p>
Als erste Modifikation führen wir eine <em>GRect</em> Klasse ein.</p>
<pre style="margin-left: 40px;">
private class GRect {
protected int x, y, w, h;
private Paint paint;
public GRect(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLUE);
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
}
public void setColor(int color) {
paint.setColor(color);
}
public void <span style="color:#0000ff;">draw</span>(Canvas canvas) {
canvas.drawRect(new RectF(this.x, this.y, this.x + this.w, this.y + this.h), paint);
}
}</pre>
<p>
Diese Klasse verwendet Instanzvariablen für den Zustand und sie hat auch eine <em>draw()</em> Methode, die für das Zeichnen verantwortlich ist.</p>
<p>
In der View Klasse sieht die onDraw() Methode jetzt etwas komplizierter aus, aber nur, weil wir die Erzeugung der GRect-Objekte von dem Zeichnen trennen:</p>
<pre style="margin-left: 40px;">
protected void onDraw(Canvas canvas) {
<span style="color:#0000ff;">// seperate creation (this part takes about 1250ms)</span>
GRect[] rects = new GRect[50000];
for (int k = 0; k < 50000; k++) {
// create randomly sized rect
int w = rgen.nextInt(SIZE);
int x = rgen.nextInt(getWidth());
int y = rgen.nextInt(getHeight());
rects[k] = new GRect(x, y, w, w);
rects[k].setColor(rgen.nextInt());
}
<span style="color:#0000ff;">// from drawing (this part also takes about 1250ms)</span>
long startTime = System.currentTimeMillis();
for (int k = 0; k < 50000; k++) {
rects[k].draw(canvas);
}
...
}</pre>
<p>
Der Grund für diese Trennung ist, dass unter normalen Umständen die meisten Objekte nur einmal erstellen würden und dann nur gelegentlich ein paar hinzugefügt oder entfernt würden. Das hat aber nichts mit der Grafikleistung zu tun.</p>
<p>
Naiv würde man erwarten, dass dieser Ansatz deutlich langsamer sein müsste als unser erster Ansatz. Erstens, weil wir Objekte verwenden und die Erstellung von Objekten teuer ist. Zweitens, weil der Code komplizierter aussieht, was in der Regel mit Langsamkeit übersetzt wird. Aber das Gegenteil ist der Fall: Dieser Code benötigt nur 1250ms, d.h. er ist ca. 20% schneller. Wir lernen also, dass Objektorientierung, wenn richtig angewendet, zu einer Leistungssteigerung führen kann. (Der Grund liegt wahrscheinlich am Prozessor-Cache.)</p>
<h3>
Canvas Drawing using a Bitmap</h3>
<p>
Auf fast allen Rechnern sind die einfachen Array Operationen deutlich optimiert. Für die Grafikprogrammierung bedeutet das, dass man bei dem Arbeiten mit Bitmaps idealerweise mit dem zugrunde liegenden Pixel-Array arbeitet. Um das auszuprobieren schreiben wir unseren View etwas um:</p>
<pre style="margin-left: 40px;">
class FastGRectView extends View {
...
private int mCanvasWidth;
private int mCanvasHeight;
private Bitmap bitmap;
private int[] bitMapArray;
...
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (bitmap != null) {
bitmap.recycle();
}
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvasWidth = w;
mCanvasHeight = h;
bitMapArray = new int[mCanvasWidth * mCanvasHeight];
}
...
}</pre>
<p>
Wir deklarieren eine Bitmap und ihr Pixel-Array als Instanzvariablen. Zusätzlich benötigen wir die onSizeChanged() Methode, in der wir die Bitmap und das Array initialisieren.</p>
<p>
Die onDraw() Methode erführt folgende Änderungen:</p>
<pre style="margin-left: 40px;">
for (int k = 0; k < 50000; k++) {
int w = rgen.nextInt(SIZE);
int x = rgen.nextInt(getWidth() - SIZE);
int y = rgen.nextInt(getHeight() - SIZE);
<span style="color:#0000ff;">drawRect</span>(x, y, w, w, rgen.nextInt());
}
<span style="color:#0000ff;">bitmap.copyPixelsFromBuffer</span>(IntBuffer.wrap(bitMapArray));
<span style="color:#0000ff;">canvas.drawBitmap</span>(bitmap, 0, 0, null);</pre>
<p>
In der drawRect() Methode zeichnen wir in das Pixel-Array. Die Methode<em> bitmap.copyPixelsFromBuffer()</em> erlaubt es uns, aus dem Array eine Bitmap zu machen und dann mit der Methode drawBitmap() des Canvas die Bitmap auf dem Bildschirm zu zeichnen.</p>
<p>
Die drawRect() Methode ist ein wenig primitiv, da es sich um eine Manipulation auf Byte-Ebene handelt:</p>
<pre style="margin-left: 40px;">
private void drawRect(int x, int y, int w, int h, int color) {
int len = bitMapArray.length;
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
int idx = (y + j) * mCanvasWidth + (x + i);
if (idx < len) {
bitMapArray[(y + j) * mCanvasWidth + (x + i)] = color;
}
}
}
}</pre>
<p>
Und, wie lange dauert es? Nun, es dauert 740ms. Das ist doppelt so schnell wie unser ursprünglicher Ansatz.</p>
<h3>
Canvas Drawing using a Bitmap and a GRect</h3>
<p>
Können wir es noch besser machen? Betrachtet man unser vorheriges Beispiel, gibt es Hoffnung. Auch hier lagern wir unsere Datenhaltung und das Zeichnen in eine GRect Klasse aus:</p>
<pre style="margin-left: 40px;">
private class GRect {
protected int x, y, w, h;
private int color;
public GRect(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = Color.BLUE;
}
public void setColor(int color) {
this.color = color;
}
public void draw(int[] bitMapArray) {
int len = bitMapArray.length;
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
int idx = (y + j) * mCanvasWidth + (x + i);
if (idx < len) {
bitMapArray[(y + j) * mCanvasWidth + (x + i)] = color;
}
}
}
}
}</pre>
<p>
Wir stellen fest, dass sie fast identisch zu der vorherigen GRect-Klasse ist.</p>
<p>
Das gleiche gilt für die onDraw() Methode:</p>
<pre style="margin-left: 40px;">
...
// from drawing (this part also takes about 1250ms)
long startTime = System.currentTimeMillis();
for (int k = 0; k < 50000; k++) {
rects[k].draw(<span style="color:#0000ff;">bitMapArray</span>);
}
bitmap.copyPixelsFromBuffer(IntBuffer.wrap(bitMapArray));
// draw bitmap on canvas
canvas.<span style="color:#0000ff;">drawBitmap</span>(bitmap, 0, 0, null);
...
</pre>
<p>
Der einzige Unterschied ist, dass wir das Pixel-Array an die draw()-Methode der GRects übergeben und dass wir die drawBitmap() Methode des Canvas Objekts am Ende aufrufen.</p>
<p>
Ist es wirklich schneller? 490ms ist die Zeit, die es dauerte. Das ist dreimal schneller als unser ursprünglicher Ansatz! Wenn wir z.B. 25 Bilder pro Sekunde in unserem Spiel haben wollen, bedeutet das, dass wir ungefähr 2000 GRect Objekte pro Frame zeichnen können.</p>
<p>
Lassen Sie sich also nicht von Leuten täuschen, die sagen, dass Objekte oder die Objektorientierung langsam ist. Ist sie komplizierter? Nun, am Anfang sieht es so aus. Aber auch das hängt von der Verkapselung ab. Im nächsten Beispiel sehen wir, wie man all dies in eine Bibliothek umwandelt, die der ACM-Grafikbibliothek ähnelt. Versuchen Sie danach, ein Spiel wie BrickBreaker mit den beiden verschiedenen Ansätzen zu programmieren und treffen Sie dann ein Urteil.</p>
<p>
Noch ein Hinweis: In unserer Version der ACM-Grafikbibliothek haben wir den Bitmap-Ansatz nicht verwendet. Sie fragen sich vielleicht, warum? Nun, erstens, wenn wir deutlich weniger als 2000 Objekte zeichnen, vielleicht ein paar hundert, dann ist das Speichern nicht ganz so wichtig, weil die Methode bitmap.copyPixelsFromBuffer() etwa 10ms dauert. Zusätzlich bietet die Klasse Canvas viele praktische Methoden wie drawOval(), drawLines() und drawText(), um nur einige zu nennen. Die Implementierung all dieser Elemente in unserem Bitmap-Ansatz nimmt viel Entwicklungszeit in Anspruch. Aber wenn unsere Programme mit vielen tausend Objekten arbeiten müssten, dann würden wir mit Sicherheit auf den Bitmap-Ansatz zurückkommen.</p>
<p>
.</p>
<h2>
<img alt="" src="images/MandelbrotSlowActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />Mandelbrot</h2>
<p>
Auch Mandelbrot Fraktale haben wir schon im zweiten Buch gesehen. Dabei wird einfach die mathematische Gleichung<br />
<br />
z_n+1 = z_n * z_n + c<br />
<br />
grafisch dargestellt. Den Code können wir fast eins-zu-eins aus dem zweiten Semester übernehmen. Allerdings sollten wir vieleicht erst einmal in Vierer-Schritten durch die Iteration gehen, es stellt sich nämlich heraus, dass es gefühlt ewig dauert bis unser Bild fertig ist. </p>
<p>
Ähnlich wie beim GameOfLife Beispiel kann man das Ganze fast um den Faktor zehn beschleunigen, wenn man mit einem Array und einer Bitmap arbeitet, anstelle der drawPoint() oder drawRect() Methode der Canvas Klasse. Dazu definieren wir eine Bitmap und ein bitMapArray als Instanzvariablen:</p>
<pre style="margin-left: 40px;">
private Bitmap bitmap;
private int[] bitMapArray;</pre>
<p>
In der onSizeChanged() Methode initialisieren wir die beiden</p>
<pre style="margin-left: 40px;">
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (bitmap != null) {
bitmap.recycle();
}
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvasWidth = w;
mCanvasHeight = h;
bitMapArray = new int[mCanvasWidth * mCanvasHeight];
}</pre>
<p>
.</p>
<p>
<img alt="" src="images/MandelbrotFastActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />Und in der onDraw() Methode berechnen wir das Array, machen daraus dann eine Bitmap und zeichnen die Bitmap auf den Canvas:</p>
<pre style="margin-left: 40px;">
public void onDraw(Canvas canvas) {
double xStep = (xMax - xMin) / mCanvasWidth * 1;
double yStep = (yMax - yMin) / mCanvasHeight * 1;
for (double x = xMin; x < xMax; x += xStep) {
int i = (int) (((x - xMin) * mCanvasWidth) / (xMax - xMin));
for (double y = yMin; y < yMax; y += yStep) {
int j = (int) (((y - yMin) * mCanvasHeight) / (yMax - yMin));
bitMapArray[j * mCanvasWidth + i] = function(x, y);
}
}
bitmap.setPixels(bitMapArray, 0, mCanvasWidth, 0, 0, mCanvasWidth, mCanvasHeight);
canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()),
new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()), null);
}
</pre>
<p>
Das geht jetzt schon viel schneller, ca. ein Bild pro Sekunde.</p>
<p>
Geht es noch schneller? Die meisten Android CPUs haben inzwischen mehr als einen Core, Quadcores sind heute schon fast die Regel. Bisher haben wir aber in all unseren Programmen immer nur einen dieser Cores verwendet. Wenn wir es schaffen die anderen auch arbeiten zu lassen, dann können wir unser Programm noch mal um einiges schneller machen, je nachdem wie viele Cores unser Gerät hat. Wie das geht sehen wir im Kapitel zu Concurrency.</p>
<p>
.</p>
<p class="footer">
Copyright © 2016-2021 <a href="http://www.lano.de">Ralph P. Lano</a>. All rights reserved.
</p>
</div>
</center>
</div>
</body>
</html>