From 15ee3be824ae96a15b7b577c2c6f0dddf11a8830 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 13 Jun 2022 16:07:17 -0500 Subject: [PATCH] [android] avoid View.Context where unecessary Context: https://github.com/dotnet/maui/pull/7996 Context: https://github.com/unoplatform/performance/tree/master/src/dopes/DopeTestMaui Building upon my changes in #7996 (sample sample), I noticed: 7.60s (14%) mono.android!Android.Views.View.get_Context() 14% of the time is literally spent calling `View.Context`! We did a little investigation on the underlying Java interop, this is basically doing: 1. Call into JNI, get an `IntPtr`. 2. See if that `IntPtr` maps to a C# object that is already alive. 3. Return the C# object, or create a new one if needed. We can actually avoid all this work, in this case. For example: 5.48s (10%) microsoft.maui!Microsoft.Maui.Platform.TransformationExtensions.UpdateAnchorX(Android.Views.View,Microsoft.Maui.IView) 4.28s (8.2%) microsoft.maui!Microsoft.Maui.Platform.TransformationExtensions.UpdateAnchorY(Android.Views.View,Microsoft.Maui.IView) These extension methods call `View.Context` twice: public static void UpdateTranslationY(this AView platformView, IView view) { if (platformView.Context == null) return; platformView.TranslationY = platformView.Context.ToPixels(view.TranslationY); } We can actually, make an overload for `ToPixels()` to where `View.Context` wouldn't be called *at all*: internal static float ToPixels (this View view, double dp) { if (s_displayDensity != float.MinValue) return (float)Math.Ceiling(dp * s_displayDensity); return view.Context.ToPixels(dp); } I used this everywhere I saw it appearing in `dotnet trace` output. Next, I saw `View.Context` being called a lot from `LayoutViewGroup` and `ContentViewGroup`. I could simply store the value in the constructor for these types, make it non-nullable, and remove null checks. ~~ Results ~~ A `Release` build on a Pixel 5 device, I was getting: Before: 64.23 Dopes/s After: 81.70 Dopes/s --- .../src/Platform/Android/ContentViewGroup.cs | 27 ++++++++++--------- .../src/Platform/Android/ContextExtensions.cs | 7 +++++ .../src/Platform/Android/LayoutViewGroup.cs | 25 +++++++++-------- .../Platform/Android/TextViewExtensions.cs | 15 +++-------- .../Android/TransformationExtensions.cs | 20 +++----------- .../src/Platform/Android/ViewExtensions.cs | 16 +++++------ 6 files changed, 50 insertions(+), 60 deletions(-) diff --git a/src/Core/src/Platform/Android/ContentViewGroup.cs b/src/Core/src/Platform/Android/ContentViewGroup.cs index a08e816bf755..3098d26e0a6d 100644 --- a/src/Core/src/Platform/Android/ContentViewGroup.cs +++ b/src/Core/src/Platform/Android/ContentViewGroup.cs @@ -12,25 +12,33 @@ namespace Microsoft.Maui.Platform public class ContentViewGroup : ViewGroup { IBorderStroke? _clip; + readonly Context _context; public ContentViewGroup(Context context) : base(context) { + _context = context; } public ContentViewGroup(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { + var context = Context; + ArgumentNullException.ThrowIfNull(context); + _context = context; } public ContentViewGroup(Context context, IAttributeSet attrs) : base(context, attrs) { + _context = context; } public ContentViewGroup(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr) { + _context = context; } public ContentViewGroup(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes) { + _context = context; } protected override void DispatchDraw(Canvas? canvas) @@ -43,19 +51,14 @@ protected override void DispatchDraw(Canvas? canvas) protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (Context == null) - { - return; - } - if (CrossPlatformMeasure == null) { base.OnMeasure(widthMeasureSpec, heightMeasureSpec); return; } - var deviceIndependentWidth = widthMeasureSpec.ToDouble(Context); - var deviceIndependentHeight = heightMeasureSpec.ToDouble(Context); + var deviceIndependentWidth = widthMeasureSpec.ToDouble(_context); + var deviceIndependentHeight = heightMeasureSpec.ToDouble(_context); var widthMode = MeasureSpec.GetMode(widthMeasureSpec); var heightMode = MeasureSpec.GetMode(heightMeasureSpec); @@ -67,8 +70,8 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) var width = widthMode == MeasureSpecMode.Exactly ? deviceIndependentWidth : measure.Width; var height = heightMode == MeasureSpecMode.Exactly ? deviceIndependentHeight : measure.Height; - var platformWidth = Context.ToPixels(width); - var platformHeight = Context.ToPixels(height); + var platformWidth = _context.ToPixels(width); + var platformHeight = _context.ToPixels(height); // Minimum values win over everything platformWidth = Math.Max(MinimumWidth, platformWidth); @@ -79,12 +82,12 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) protected override void OnLayout(bool changed, int left, int top, int right, int bottom) { - if (CrossPlatformArrange == null || Context == null) + if (CrossPlatformArrange == null) { return; } - var destination = Context!.ToCrossPlatformRectInReferenceFrame(left, top, right, bottom); + var destination = _context.ToCrossPlatformRectInReferenceFrame(left, top, right, bottom); CrossPlatformArrange(destination); } @@ -107,7 +110,7 @@ void ClipChild(Canvas? canvas) if (Clip == null || canvas == null) return; - float density = Context.GetDisplayDensity(); + float density = _context.GetDisplayDensity(); float strokeThickness = (float)(Clip.StrokeThickness * density); float offset = strokeThickness / 2; diff --git a/src/Core/src/Platform/Android/ContextExtensions.cs b/src/Core/src/Platform/Android/ContextExtensions.cs index 9bf63495deb6..e91ede9face1 100644 --- a/src/Core/src/Platform/Android/ContextExtensions.cs +++ b/src/Core/src/Platform/Android/ContextExtensions.cs @@ -72,6 +72,13 @@ public static void ShowKeyboard(this Context self, global::Android.Views.View vi service.ShowSoftInput(view, ShowFlags.Implicit); } + internal static float ToPixels (this View view, double dp) + { + if (s_displayDensity != float.MinValue) + return (float)Math.Ceiling(dp * s_displayDensity); + return view.Context.ToPixels(dp); + } + public static float ToPixels(this Context? self, double dp) { EnsureMetrics(self); diff --git a/src/Core/src/Platform/Android/LayoutViewGroup.cs b/src/Core/src/Platform/Android/LayoutViewGroup.cs index b86fb8b27a97..538ab0cd4ef2 100644 --- a/src/Core/src/Platform/Android/LayoutViewGroup.cs +++ b/src/Core/src/Platform/Android/LayoutViewGroup.cs @@ -14,27 +14,35 @@ namespace Microsoft.Maui.Platform public class LayoutViewGroup : ViewGroup { readonly ARect _clipRect = new(); + readonly Context _context; public bool InputTransparent { get; set; } public LayoutViewGroup(Context context) : base(context) { + _context = context; } public LayoutViewGroup(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { + var context = Context; + ArgumentNullException.ThrowIfNull(context); + _context = context; } public LayoutViewGroup(Context context, IAttributeSet attrs) : base(context, attrs) { + _context = context; } public LayoutViewGroup(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr) { + _context = context; } public LayoutViewGroup(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes) { + _context = context; } public bool ClipsToBounds { get; set; } @@ -44,19 +52,14 @@ public LayoutViewGroup(Context context, IAttributeSet attrs, int defStyleAttr, i // apply to ViewHandlerExtensions.MeasureVirtualView protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (Context == null) - { - return; - } - if (CrossPlatformMeasure == null) { base.OnMeasure(widthMeasureSpec, heightMeasureSpec); return; } - var deviceIndependentWidth = widthMeasureSpec.ToDouble(Context); - var deviceIndependentHeight = heightMeasureSpec.ToDouble(Context); + var deviceIndependentWidth = widthMeasureSpec.ToDouble(_context); + var deviceIndependentHeight = heightMeasureSpec.ToDouble(_context); var widthMode = MeasureSpec.GetMode(widthMeasureSpec); var heightMode = MeasureSpec.GetMode(heightMeasureSpec); @@ -68,8 +71,8 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) var width = widthMode == MeasureSpecMode.Exactly ? deviceIndependentWidth : measure.Width; var height = heightMode == MeasureSpecMode.Exactly ? deviceIndependentHeight : measure.Height; - var platformWidth = Context.ToPixels(width); - var platformHeight = Context.ToPixels(height); + var platformWidth = _context.ToPixels(width); + var platformHeight = _context.ToPixels(height); // Minimum values win over everything platformWidth = Math.Max(MinimumWidth, platformWidth); @@ -83,12 +86,12 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) // apply to ViewHandlerExtensions.MeasureVirtualView protected override void OnLayout(bool changed, int l, int t, int r, int b) { - if (CrossPlatformArrange == null || Context == null) + if (CrossPlatformArrange == null || _context == null) { return; } - var destination = Context!.ToCrossPlatformRectInReferenceFrame(l, t, r, b); + var destination = _context.ToCrossPlatformRectInReferenceFrame(l, t, r, b); CrossPlatformArrange(destination); diff --git a/src/Core/src/Platform/Android/TextViewExtensions.cs b/src/Core/src/Platform/Android/TextViewExtensions.cs index 976cc3b03703..2f10da12a69a 100644 --- a/src/Core/src/Platform/Android/TextViewExtensions.cs +++ b/src/Core/src/Platform/Android/TextViewExtensions.cs @@ -72,18 +72,11 @@ public static void UpdateVerticalTextAlignment(this TextView textView, ITextAlig public static void UpdatePadding(this TextView textView, ILabel label) { - var context = textView.Context; - - if (context == null) - { - return; - } - textView.SetPadding( - (int)context.ToPixels(label.Padding.Left), - (int)context.ToPixels(label.Padding.Top), - (int)context.ToPixels(label.Padding.Right), - (int)context.ToPixels(label.Padding.Bottom)); + (int)textView.ToPixels(label.Padding.Left), + (int)textView.ToPixels(label.Padding.Top), + (int)textView.ToPixels(label.Padding.Right), + (int)textView.ToPixels(label.Padding.Bottom)); } public static void UpdateTextDecorations(this TextView textView, ILabel label) diff --git a/src/Core/src/Platform/Android/TransformationExtensions.cs b/src/Core/src/Platform/Android/TransformationExtensions.cs index b09914aec431..666310c59511 100644 --- a/src/Core/src/Platform/Android/TransformationExtensions.cs +++ b/src/Core/src/Platform/Android/TransformationExtensions.cs @@ -6,18 +6,12 @@ public static class TransformationExtensions { public static void UpdateTranslationX(this AView platformView, IView view) { - if (platformView.Context == null) - return; - - platformView.TranslationX = platformView.Context.ToPixels(view.TranslationX); + platformView.TranslationX = platformView.ToPixels(view.TranslationX); } public static void UpdateTranslationY(this AView platformView, IView view) { - if (platformView.Context == null) - return; - - platformView.TranslationY = platformView.Context.ToPixels(view.TranslationY); + platformView.TranslationY = platformView.ToPixels(view.TranslationY); } public static void UpdateScale(this AView platformView, IView view) @@ -63,19 +57,13 @@ public static void UpdateRotationY(this AView platformView, IView view) public static void UpdateAnchorX(this AView platformView, IView view) { - if (platformView.Context == null) - return; - - var pivotX = (float)(view.AnchorX * platformView.Context.ToPixels(view.Frame.Width)); + var pivotX = (float)(view.AnchorX * platformView.ToPixels(view.Frame.Width)); PlatformInterop.SetPivotXIfNeeded(platformView, pivotX); } public static void UpdateAnchorY(this AView platformView, IView view) { - if (platformView.Context == null) - return; - - var pivotY = (float)(view.AnchorY * platformView.Context.ToPixels(view.Frame.Height)); + var pivotY = (float)(view.AnchorY * platformView.ToPixels(view.Frame.Height)); PlatformInterop.SetPivotYIfNeeded(platformView, pivotY); } } diff --git a/src/Core/src/Platform/Android/ViewExtensions.cs b/src/Core/src/Platform/Android/ViewExtensions.cs index eb88b0529689..3bbec646413f 100644 --- a/src/Core/src/Platform/Android/ViewExtensions.cs +++ b/src/Core/src/Platform/Android/ViewExtensions.cs @@ -22,12 +22,8 @@ public static partial class ViewExtensions { public static void Initialize(this AView platformView, IView view) { - var context = platformView.Context; - if (context == null) - return; - - var pivotX = (float)(view.AnchorX * context.ToPixels(view.Frame.Width)); - var pivotY = (float)(view.AnchorY * context.ToPixels(view.Frame.Height)); + var pivotX = (float)(view.AnchorX * platformView.ToPixels(view.Frame.Width)); + var pivotY = (float)(view.AnchorY * platformView.ToPixels(view.Frame.Height)); int visibility; if (view is IActivityIndicator a) @@ -43,12 +39,12 @@ public static void Initialize(this AView platformView, IView view) PlatformInterop.Set(platformView, visibility: visibility, layoutDirection: (int)GetLayoutDirection(view), - minimumHeight: (int)context.ToPixels(view.MinimumHeight), - minimumWidth: (int)context.ToPixels(view.MinimumWidth), + minimumHeight: (int)platformView.ToPixels(view.MinimumHeight), + minimumWidth: (int)platformView.ToPixels(view.MinimumWidth), enabled: view.IsEnabled, alpha: (float)view.Opacity, - translationX: context.ToPixels(view.TranslationX), - translationY: context.ToPixels(view.TranslationY), + translationX: platformView.ToPixels(view.TranslationX), + translationY: platformView.ToPixels(view.TranslationY), scaleX: (float)(view.Scale * view.ScaleX), scaleY: (float)(view.Scale * view.ScaleY), rotation: (float)view.Rotation,