orbtk
orbtk copied to clipboard
Improve LayoutSystem performance
Context
Hello. When placing several widgets into a GridLayout, it becomes very slow, the interactive widgets, like a TextBox are unusable.
Problem description & Solution
I inserted this piece of code into GridLayout's measure() and arrange() methods start and end:
use std::time::Instant;
let start = Instant::now();
...
println!("Measure time: {:?}", end.duration_since(start));
Measurings (sorry for the long list, but it shows that GridLayout's algorithm isn't the most optimized):
Meaasure time: 6.1µs Meaasure time: 34.6µs Meaasure time: 667µs Meaasure time: 991.5µs Meaasure time: 6.5µs Meaasure time: 32.9µs Meaasure time: 10.3771ms Meaasure time: 140.5µs Meaasure time: 877.1µs Meaasure time: 93.4µs Meaasure time: 321.1µs Meaasure time: 12.7535ms Meaasure time: 6.8µs Meaasure time: 37µs Meaasure time: 674.2µs Meaasure time: 154.6µs Meaasure time: 347.1µs Meaasure time: 79.2µs Meaasure time: 269.3µs Meaasure time: 2.1839ms Meaasure time: 6.6µs Meaasure time: 33.2µs Meaasure time: 418.5µs Meaasure time: 89.7µs Meaasure time: 638.8µs Meaasure time: 80.9µs Meaasure time: 291.7µs Meaasure time: 1.9777ms Meaasure time: 6.4µs Meaasure time: 34.1µs Meaasure time: 836.7µs Meaasure time: 1.0659ms Meaasure time: 6.6µs Meaasure time: 34µs Meaasure time: 945.4µs Meaasure time: 94µs Meaasure time: 283.9µs Meaasure time: 80.5µs Meaasure time: 271.8µs Meaasure time: 2.4615ms Meaasure time: 6.5µs Meaasure time: 33.1µs Meaasure time: 415.8µs Meaasure time: 89.8µs Meaasure time: 7.9858ms Meaasure time: 110.7µs Meaasure time: 345.2µs Meaasure time: 9.3715ms Meaasure time: 6.4µs Meaasure time: 34.8µs Meaasure time: 778.9µs Meaasure time: 91.1µs Meaasure time: 284.4µs Meaasure time: 84.2µs Meaasure time: 592.6µs Meaasure time: 2.2717ms Meaasure time: 34.8722ms Meaasure time: 35.0655ms Meaasure time: 35.2823ms Arrange time: 28.7µs Arrange time: 105.5µs Arrange time: 636.6µs Arrange time: 1.5591ms Arrange time: 32.7µs Arrange time: 109.7µs Arrange time: 1.0041ms Arrange time: 225µs Arrange time: 472.9µs Arrange time: 221.8µs Arrange time: 466.2µs Arrange time: 2.9453ms Arrange time: 29.3µs Arrange time: 106.6µs Arrange time: 901.8µs Arrange time: 220.1µs Arrange time: 654.9µs Arrange time: 225.1µs Arrange time: 9.0352ms Arrange time: 11.5368ms Arrange time: 32.9µs Arrange time: 106.3µs Arrange time: 948.1µs Arrange time: 222.9µs Arrange time: 471.2µs Arrange time: 226.3µs Arrange time: 730.8µs Arrange time: 2.9365ms Arrange time: 29.1µs Arrange time: 108.6µs Arrange time: 940.3µs Arrange time: 8.3761ms Arrange time: 30.3µs Arrange time: 108.1µs Arrange time: 672.9µs Arrange time: 221.6µs Arrange time: 462µs Arrange time: 228.7µs Arrange time: 764.6µs Arrange time: 2.9634ms Arrange time: 30.2µs Arrange time: 106.6µs Arrange time: 930.8µs Arrange time: 222µs Arrange time: 461.4µs Arrange time: 266.2µs Arrange time: 8.0127ms Arrange time: 10.38ms Arrange time: 30.1µs Arrange time: 109.8µs Arrange time: 987.3µs Arrange time: 224.3µs Arrange time: 467.8µs Arrange time: 226.2µs Arrange time: 669.8µs Arrange time: 2.9048ms Arrange time: 52.8509ms Arrange time: 53.0946ms Arrange time: 53.388ms Meaasure time: 6.1µs Meaasure time: 37.4µs Meaasure time: 895µs Meaasure time: 1.3521ms Meaasure time: 7.2µs Meaasure time: 38.6µs Meaasure time: 1.5492ms Meaasure time: 104.6µs Meaasure time: 395.1µs Meaasure time: 152.8µs Meaasure time: 8.1894ms Meaasure time: 11.4063ms Meaasure time: 7.3µs Meaasure time: 37.3µs Meaasure time: 780.7µs Meaasure time: 101.4µs Meaasure time: 442.9µs Meaasure time: 86.5µs Meaasure time: 329.1µs Meaasure time: 2.8321ms Meaasure time: 6.5µs Meaasure time: 49.1µs Meaasure time: 1.1471ms Meaasure time: 123.5µs Meaasure time: 715.6µs Meaasure time: 172.5µs Meaasure time: 763.7µs Meaasure time: 12.3589ms Meaasure time: 6.6µs Meaasure time: 62.4µs Meaasure time: 10.049ms Meaasure time: 10.4978ms Meaasure time: 6.9µs Meaasure time: 41.2µs Meaasure time: 581.7µs Meaasure time: 128.6µs Meaasure time: 510.8µs Meaasure time: 82.2µs Meaasure time: 328.9µs Meaasure time: 2.7888ms Meaasure time: 6.4µs Meaasure time: 34.8µs Meaasure time: 517.4µs Meaasure time: 93.6µs Meaasure time: 8.3588ms Meaasure time: 99.2µs Meaasure time: 306.2µs Meaasure time: 10.0698ms Meaasure time: 7µs Meaasure time: 36.9µs Meaasure time: 784.2µs Meaasure time: 95.2µs Meaasure time: 509µs Meaasure time: 83.6µs Meaasure time: 287.9µs Meaasure time: 2.3799ms Meaasure time: 64.2844ms Meaasure time: 64.8213ms Meaasure time: 65.0137ms Arrange time: 109.9µs Arrange time: 8.5365ms Arrange time: 8.9589ms Arrange time: 104.4µs Arrange time: 665.1µs Arrange time: 220.5µs Arrange time: 459.4µs Arrange time: 220.6µs Arrange time: 666.3µs Arrange time: 2.55ms Arrange time: 104.3µs Arrange time: 421.9µs Arrange time: 223.7µs Arrange time: 464.5µs Arrange time: 231.5µs Arrange time: 521.1µs Arrange time: 10.1503ms Arrange time: 105.3µs Arrange time: 440.8µs Arrange time: 218.8µs Arrange time: 463.4µs Arrange time: 218.3µs Arrange time: 640.4µs Arrange time: 2.32ms Arrange time: 106.1µs Arrange time: 432.8µs Arrange time: 805µs Arrange time: 104.9µs Arrange time: 527.2µs Arrange time: 232.7µs Arrange time: 584.9µs Arrange time: 220.7µs Arrange time: 699.5µs Arrange time: 9.8258ms Arrange time: 104.6µs Arrange time: 420.1µs Arrange time: 221.1µs Arrange time: 457.6µs Arrange time: 224.7µs Arrange time: 547.8µs Arrange time: 2.2189ms Arrange time: 104.7µs Arrange time: 422.7µs Arrange time: 222.4µs Arrange time: 459.4µs Arrange time: 250.8µs Arrange time: 741.6µs Arrange time: 8.6783ms Arrange time: 47.887ms Arrange time: 48.1978ms Arrange time: 48.5076ms Meaasure time: 6.3µs Meaasure time: 36.3µs Meaasure time: 609.5µs Meaasure time: 846.7µs Meaasure time: 6.7µs Meaasure time: 34.8µs Meaasure time: 7.0589ms Meaasure time: 109.1µs Meaasure time: 449.2µs Meaasure time: 91.4µs Meaasure time: 622.8µs Meaasure time: 17.0059ms Meaasure time: 6.9µs Meaasure time: 37.5µs Meaasure time: 600.3µs Meaasure time: 96.1µs Meaasure time: 428.1µs Meaasure time: 92.6µs Meaasure time: 296.9µs Meaasure time: 2.677ms Meaasure time: 6.7µs Meaasure time: 34.5µs Meaasure time: 424.4µs Meaasure time: 88.2µs Meaasure time: 396µs Meaasure time: 79.9µs Meaasure time: 268.2µs Meaasure time: 1.7715ms Meaasure time: 6.8µs Meaasure time: 63.2µs Meaasure time: 678.4µs Meaasure time: 1.2475ms Meaasure time: 6.9µs Meaasure time: 62.7µs Meaasure time: 561.5µs Meaasure time: 93.8µs Meaasure time: 385.7µs Meaasure time: 85.7µs Meaasure time: 278µs Meaasure time: 2.0476ms Meaasure time: 6.5µs Meaasure time: 34.8µs Meaasure time: 417µs Meaasure time: 88.9µs Meaasure time: 279.9µs Meaasure time: 81.9µs Meaasure time: 384.4µs Meaasure time: 9.9961ms Meaasure time: 6.7µs Meaasure time: 37.7µs Meaasure time: 614.7µs Meaasure time: 96.4µs Meaasure time: 292µs Meaasure time: 84.4µs Meaasure time: 469.7µs Meaasure time: 2.5407ms Meaasure time: 47.9003ms Meaasure time: 48.2805ms Meaasure time: 48.489ms Arrange time: 111µs Arrange time: 438.2µs Arrange time: 738.9µs Arrange time: 112.1µs Arrange time: 8.4524ms Arrange time: 258.4µs Arrange time: 629.8µs Arrange time: 260.6µs Arrange time: 573.7µs Arrange time: 10.9725ms Arrange time: 100.8µs Arrange time: 435.3µs Arrange time: 220µs Arrange time: 624.1µs Arrange time: 222.5µs Arrange time: 9.8266ms Arrange time: 11.936ms Arrange time: 106.1µs Arrange time: 728.4µs Arrange time: 222µs Arrange time: 469.5µs Arrange time: 220.9µs Arrange time: 678.3µs Arrange time: 2.7507ms Arrange time: 110.1µs Arrange time: 2.6084ms Arrange time: 4.2353ms Arrange time: 107.8µs Arrange time: 9.4019ms Arrange time: 236.9µs Arrange time: 575.1µs Arrange time: 222.2µs Arrange time: 477µs Arrange time: 11.6102ms Arrange time: 107.5µs Arrange time: 697µs Arrange time: 224.1µs Arrange time: 460.7µs Arrange time: 227µs Arrange time: 8.8505ms Arrange time: 10.9015ms Arrange time: 106.6µs Arrange time: 774.4µs Arrange time: 228.6µs Arrange time: 489.4µs Arrange time: 222.2µs Arrange time: 674.2µs Arrange time: 2.7287ms Arrange time: 66.5562ms Arrange time: 66.7964ms Arrange time: 67.0909ms
Examples and MockUps
I tested the performance with this code on a 3.2 Ghz 4-core Intel i5 6500
use orbtk::prelude::*;
#[derive(Default, AsAny)]
struct MainViewState {}
impl MainViewState {}
impl State for MainViewState {}
widget!(MainView<MainViewState> {
text: String
});
impl Template for MainView {
fn template(self, _id: Entity, ctx: &mut BuildContext) -> Self {
self.name("MainView")
.width(400.0)
.height(1000.0)
.text("")
.child(
Grid::new()
//.columns(Columns::create().push("auto").build())
.rows(
Rows::create()
.push("auto")
.push("auto")
.push("auto")
.push("auto")
.push("auto")
.push("auto")
.push("auto")
.push("auto")
.build(),
)
.child(
generate_text_field(ctx, "Title")
.attach(Grid::column(0))
.attach(Grid::row(0))
.build(ctx),
)
.child(
generate_text_array_field(ctx, "Authors")
.attach(Grid::column(0))
.attach(Grid::row(1))
.build(ctx),
)
.child(
generate_text_array_field(ctx, "Owners")
.attach(Grid::column(0))
.attach(Grid::row(2))
.build(ctx),
)
.child(
generate_text_array_field(ctx, "Recipients")
.attach(Grid::column(0))
.attach(Grid::row(3))
.build(ctx),
)
.child(
generate_text_field(ctx, "Type")
.attach(Grid::column(0))
.attach(Grid::row(4))
.build(ctx),
)
.child(
generate_text_array_field(ctx, "Languages")
.attach(Grid::column(0))
.attach(Grid::row(5))
.build(ctx),
)
.child(
generate_text_array_field(ctx, "Tags")
.attach(Grid::column(0))
.attach(Grid::row(6))
.build(ctx),
)
.child(
generate_text_array_field(ctx, "Keywords")
.attach(Grid::column(0))
.attach(Grid::row(7))
.build(ctx),
)
.build(ctx),
)
}
}
fn generate_text_field(ctx: &mut BuildContext, label: &str) -> Container {
Container::new()
.padding(8)
.border_width(2.0)
.border_brush(Color::rgb(255, 255, 255))
.child(
Grid::new()
.rows(Rows::create().push("auto").push("auto").build())
.child(
TextBlock::new()
.attach(Grid::row(0))
.text(label)
.build(ctx),
)
.child(
TextBox::new()
.attach(Grid::row(1))
.width(300.0)
.text("")
.build(ctx),
)
.build(ctx),
)
}
fn generate_text_array_field(ctx: &mut BuildContext, label: &str) -> Container {
Container::new()
.padding(8)
.border_width(2.0)
.border_brush(Color::rgb(255, 255, 255))
.child(
Grid::new()
.columns(Columns::create().push("auto").push("auto").build())
.rows(Rows::create().push("auto").push("auto").push("auto").build())
.child(
TextBlock::new()
.attach(Grid::row(0))
.text(label)
.build(ctx),
)
.child(
TextBox::new()
.width(300.0)
.max_width(300.0)
.text("")
.attach(Grid::column(0))
.attach(Grid::row(1))
.build(ctx),
)
.child(
Button::new()
.size(40.0, 32.0)
.text("Delete")
.attach(Grid::row(1))
.attach(Grid::column(1))
.build(ctx),
)
.child(
Button::new()
.size(40.0, 32.0)
.text("Add")
.attach(Grid::row(2))
.attach(Grid::column(0))
.build(ctx),
)
.build(ctx),
)
}
pub fn main() {
Application::new()
.window(|ctx| {
Window::new()
.title("Window Title")
.position((100.0, 100.0))
.resizeable(true)
.size(450.0, 500.0)
.child(MainView::new().build(ctx))
.build(ctx)
})
.run();
}
I measured stuff again. Looks like the problem is in the arrange() method.
To my surprise the get_column_x_and_width( ), get_row_y_and_height() , calculate_column_width() , calculate_column_height() methods i think are pretty fast, they run for 2-10 µs, they are scaling well.
With 1000 TextBlock widgets in the grid, the arrange() took 8 seconds to run.The measure() method could be faster too, it took 500 ms to measure 1000 widget, but the winner is the arrange() clearly.
Also, it is unclear why the measure() and arrange() get called always twice.
Oh, and there is an excessive redrawing on the grid's child (all of them( when one of them become "dirty", eg typing in a textbox, hovering the mouse over them. This could be definitely improved using a cache. Or the grid just calculate only for the dirty widgets.
The major problem is not the Grid itself it is the complete layout system. On each iteration all layouts will be new calculated if there is an update or not. I start introducing an layout_dirty widget flag that is set to true if a property on a widget is changed that has impact on its layout. I started with the fixed sized layouts like text and image. Now their size is only measures initial or if there is an layout property update like font_size or text. On the second run it keeps the calculated size. On the showcase example on debug I helped me to reduce the whole layout calculation per run from 30ms to 21ms. On release from 4ms to 2ms.
At next I will introduce a mechanism that a parent layout can check if one of its children is layout_dirty if not, it will keeps also its calculated positions and sizes.
in the future, we could split the calculation of layouts to multiple threads, if the calculations runs too long
another improvement I'm thinking of is adding a batch_update method on the Context:
Right now this code
ctx.widget().set("key", val);
ctx.widget().set("key_2", new_val);
ctx.widget().set("last_key", another_val);
would trigger 3 updates, right?
Calling batch_update() would set a flag to indicate that the next updates of properties do not trigger an update, eg. it would not set the dirty_flag.After updating the values of properties, calling the commit() method would trigger the update finally.
Like:
ctx.batch_update_start();
ctx.widget().set("key", val);
ctx.widget().set("key_2", new_val);
ctx.widget().set("last_key", another_val);
ctx.batch_update_end(); // or ctx.commit();
would trigger 3 updates, right?
If you trigger these in the same loop, update will only triggered one time. For this a internal dirty flag is used. That flag is reset to false after rendering is finsihed.