Mir

Merge lp:~vanvugt/mir/fix-r1049-regressions into lp:mir

Proposed by Daniel van Vugt
Status: Merged
Approved by: Thomas Voß
Approved revision: no longer in the source branch.
Merged at revision: 1156
Proposed branch: lp:~vanvugt/mir/fix-r1049-regressions
Merge into: lp:mir
Diff against target: 311 lines (+100/-33)
3 files modified
src/server/compositor/switching_bundle.cpp (+30/-16)
tests/mir_test_framework/testing_server_options.cpp (+1/-3)
tests/unit-tests/compositor/test_switching_bundle.cpp (+69/-14)
To merge this branch: bzr merge lp:~vanvugt/mir/fix-r1049-regressions
Reviewer Review Type Date Requested Status
Kevin DuBois (community) Approve
Alexandros Frantzis (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+191776@code.launchpad.net

Commit message

Fix significant performance issues LP: #1241369 / LP: #1241371, and probably
more(!)

Added regression test to catch such regressions and revert the offending
commit r1049.

Description of the change

The problem was the new algorithm in r1049, in keeping one buffer always allocated to compositing, did not have room in the queue to properly pipeline subsequent client buffer allocations. So clients got much less frame time than they often need and some of them dropped down to half frame rate.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

seems the test would take at least 1s to run (because of the sleep in 236) could we make it run faster?

review: Needs Information
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

The test loses accuracy if you shorten it. So for the sake of a second or so I'd rather keep it how it is.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

296 + sync.lock();
297 + sync.unlock();

?

review: Needs Information
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

The weird-looking sync.lock/unlock() calls help to create exactly the right (wrong) timing conditions between the two threads necessary to reproduce the problem reliably (and so fails without the fix). Otherwise it's surprisingly difficult to reproduce.

Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

OK. Hopefully we can find a way to restore the strong guarantee of the compositor always having a buffer without adversely affecting performance.

review: Approve
Revision history for this message
Kevin DuBois (kdub) wrote :

+1 on Alexandros's comment.

I'd still rather have a shorter-running test, but I'd rather have fewer bugs/more performance...
This algorithm has gotten away from deep comprehension on my part, but looks good to me.

review: Approve
Revision history for this message
Kevin DuBois (kdub) wrote :

> +1 on Alexandros's comment.
>
> I'd still rather have a shorter-running test, but I'd prefer fewer
> bugs/more performance more than I want a shorter running test...

> This algorithm has gotten away from deep comprehension on my part, but looks
> good to me.

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

The test is designed to simulate the root cause (which is subtle and apparently not common enough for us to notice for quite a while).

The root cause is that in designating one buffer to compositor, even when it's not being used, that buffer is unavailable to become a client buffer even while the client has a "ready" buffer queued up (but the compositor hasn't yet ticked over and asked for the next compositor buffer). The result is that clients have to wait longer to acquire a buffer, and for slow-ish clients, this can mean missing the frame deadline. And it happens repeatedly, so you stay at half frame rate.

I started with a naive fix, to simply promote any ready buffer to "compositor" if there were no "used" compositor buffers at the time. As a fix that worked, but it also results in many clients continuously skipping a buffer and thus rendering at double frame rate: 120Hz.

With additional logic and complexity, it would be possible to reinstate the current algorithm. But I figure the pre-1049 algorithm is better tested and already known to provide correct sync.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/server/compositor/switching_bundle.cpp'
2--- src/server/compositor/switching_bundle.cpp 2013-09-02 09:20:16 +0000
3+++ src/server/compositor/switching_bundle.cpp 2013-10-21 08:54:23 +0000
4@@ -28,7 +28,7 @@
5 *
6 * The successive stages are contiguous elements in the ring (starting at
7 * element "first_compositor"):
8- * first_compositor * ncompositors(one or more)
9+ * first_compositor * ncompositors(zero or more)
10 * first_ready * nready(zero or more)
11 * first_client * nclients(zero or more)
12 *
13@@ -72,18 +72,18 @@
14 : bundle_properties{property_request},
15 gralloc{gralloc},
16 nbuffers{nbuffers},
17- first_compositor{0}, ncompositors{1},
18- first_ready{first_compositor + ncompositors}, nready{0},
19- first_client{first_ready + nready}, nclients{0},
20+ first_compositor{0}, ncompositors{0},
21+ first_ready{0}, nready{0},
22+ first_client{0}, nclients{0},
23 snapshot{-1}, nsnapshotters{0},
24 last_consumed{0},
25 overlapping_compositors{false},
26 framedropping{false}, force_drop{0}
27 {
28- if (nbuffers < 2 || nbuffers > MAX_NBUFFERS)
29+ if (nbuffers < 1 || nbuffers > MAX_NBUFFERS)
30 {
31 BOOST_THROW_EXCEPTION(std::logic_error("SwitchingBundle only supports "
32- "nbuffers between 2 and " +
33+ "nbuffers between 1 and " +
34 std::to_string(MAX_NBUFFERS)));
35 }
36 }
37@@ -264,9 +264,29 @@
38 std::unique_lock<std::mutex> lock(guard);
39 int compositor;
40
41- if (!nready || frameno == last_consumed)
42+ // Multi-monitor acquires close to each other get the same frame:
43+ bool same_frame = (frameno == last_consumed);
44+
45+ int avail = nfree();
46+ bool can_recycle = ncompositors || avail;
47+
48+ if (!nready || (same_frame && can_recycle))
49 {
50- compositor = last_compositor();
51+ if (ncompositors)
52+ {
53+ compositor = last_compositor();
54+ }
55+ else if (avail)
56+ {
57+ first_compositor = prev(first_compositor);
58+ compositor = first_compositor;
59+ ncompositors++;
60+ }
61+ else
62+ {
63+ BOOST_THROW_EXCEPTION(std::logic_error(
64+ "compositor_acquire would block; probably too many clients."));
65+ }
66 }
67 else
68 {
69@@ -275,12 +295,6 @@
70 nready--;
71 ncompositors++;
72 last_consumed = frameno;
73-
74- while (ncompositors > 1 && !ring[first_compositor].users)
75- {
76- first_compositor = next(first_compositor);
77- ncompositors--;
78- }
79 }
80
81 overlapping_compositors = (ncompositors > 1);
82@@ -303,7 +317,7 @@
83 }
84 }
85
86- if (compositor < 0 || ring[compositor].users <= 0)
87+ if (compositor < 0)
88 {
89 BOOST_THROW_EXCEPTION(std::logic_error(
90 "compositor_release given a non-compositor buffer"));
91@@ -312,7 +326,7 @@
92 ring[compositor].users--;
93 if (compositor == first_compositor)
94 {
95- while (ncompositors > 1 && !ring[first_compositor].users)
96+ while (!ring[first_compositor].users && ncompositors)
97 {
98 first_compositor = next(first_compositor);
99 ncompositors--;
100
101=== modified file 'tests/mir_test_framework/testing_server_options.cpp'
102--- tests/mir_test_framework/testing_server_options.cpp 2013-10-15 08:53:10 +0000
103+++ tests/mir_test_framework/testing_server_options.cpp 2013-10-21 08:54:23 +0000
104@@ -211,12 +211,10 @@
105 mc::CompositingCriteria const&, ms::BufferStream& stream)
106 {
107 // Need to acquire the texture to cycle buffers
108- stream.lock_compositor_buffer(++frameno);
109+ stream.lock_compositor_buffer(0);
110 }
111
112 void clear(unsigned long) override {}
113-
114- unsigned long frameno = 0;
115 };
116
117 class StubRendererFactory : public mc::RendererFactory
118
119=== modified file 'tests/unit-tests/compositor/test_switching_bundle.cpp'
120--- tests/unit-tests/compositor/test_switching_bundle.cpp 2013-09-17 13:21:10 +0000
121+++ tests/unit-tests/compositor/test_switching_bundle.cpp 2013-10-21 08:54:23 +0000
122@@ -24,6 +24,7 @@
123
124 #include <atomic>
125 #include <thread>
126+#include <mutex>
127 #include <chrono>
128
129 namespace geom=mir::geometry;
130@@ -57,7 +58,7 @@
131 geom::PixelFormat::argb_8888,
132 mg::BufferUsage::software};
133
134- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
135+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
136 {
137 mc::SwitchingBundle bundle(nbuffers, allocator, properties);
138
139@@ -91,7 +92,7 @@
140
141 TEST_F(SwitchingBundleTest, client_acquire_basic)
142 {
143- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
144+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
145 {
146 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
147
148@@ -115,7 +116,7 @@
149
150 TEST_F(SwitchingBundleTest, is_really_synchronous)
151 {
152- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
153+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
154 {
155 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
156 mg::BufferID prev_id, prev_prev_id;
157@@ -204,7 +205,7 @@
158
159 TEST_F(SwitchingBundleTest, out_of_order_client_release)
160 {
161- for (int nbuffers = 3; nbuffers <= 5; nbuffers++)
162+ for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
163 {
164 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
165
166@@ -225,7 +226,7 @@
167
168 TEST_F(SwitchingBundleTest, compositor_acquire_basic)
169 {
170- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
171+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
172 {
173 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
174 unsigned long frameno = 0;
175@@ -246,7 +247,7 @@
176
177 TEST_F(SwitchingBundleTest, compositor_acquire_never_blocks)
178 {
179- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
180+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
181 {
182 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
183 unsigned long frameno = 0;
184@@ -265,7 +266,7 @@
185
186 TEST_F(SwitchingBundleTest, compositor_acquire_recycles_latest_ready_buffer)
187 {
188- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
189+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
190 {
191 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
192 unsigned long frameno = 1;
193@@ -354,7 +355,7 @@
194
195 TEST_F(SwitchingBundleTest, snapshot_acquire_basic)
196 {
197- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
198+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
199 {
200 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
201
202@@ -368,7 +369,7 @@
203
204 TEST_F(SwitchingBundleTest, snapshot_acquire_never_blocks)
205 {
206- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
207+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
208 {
209 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
210 const int N = 100;
211@@ -511,7 +512,7 @@
212 * unique ones while it makes sense to do so. Buffers are big things and
213 * should be allocated sparingly...
214 */
215- for (int nbuffers = 2; nbuffers <= 5; nbuffers++)
216+ for (int nbuffers = 1; nbuffers <= 5; nbuffers++)
217 {
218 mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
219 unsigned long frameno = 0;
220@@ -581,7 +582,7 @@
221 }
222 }
223
224-TEST_F(SwitchingBundleTest, framedropping_clients_get_all_but_one_buffers)
225+TEST_F(SwitchingBundleTest, framedropping_clients_get_all_buffers)
226 {
227 const int max_nbuffers = 5;
228 for (int nbuffers = 2; nbuffers <= max_nbuffers; nbuffers++)
229@@ -594,7 +595,7 @@
230 mg::BufferID expect[max_nbuffers];
231 std::shared_ptr<mg::Buffer> buf[max_nbuffers];
232
233- for (int b = 0; b < nbuffers - 1; b++)
234+ for (int b = 0; b < nbuffers; b++)
235 {
236 buf[b] = bundle.client_acquire();
237 expect[b] = buf[b]->id();
238@@ -603,13 +604,13 @@
239 ASSERT_NE(expect[p], expect[b]);
240 }
241
242- for (int b = 0; b < nbuffers - 1; b++)
243+ for (int b = 0; b < nbuffers; b++)
244 bundle.client_release(buf[b]);
245
246 for (int frame = 0; frame < nframes; frame++)
247 {
248 auto client = bundle.client_acquire();
249- ASSERT_EQ(expect[frame % (nbuffers - 1)], client->id());
250+ ASSERT_EQ(expect[frame % nbuffers], client->id());
251 bundle.client_release(client);
252 }
253 }
254@@ -699,3 +700,57 @@
255 }
256 }
257
258+TEST_F(SwitchingBundleTest, slow_client_framerate_matches_compositor)
259+{ // Regression test LP: #1241369 / LP: #1241371
260+ for (int nbuffers = 2; nbuffers <= 3; nbuffers++)
261+ {
262+ mc::SwitchingBundle bundle(nbuffers, allocator, basic_properties);
263+ unsigned long client_frames = 0;
264+ const unsigned long compose_frames = 100;
265+ const auto frame_time = std::chrono::milliseconds(16);
266+
267+ bundle.allow_framedropping(false);
268+
269+ std::atomic<bool> done(false);
270+ std::mutex sync;
271+
272+ std::thread monitor1([&]
273+ {
274+ for (unsigned long frame = 0; frame != compose_frames+3; frame++)
275+ {
276+ std::this_thread::sleep_for(frame_time);
277+ sync.lock();
278+ auto buf = bundle.compositor_acquire(frame);
279+ bundle.compositor_release(buf);
280+ sync.unlock();
281+
282+ if (frame == compose_frames)
283+ {
284+ // Tell the "client" to stop after compose_frames, but
285+ // don't stop rendering immediately to avoid blocking
286+ // if we rendered any twice
287+ done.store(true);
288+ }
289+ }
290+ });
291+
292+ bundle.client_release(bundle.client_acquire());
293+
294+ while (!done.load())
295+ {
296+ sync.lock();
297+ sync.unlock();
298+ auto buf = bundle.client_acquire();
299+ std::this_thread::sleep_for(frame_time);
300+ bundle.client_release(buf);
301+ client_frames++;
302+ }
303+
304+ monitor1.join();
305+
306+ // Roughly compose_frames == client_frames within 20%
307+ ASSERT_GT(client_frames, compose_frames * 0.8f);
308+ ASSERT_LT(client_frames, compose_frames * 1.2f);
309+ }
310+}
311+

Subscribers

People subscribed via source and target branches