Bug 40953

Summary: Bad performance using TypeBuilder
Product: [Mono] Runtime Reporter: Igor Chevdar <homuroll>
Component: ReflectionAssignee: Bugzilla <bugzilla>
Status: CONFIRMED ---    
Severity: normal CC: kumpera, mono-bugs+runtime, vargaz
Priority: Normal    
Version: unspecified   
Target Milestone: Future Cycle (TBD)   
Hardware: PC   
OS: Linux   
Tags: Is this bug a regression?: ---
Last known good build:
Attachments: The test program

Description Igor Chevdar 2016-05-07 16:45:30 UTC
Created attachment 15938 [details]
The test program

Hello.

I extensively use dynamic code generation with AssemblyBuilder/ModuleBuilder/TypeBuilder. I've always been using Microsoft.NET, but recently tried to run my projects with Mono on Ubuntu (my version of Ubuntu is 16.04 and version of Mono is 4.2.1). Surprisingly all of my performance tests shew that code is working much slower. I investigated the problem and found that any use of TypeBuilder results in bad performance. I tried to find some clues on the Internet but haven't found anything useful. I wrote rather simple example to demonstrate that. In that example there is class Closure used as argument of a DynamicMethod, but if we try to build that class dynamically with TypeBuilder suddenly the resulting code is working 6-8 times slower on my computer under x64 arch (Intel Core i7-4710MQ @ 2.5GHz, 16GB RAM). Under Microsoft.NET (I have 4.6 framework installed) there is no significant difference between these two versions.

To reproduce one needs to compile the attached file (I tried to compile it on both Visual Studio 2013 and MonoDevelop - results are very similar) and run it with Mono on Linux.
Comment 1 Igor Chevdar 2016-05-10 07:25:10 UTC
I managed to simplify the test program. Now it only calls the default constructor and still TypeBuilder works much slower than explicitly written.



public class Program
    {
        static void Main(string[] args)
        {
            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("test"), AssemblyBuilderAccess.Run);
            var module = assembly.DefineDynamicModule("zzz");

            var closureTypeBuilder = module.DefineType("Closure", TypeAttributes.Public | TypeAttributes.Class);
            
            var dynamicClosureType = closureTypeBuilder.CreateType();
            var staticClosureType = typeof(Closure);

            foreach(var closureType in new[] {staticClosureType, dynamicClosureType})
            {
                var compiledExp = CreateFunc(closureType);
                for(int i = 0; i < 10; ++i)
                    compiledExp();

                const int iterations = 1000001;
                var stopwatch = Stopwatch.StartNew();
                for(int i = 0; i < iterations; ++i)
                    compiledExp();
                stopwatch.Stop();
                Console.WriteLine("{0:0.000} millions runs per second", iterations * 1.0 / stopwatch.Elapsed.TotalSeconds / 1000000.0);
            }
        }

        private static Func<object> CreateFunc(Type closureType)
        {
            var lambdaMethod = new DynamicMethod("lambda", typeof(object), Type.EmptyTypes, typeof(string), true);
            var il = lambdaMethod.GetILGenerator();
            il.Emit(OpCodes.Newobj, closureType.GetConstructor(Type.EmptyTypes));
            il.Emit(OpCodes.Ret);
            var lambda = (Func<object>)lambdaMethod.CreateDelegate(typeof(Func<object>));
            return lambda;
        }

        public class Closure
        {
        }
    }
Comment 2 Zoltan Varga 2016-05-12 03:14:25 UTC
This is because of these in reflection.c:

	klass->has_finalize = 1;
	klass->has_finalize_inited = 1;

which makes the runtime think dynamic classes have a finalizer, making us use slower allocation functions.
Comment 3 Igor Chevdar 2016-05-12 08:50:34 UTC
Wow, that's interesting. Is there any reason behind that assumption? And will there be a way to configure that in the future?
Comment 4 Zoltan Varga 2016-05-12 10:04:19 UTC
Probly not, its just a bug.