Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce allocations for generated constructors #1075

Merged
merged 8 commits into from
Jan 19, 2022
Merged

Conversation

j0shuams
Copy link
Contributor

@j0shuams j0shuams commented Jan 12, 2022

For generated types, we often have a public constructor and then internal/base constructors. We reduce the space used when calling one of these public constructors by refactoring the relevant ObjectReference code. Instead of creating and immediately invoking a lambda to give us an object reference, we inline the code in the public constructor.

Also included is a refactoring of the generated "_lazyInterfaces" dictionary. We saw before that it was being initialized in those internal constructors, but without any references to local variables. We move them to the global level and use the Interlocked.CompareExchange pattern of lazy initialization, rather than the .NET Lazy type.

I have run some benchmarks and have two scenarios.

For the change to constructors and making/invoking a lambda, we only make this change for net5.0 code. The code to assign _inner is different for netstandard compatibility, making our optimization untenable. However, the change to the lazy interface initializations provides improvements to all runtimes (net5, net3, netstd2).

Net5.0

Codepath Master - time Topic - time Master - memory Topic - memory
Constructors 3.89 micro 3.74 micro 584 B 392 B
Lazy Interface 11.57 micro 10.53 micro 2752 B 1864 B

NetStandard2.0

Codepath Master - time Topic - time Master - memory Topic - memory
Constructors 17.59 micro 17.86 micro 3481 B 3453 B
Lazy Interface 10.98 micro 10.07 micro 3000 B 2024 B

@j0shuams j0shuams requested a review from manodasanW January 12, 2022 18:26
@j0shuams
Copy link
Contributor Author

Example of difference in generated code, taken from src\Projections\Windows\GeneratedFiles\net5.0\Windows.Storage.Streams.cs

Code generated on master branch

public sealed class DataReader : IDataReader, ...
{
        private IntPtr ThisPtr => _inner == null ? (((IWinRTObject)this).NativeObject).ThisPtr : _inner.ThisPtr;

        private IObjectReference _inner = null;

        private readonly Dictionary<Type, object> _lazyInterfaces;
        
        ...
        
        internal sealed class _IDataReaderFactory { ... }
        
        public DataReader(IInputStream inputStream) : this(((Func<IObjectReference>)(() => {
            IntPtr ptr = (_IDataReaderFactory.Instance.CreateDataReader(inputStream));
            try
            {
                return (ComWrappersSupport.GetObjectReferenceForInterface(ptr));
            }
            finally
            {
                MarshalInspectable<object>.DisposeAbi(ptr);
            }
        }))())
        {
            ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr);
            ComWrappersHelper.Init(_inner, false);
        }
        
        ...
        
        internal DataReader(IObjectReference objRef)
        {
            _inner = objRef.As(GuidGenerator.GetIID(typeof(IDataReader).GetHelperType()));


            _lazyInterfaces = new Dictionary<Type, object>(1)
            {
                {typeof(global::System.IDisposable), new Lazy<global::System.IDisposable>(() => (global::System.IDisposable)(object)new SingleInterfaceOptimizedObject(typeof(global::System.IDisposable), _inner ?? ((IWinRTObject)this).NativeObject))},
            };

        }
        
        ...
        
        private global::System.IDisposable AsInternal(InterfaceTag<global::System.IDisposable> _) =>  ((Lazy<global::System.IDisposable>)_lazyInterfaces[typeof(global::System.IDisposable)]).Value;


}

Code generated on topic branch jlarkin/objref-func

public sealed class DataReader : IDataReader, ...
{
        private IntPtr ThisPtr => _inner == null ? (((IWinRTObject)this).NativeObject).ThisPtr : _inner.ThisPtr;

        private IObjectReference _inner = null;
        
        private volatile global::System.IDisposable _lazy_global__System_IDisposable;

        private global::System.IDisposable Make__lazy_global__System_IDisposable()
        {
            global::System.Threading.Interlocked.CompareExchange(ref _lazy_global__System_IDisposable, (global::System.IDisposable)(object)new SingleInterfaceOptimizedObject(typeof(global::System.IDisposable), _inner ?? ((IWinRTObject)this).NativeObject), null);
            return _lazy_global__System_IDisposable;
        }

        ...
        
        internal sealed class _IDataReaderFactory { ... }
        
        public DataReader(IInputStream inputStream) 
        { 
        IntPtr ptr = (_IDataReaderFactory.Instance.CreateDataReader(inputStream)); 
        try 
        { 
        _inner = ComWrappersSupport.GetObjectReferenceForInterface(ptr); 
        } 
        finally 
        { 
        MarshalInspectable<object>.DisposeAbi(ptr); 
        }

        ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr);
        ComWrappersHelper.Init(_inner, false);
        }
        
        ...
        
        internal DataReader(IObjectReference objRef)
        {
            _inner = objRef.As(GuidGenerator.GetIID(typeof(IDataReader).GetHelperType()));

        }
        
        ... 
        
        private global::System.IDisposable AsInternal(InterfaceTag<global::System.IDisposable> _) =>  Make__lazy_global__System_IDisposable();

@j0shuams j0shuams merged commit 3bf2eaa into master Jan 19, 2022
@j0shuams j0shuams deleted the jlarkin/objref-func branch January 19, 2022 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants