Skip to content

[android] reduce JNI calls during layout measurement#8034

Merged
PureWeen merged 1 commit intodotnet:mainfrom
jonathanpeppers:MeasuredWidthHeight
Jun 15, 2022
Merged

[android] reduce JNI calls during layout measurement#8034
PureWeen merged 1 commit intodotnet:mainfrom
jonathanpeppers:MeasuredWidthHeight

Conversation

@jonathanpeppers
Copy link
Member

Context: https://github.com/unoplatform/performance/tree/master/src/dopes/DopeTestMaui

Building upon #7996, #8001, and #8033, I noticed while profiling the
sample app putting N Label on the screen:

783.92ms (6.2%) microsoft.maui!Microsoft.Maui.ViewHandlerExtensions.GetDesiredSizeFromHandler(Microsoft.Maui.IViewHandler,double,double)

So around %6 of the time spend just measuring.

Looking through the call stack, I can see 3 JNI calls happening:

932.51ms (7.4%)  mono.android!Android.Views.View.Measure(int,int)
115.53ms (0.91%) mono.android!Android.Views.View.get_MeasuredWidth()
 96.97ms (0.77%) mono.android!Android.Views.View.get_MeasuredHeight()

So, we could write a Java method that calls all three of these and
somehow returns the MeasuredWidth and MeasuredHeight. After a
little research, it seemed the best approach here was to "pack" two
integers into a long. If we tried to return some Java object
instead, then we'd have the same number of JNI calls to get the
integers out.

I found a couple links describing how to "pack" an int into a long:

Which after some testing, arrives at:

public static long measureAndGetWidthAndHeight(View view, int widthMeasureSpec, int heightMeasureSpec) {
    view.measure(widthMeasureSpec, heightMeasureSpec);
    int width = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();
    return ((long)width << 32) | (height & 0xffffffffL);
}

Unpacked in C# such as:

var packed = PlatformInterop.MeasureAndGetWidthAndHeight(platformView, widthSpec, heightSpec);
var measuredWidth = (int)(packed >> 32);
var measuredHeight = (int)(packed & 0xffffffffL);

Reducing 3 JNI calls in every View's layout to 1.

Results

A Release build on a Pixel 5 device, I was getting:

Before:  91.94 Dopes/s
After:  102.45 Dopes/s

image

After profiling again, it drops the % time spent in
GetDesiredSizeFromHandler:

528.96ms (4.5%) microsoft.maui!Microsoft.Maui.ViewHandlerExtensions.GetDesiredSizeFromHandler

So the added math is negligible compared to reduced JNI calls.

Context: https://github.com/unoplatform/performance/tree/master/src/dopes/DopeTestMaui

Building upon dotnet#7996, dotnet#8001, and dotnet#8033, I noticed while profiling the
sample app putting N Label on the screen:

    783.92ms (6.2%) microsoft.maui!Microsoft.Maui.ViewHandlerExtensions.GetDesiredSizeFromHandler(Microsoft.Maui.IViewHandler,double,double)

So around %6 of the time spend just measuring.

Looking through the call stack, I can see 3 JNI calls happening:

    932.51ms (7.4%)  mono.android!Android.Views.View.Measure(int,int)
    115.53ms (0.91%) mono.android!Android.Views.View.get_MeasuredWidth()
     96.97ms (0.77%) mono.android!Android.Views.View.get_MeasuredHeight()

So, we could write a Java method that calls all three of these and
somehow returns the `MeasuredWidth` and `MeasuredHeight`. After a
little research, it seemed the best approach here was to "pack" two
integers into a `long`. If we tried to return some Java object
instead, then we'd have the same number of JNI calls to get the
integers out.

I found a couple links describing how to "pack" an `int` into a `long`:

* Java: https://stackoverflow.com/a/12772968
* C#: https://stackoverflow.com/a/827267

Which after some testing, arrives at:

    public static long measureAndGetWidthAndHeight(View view, int widthMeasureSpec, int heightMeasureSpec) {
        view.measure(widthMeasureSpec, heightMeasureSpec);
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
        return ((long)width << 32) | (height & 0xffffffffL);
    }

Unpacked in C# such as:

    var packed = PlatformInterop.MeasureAndGetWidthAndHeight(platformView, widthSpec, heightSpec);
    var measuredWidth = (int)(packed >> 32);
    var measuredHeight = (int)(packed & 0xffffffffL);

Reducing 3 JNI calls in every `View`'s layout to 1.

~~ Results ~~

A `Release` build on a Pixel 5 device, I was getting:

    Before:  91.94 Dopes/s
    After:  102.45 Dopes/s

After profiling again, it drops the % time spent in
`GetDesiredSizeFromHandler`:

    528.96ms (4.5%) microsoft.maui!Microsoft.Maui.ViewHandlerExtensions.GetDesiredSizeFromHandler

So the added math is negligible compared to reduced JNI calls.
@jsuarezruiz jsuarezruiz added the legacy-area-perf Startup / Runtime performance label Jun 15, 2022
@jsuarezruiz
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@PureWeen PureWeen merged commit 06e5c08 into dotnet:main Jun 15, 2022
@jonathanpeppers jonathanpeppers deleted the MeasuredWidthHeight branch June 15, 2022 14:53
@mattleibow
Copy link
Member

FYI @hartez

@hartez
Copy link
Contributor

hartez commented Jun 16, 2022

Love it!

rmarinho pushed a commit that referenced this pull request Jun 23, 2022
Context: https://github.com/unoplatform/performance/tree/master/src/dopes/DopeTestMaui

Building upon #7996, #8001, and #8033, I noticed while profiling the
sample app putting N Label on the screen:

    783.92ms (6.2%) microsoft.maui!Microsoft.Maui.ViewHandlerExtensions.GetDesiredSizeFromHandler(Microsoft.Maui.IViewHandler,double,double)

So around %6 of the time spend just measuring.

Looking through the call stack, I can see 3 JNI calls happening:

    932.51ms (7.4%)  mono.android!Android.Views.View.Measure(int,int)
    115.53ms (0.91%) mono.android!Android.Views.View.get_MeasuredWidth()
     96.97ms (0.77%) mono.android!Android.Views.View.get_MeasuredHeight()

So, we could write a Java method that calls all three of these and
somehow returns the `MeasuredWidth` and `MeasuredHeight`. After a
little research, it seemed the best approach here was to "pack" two
integers into a `long`. If we tried to return some Java object
instead, then we'd have the same number of JNI calls to get the
integers out.

I found a couple links describing how to "pack" an `int` into a `long`:

* Java: https://stackoverflow.com/a/12772968
* C#: https://stackoverflow.com/a/827267

Which after some testing, arrives at:

    public static long measureAndGetWidthAndHeight(View view, int widthMeasureSpec, int heightMeasureSpec) {
        view.measure(widthMeasureSpec, heightMeasureSpec);
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
        return ((long)width << 32) | (height & 0xffffffffL);
    }

Unpacked in C# such as:

    var packed = PlatformInterop.MeasureAndGetWidthAndHeight(platformView, widthSpec, heightSpec);
    var measuredWidth = (int)(packed >> 32);
    var measuredHeight = (int)(packed & 0xffffffffL);

Reducing 3 JNI calls in every `View`'s layout to 1.

~~ Results ~~

A `Release` build on a Pixel 5 device, I was getting:

    Before:  91.94 Dopes/s
    After:  102.45 Dopes/s

After profiling again, it drops the % time spent in
`GetDesiredSizeFromHandler`:

    528.96ms (4.5%) microsoft.maui!Microsoft.Maui.ViewHandlerExtensions.GetDesiredSizeFromHandler

So the added math is negligible compared to reduced JNI calls.
@github-actions github-actions bot locked and limited conversation to collaborators Dec 20, 2023
@Eilon Eilon added the perf/general The issue affects performance (runtime speed, memory usage, startup time, etc.) (sub: perf) label May 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

fixed-in-7.0.0-rc.1.6683 legacy-area-perf Startup / Runtime performance perf/general The issue affects performance (runtime speed, memory usage, startup time, etc.) (sub: perf)

Projects

No open projects
Status: Done & Blogged

Development

Successfully merging this pull request may close these issues.

7 participants