Browse Source

Reduced array allocations.

José Pedro 6 năm trước cách đây
mục cha
commit
19f9729e86

+ 1 - 1
build/System.Memory.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="System.Memory" Version="4.5.1" />
+    <PackageReference Include="System.Memory" Version="4.5.3" />
   </ItemGroup>
 </Project>

+ 4 - 2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -15,10 +15,10 @@ using Avalonia.Rendering;
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
     class TopLevelImpl : IAndroidView, ITopLevelImpl,  IFramebufferPlatformSurface
-
     {
         private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
         private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
+
         private ViewImpl _view;
 
         public TopLevelImpl(Context context, bool placeOnTop = false)
@@ -28,6 +28,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
                 p => GetAvaloniaPointFromEvent(p));
 
+            Surfaces = new object[] { this };
+
             MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
                 _view.Resources.DisplayMetrics.HeightPixels);
         }
@@ -82,7 +84,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public IPlatformHandle Handle => _view;
 
-        public IEnumerable<object> Surfaces => new object[] {this};
+        public IEnumerable<object> Surfaces { get; }
 
         public IRenderer CreateRenderer(IRenderRoot root)
         {

+ 16 - 7
src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Buffers;
 using System.Runtime.InteropServices;
 using System.Text;
 
@@ -6,7 +7,7 @@ namespace Avalonia.Platform.Interop
 {
     public class Utf8Buffer : SafeHandle
     {
-        private GCHandle _gchandle;
+        private GCHandle _gcHandle;
         private byte[] _data;
             
         public Utf8Buffer(string s) : base(IntPtr.Zero, true)
@@ -14,8 +15,8 @@ namespace Avalonia.Platform.Interop
             if (s == null)
                 return;
             _data = Encoding.UTF8.GetBytes(s);
-            _gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
-            handle = _gchandle.AddrOfPinnedObject();
+            _gcHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
+            handle = _gcHandle.AddrOfPinnedObject();
         }
 
         public int ByteLen => _data.Length;
@@ -26,7 +27,7 @@ namespace Avalonia.Platform.Interop
             {
                 handle = IntPtr.Zero;
                 _data = null;
-                _gchandle.Free();
+                _gcHandle.Free();
             }
             return true;
         }
@@ -40,10 +41,18 @@ namespace Avalonia.Platform.Interop
                 return null;
             int len;
             for (len = 0; pstr[len] != 0; len++) ;
-            var bytes = new byte[len];
-            Marshal.Copy(s, bytes, 0, len);
 
-            return Encoding.UTF8.GetString(bytes, 0, len);
+            var bytes = ArrayPool<byte>.Shared.Rent(len);
+
+            try
+            {
+                Marshal.Copy(s, bytes, 0, len);
+                return Encoding.UTF8.GetString(bytes, 0, len);
+            }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(bytes);
+            }
         }
     }
 }

+ 1 - 1
src/Avalonia.Controls/TreeView.cs

@@ -50,7 +50,7 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<SelectionMode> SelectionModeProperty =
             ListBox.SelectionModeProperty.AddOwner<TreeView>();
 
-        private static readonly IList Empty = new object[0];
+        private static readonly IList Empty = Array.Empty<object>();
         private object _selectedItem;
         private IList _selectedItems;
 

+ 23 - 14
src/Avalonia.FreeDesktop/NativeMethods.cs

@@ -1,12 +1,5 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using Avalonia.Controls.Platform;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Text.RegularExpressions;
+using System.Buffers;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Text;
 
@@ -21,11 +14,27 @@ namespace Avalonia.FreeDesktop
 
         public static string ReadLink(string path)
         {
-            var symlink = Encoding.UTF8.GetBytes(path);
-            var result = new byte[4095];
-            readlink(symlink, result, result.Length);
-            var rawstr = Encoding.UTF8.GetString(result);
-            return rawstr.Substring(0, rawstr.IndexOf('\0'));
+            var symlinkMaxSize = Encoding.ASCII.GetMaxByteCount(path.Length);
+            var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated
+
+            var symlink = ArrayPool<byte>.Shared.Rent(symlinkMaxSize + 1);
+            var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
+
+            try
+            {
+                var symlinkSize = Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0);
+                symlink[symlinkSize] = 0;
+
+                var size = readlink(symlink, buffer, bufferSize);
+                Debug.Assert(size < bufferSize); // if this fails, we need to increase the buffer size (dynamically?)
+
+                return Encoding.UTF8.GetString(buffer, 0, (int)size);
+            }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(symlink);
+                ArrayPool<byte>.Shared.Return(buffer);
+            }
         }
     }
 }

+ 3 - 2
src/Avalonia.X11/X11CursorFactory.cs

@@ -8,6 +8,8 @@ namespace Avalonia.X11
 {
     class X11CursorFactory : IStandardCursorFactory
     {
+        private static readonly byte[] NullCursorData = new byte[] { 0 };
+
         private static IntPtr _nullCursor;
 
         private readonly IntPtr _display;
@@ -68,9 +70,8 @@ namespace Avalonia.X11
         private static IntPtr GetNullCursor(IntPtr display)
         {
             XColor color = new XColor();
-            byte[] data = new byte[] { 0 };
             IntPtr window = XLib.XRootWindow(display, 0);
-            IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, data, 1, 1);
+            IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, NullCursorData, 1, 1);
             return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0);
         }
     }

+ 26 - 14
src/Avalonia.X11/XI2Manager.cs

@@ -1,18 +1,32 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using static Avalonia.X11.XLib;
+
 namespace Avalonia.X11
 {
     unsafe class XI2Manager
     {
+        private static readonly XiEventType[] DefaultEventTypes = new XiEventType[]
+        {
+            XiEventType.XI_Motion,
+            XiEventType.XI_ButtonPress,
+            XiEventType.XI_ButtonRelease
+        };
+
+        private static readonly XiEventType[] MultiTouchEventTypes = new XiEventType[]
+        {
+            XiEventType.XI_TouchBegin,
+            XiEventType.XI_TouchUpdate,
+            XiEventType.XI_TouchEnd
+        };
+
         private X11Info _x11;
         private bool _multitouch;
         private Dictionary<IntPtr, IXI2Client> _clients = new Dictionary<IntPtr, IXI2Client>();
+
         class DeviceInfo
         {
             public int Id { get; }
@@ -125,20 +139,18 @@ namespace Avalonia.X11
         public XEventMask AddWindow(IntPtr xid, IXI2Client window)
         {
             _clients[xid] = window;
-            var events = new List<XiEventType>
-            {
-                XiEventType.XI_Motion,
-                XiEventType.XI_ButtonPress,
-                XiEventType.XI_ButtonRelease
-            };
+
+            var eventsLength = DefaultEventTypes.Length;
 
             if (_multitouch)
-                events.AddRange(new[]
-                {
-                    XiEventType.XI_TouchBegin,
-                    XiEventType.XI_TouchUpdate,
-                    XiEventType.XI_TouchEnd
-                });
+                eventsLength += MultiTouchEventTypes.Length;
+
+            var events = new List<XiEventType>(eventsLength);
+
+            events.AddRange(DefaultEventTypes);
+
+            if (_multitouch)
+                events.AddRange(MultiTouchEventTypes);
 
             XiSelectEvents(_x11.Display, xid,
                 new Dictionary<int, List<XiEventType>> {[_pointerDevice.Id] = events});

+ 5 - 2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -6,7 +6,6 @@ using Avalonia.LinuxFramebuffer.Input;
 using Avalonia.LinuxFramebuffer.Output;
 using Avalonia.Platform;
 using Avalonia.Rendering;
-using Avalonia.Threading;
 
 namespace Avalonia.LinuxFramebuffer
 {
@@ -14,6 +13,7 @@ namespace Avalonia.LinuxFramebuffer
     {
         private readonly IOutputBackend _outputBackend;
         private readonly IInputBackend _inputBackend;
+
         private bool _renderQueued;
         public IInputRoot InputRoot { get; private set; }
 
@@ -21,6 +21,9 @@ namespace Avalonia.LinuxFramebuffer
         {
             _outputBackend = outputBackend;
             _inputBackend = inputBackend;
+
+            Surfaces = new object[] { _outputBackend };
+
             Invalidate(default(Rect));
             _inputBackend.Initialize(this, e => Input?.Invoke(e));
         }
@@ -62,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer
         public IPopupImpl CreatePopup() => null;
 
         public double Scaling => _outputBackend.Scaling;
-        public IEnumerable<object> Surfaces => new object[] {_outputBackend};
+        public IEnumerable<object> Surfaces { get; }
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }
         public Action<Size> Resized { get; set; }

+ 1 - 0
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@@ -1,6 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <PackageId>Avalonia.Direct2D1</PackageId>
   </PropertyGroup>
   <ItemGroup>

+ 19 - 6
src/Windows/Avalonia.Win32/DataObject.cs

@@ -1,5 +1,7 @@
 using System;
+using System.Buffers;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
@@ -254,9 +256,18 @@ namespace Avalonia.Win32
                 return WriteFileListToHGlobal(ref hGlobal, files);
             if (data is Stream stream)
             {
-                byte[] buffer = new byte[stream.Length - stream.Position];
-                stream.Read(buffer, 0, buffer.Length);
-                return WriteBytesToHGlobal(ref hGlobal, buffer);
+                var length = (int)(stream.Length - stream.Position);
+                var buffer = ArrayPool<byte>.Shared.Rent(length);
+
+                try
+                {
+                    stream.Read(buffer, 0, length);
+                    return WriteBytesToHGlobal(ref hGlobal, buffer.AsSpan(0, length));
+                }
+                finally
+                {
+                    ArrayPool<byte>.Shared.Return(buffer);
+                }
             }
             if (data is IEnumerable<byte> bytes)
             {
@@ -277,7 +288,7 @@ namespace Avalonia.Win32
             }
         }
 
-        private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data)
+        private unsafe int WriteBytesToHGlobal(ref IntPtr hGlobal, ReadOnlySpan<byte> data)
         {
             int required = data.Length;
             if (hGlobal == IntPtr.Zero)
@@ -287,10 +298,12 @@ namespace Avalonia.Win32
             if (required > available)
                 return STG_E_MEDIUMFULL;
 
-            IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
+            var ptr = UnmanagedMethods.GlobalLock(hGlobal);
+            Debug.Assert(ptr == hGlobal);
+
             try
             {
-                Marshal.Copy(data, 0, ptr, data.Length);
+                data.CopyTo(new Span<byte>((void*)ptr, data.Length));
                 return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
             }
             finally

+ 3 - 4
src/Windows/Avalonia.Win32/DragSource.cs

@@ -17,9 +17,8 @@ namespace Avalonia.Win32
             DataObject dataObject = new DataObject(data);
             int allowed = (int)OleDropTarget.ConvertDropEffect(allowedEffects);
 
-            int[] finalEffect = new int[1];
-            UnmanagedMethods.DoDragDrop(dataObject, src, allowed, finalEffect);
-
-            return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect[0]));}
+            UnmanagedMethods.DoDragDrop(dataObject, src, allowed, out var finalEffect);
+            return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect));
+        }
     }
 }

+ 3 - 3
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -1051,10 +1051,10 @@ namespace Avalonia.Win32.Interop
         public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi);
 
         [DllImport("user32")]
-        public static extern bool GetTouchInputInfo(
+        public static extern unsafe bool GetTouchInputInfo(
             IntPtr hTouchInput,
             uint        cInputs,
-            [Out]TOUCHINPUT[] pInputs,
+            TOUCHINPUT* pInputs,
             int         cbSize
         );
         
@@ -1115,7 +1115,7 @@ namespace Avalonia.Win32.Interop
         public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
 
         [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
-        internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
+        internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, out int finalEffect);
 
 
 

+ 25 - 18
src/Windows/Avalonia.Win32/OleDataObject.cs

@@ -1,5 +1,7 @@
 using System;
+using System.Buffers;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
@@ -86,15 +88,8 @@ namespace Avalonia.Win32
             return null;
         }
 
-        private bool IsSerializedObject(byte[] data)
-        {
-            if (data.Length < DataObject.SerializedObjectGUID.Length)
-                return false;
-            for (int i = 0; i < DataObject.SerializedObjectGUID.Length; i++)
-                if (data[i] != DataObject.SerializedObjectGUID[i])
-                    return false;
-            return true;
-        }
+        private static bool IsSerializedObject(ReadOnlySpan<byte> data) =>
+            data.StartsWith(DataObject.SerializedObjectGUID);
 
         private static IEnumerable<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
         {
@@ -148,21 +143,33 @@ namespace Avalonia.Win32
         private IEnumerable<string> GetDataFormatsCore()
         {
             var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET);
+
             if (enumFormat != null)
             {
                 enumFormat.Reset();
-                FORMATETC[] formats = new FORMATETC[1];
-                int[] fetched = { 1 };
-                while (fetched[0] > 0)
+                
+                var formats = ArrayPool<FORMATETC>.Shared.Rent(1);
+                var fetched = ArrayPool<int>.Shared.Rent(1);
+
+                try
                 {
-                    fetched[0] = 0;
-                    if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
+                    do
                     {
-                        if (formats[0].ptd != IntPtr.Zero)
-                            Marshal.FreeCoTaskMem(formats[0].ptd);
-                        
-                        yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
+                        fetched[0] = 0;
+                        if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
+                        {
+                            if (formats[0].ptd != IntPtr.Zero)
+                                Marshal.FreeCoTaskMem(formats[0].ptd);
+
+                            yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
+                        }
                     }
+                    while (fetched[0] > 0);
+                }
+                finally
+                {
+                    ArrayPool<FORMATETC>.Shared.Return(formats);
+                    ArrayPool<int>.Shared.Return(fetched);
                 }
             }
         }

+ 7 - 5
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -5,7 +5,6 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
-using System.Reactive.Disposables;
 using System.Runtime.InteropServices;
 using Avalonia.Controls;
 using Avalonia.Input;
@@ -13,7 +12,6 @@ using Avalonia.Input.Raw;
 using Avalonia.OpenGL;
 using Avalonia.Platform;
 using Avalonia.Rendering;
-using Avalonia.Threading;
 using Avalonia.Win32.Input;
 using Avalonia.Win32.Interop;
 using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -430,7 +428,7 @@ namespace Avalonia.Win32
         }
         
         [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
-        protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
+        protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
         {
             bool unicode = UnmanagedMethods.IsWindowUnicode(hWnd);
 
@@ -637,8 +635,12 @@ namespace Avalonia.Win32
                         new Point(0, 0), GetMouseModifiers(wParam));
                     break;
                 case WindowsMessage.WM_TOUCH:
-                    var touchInputs = new TOUCHINPUT[wParam.ToInt32()];
-                    if (GetTouchInputInfo(lParam, (uint)wParam.ToInt32(), touchInputs, Marshal.SizeOf<TOUCHINPUT>()))
+                    var touchInputCount = wParam.ToInt32();
+
+                    var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount];
+                    var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount);
+
+                    if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>()))
                     {
                         foreach (var touchInput in touchInputs)
                         {

+ 6 - 3
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@@ -16,14 +16,17 @@ namespace Avalonia.iOS
     [Adopts("UIKeyInput")]
     class TopLevelImpl : UIView, ITopLevelImpl, IFramebufferPlatformSurface
     {
-        private IInputRoot _inputRoot;
         private readonly KeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
 
+        private IInputRoot _inputRoot;
+
         public TopLevelImpl()
         {
             _keyboardHelper = new KeyboardEventsHelper<TopLevelImpl>(this);
             AutoresizingMask = UIViewAutoresizing.All;
             _keyboardHelper.ActivateAutoShowKeyboard();
+
+            Surfaces = new object[] { this };
         }
 
         [Export("hasText")]
@@ -76,8 +79,8 @@ namespace Avalonia.iOS
         {
             //Not supported
         }
-        
-        public IEnumerable<object> Surfaces => new object[] { this };
+
+        public IEnumerable<object> Surfaces { get; }
         
         public override void TouchesEnded(NSSet touches, UIEvent evt)
         {