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

The call time of the static ProxyGenerator's CreateClassProxyType method is gradually increasing. #486

Closed
maliming opened this issue Mar 24, 2020 · 4 comments

Comments

@maliming
Copy link

⚠️ Use of a single ProxyGenerator's instance: If you have a long running process (web site, windows service, etc.) and you have to create many dynamic proxies, you should make sure to reuse the same ProxyGenerator instance. If not, be aware that you will then bypass the caching mechanism. Side effects are high CPU usage and constant increase in memory consumption.

Castle.Core version 4.4.0

I have a lot of mvc controllers for net core 3.1(netcoreapp3.1).

public abstract class MyControllerBase : Controller
{
}

public class Test0Controller : MyControllerBase
{
	[HttpGet]
	[Route("Get")]
	public string TestMethod()
	{
		return "test";
	}
}

public class Test1Controller : MyControllerBase
{
	[HttpGet]
	[Route("Get")]
	public string TestMethod()
	{
		return "test";
	}
}

//public class Test200Controller : MyControllerBase

But the result of static ProxyGenerator and creating a new ProxyGenerator is different.
The call time of the static ProxyGenerator's CreateClassProxyType method is gradually increasing.

public class Program
{
	private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();
	
	public static void Main(string[] args)
	{
		foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(MyControllerBase).IsAssignableFrom(x)))
		{
			var stopwatch1 = Stopwatch.StartNew();
			ProxyGenerator.ProxyBuilder.CreateClassProxyType(
				type,
				Type.EmptyTypes,
				ProxyGenerationOptions.Default);
			stopwatch1.Stop();

			var stopwatch2 = Stopwatch.StartNew();
			new ProxyGenerator().ProxyBuilder.CreateClassProxyType(
				type,
				Type.EmptyTypes,
				ProxyGenerationOptions.Default);
			stopwatch2.Stop();

			
			Console.WriteLine("static ProxyGenerator=>ElapsedMilliseconds:" + stopwatch1.ElapsedMilliseconds + "\t" + "new ProxyGenerator=>ElapsedMilliseconds:" + stopwatch2.ElapsedMilliseconds);
		}
	
	}
}

Console output:

static ProxyGenerator=>ElapsedMilliseconds:86   new ProxyGenerator=>ElapsedMilliseconds:64
static ProxyGenerator=>ElapsedMilliseconds:136  new ProxyGenerator=>ElapsedMilliseconds:62
static ProxyGenerator=>ElapsedMilliseconds:154  new ProxyGenerator=>ElapsedMilliseconds:65
static ProxyGenerator=>ElapsedMilliseconds:209  new ProxyGenerator=>ElapsedMilliseconds:59
static ProxyGenerator=>ElapsedMilliseconds:221  new ProxyGenerator=>ElapsedMilliseconds:59
static ProxyGenerator=>ElapsedMilliseconds:250  new ProxyGenerator=>ElapsedMilliseconds:58
static ProxyGenerator=>ElapsedMilliseconds:274  new ProxyGenerator=>ElapsedMilliseconds:76
static ProxyGenerator=>ElapsedMilliseconds:295  new ProxyGenerator=>ElapsedMilliseconds:55
static ProxyGenerator=>ElapsedMilliseconds:318  new ProxyGenerator=>ElapsedMilliseconds:55
static ProxyGenerator=>ElapsedMilliseconds:352  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:376  new ProxyGenerator=>ElapsedMilliseconds:54
static ProxyGenerator=>ElapsedMilliseconds:405  new ProxyGenerator=>ElapsedMilliseconds:55
static ProxyGenerator=>ElapsedMilliseconds:413  new ProxyGenerator=>ElapsedMilliseconds:55
static ProxyGenerator=>ElapsedMilliseconds:450  new ProxyGenerator=>ElapsedMilliseconds:55
static ProxyGenerator=>ElapsedMilliseconds:471  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:513  new ProxyGenerator=>ElapsedMilliseconds:59
static ProxyGenerator=>ElapsedMilliseconds:528  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:565  new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:588  new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:611  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:643  new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:690  new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:687  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:722  new ProxyGenerator=>ElapsedMilliseconds:63
static ProxyGenerator=>ElapsedMilliseconds:748  new ProxyGenerator=>ElapsedMilliseconds:55
static ProxyGenerator=>ElapsedMilliseconds:761  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:799  new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:830  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:847  new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:886  new ProxyGenerator=>ElapsedMilliseconds:58
static ProxyGenerator=>ElapsedMilliseconds:957  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:979  new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:1008 new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:1036 new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:1052 new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:1094 new ProxyGenerator=>ElapsedMilliseconds:56
@stakx
Copy link
Member

stakx commented Mar 24, 2020

Assuming that your benchmark method gives sufficiently accurate results, I am curious how they would change if you...

  • ran this on a different runtime / runtime version;
  • put each controller type in a separate assembly;
  • tacked a .Reverse() onto the expression you're iterating over.

The first of these would tell us if there has been a performance regression in .NET Core 3.1 reflection.

Either of the last two might tell us if the degrading perf has to do with the fact that later types' metadata is placed further back in the IL metadata tables (which might matter if reflection does lots of linear table scans). This is admittedly a wild guess, but would be interesting to know.

@maliming
Copy link
Author

hi @stakx

  1. I changed the runtime from netcoreapp3.1 to netcoreapp2.2 and netcoreapp2.1.
  2. I created 10 class libraries, and each library has 1 controller.
  3. add Reverse.

But the result is still that the execution time of the static ProxyGenerator is gradually increasing.

10 controllers can let us see the growth trend.

static ProxyGenerator=>ElapsedMilliseconds:170  new ProxyGenerator=>ElapsedMilliseconds:58
static ProxyGenerator=>ElapsedMilliseconds:71   new ProxyGenerator=>ElapsedMilliseconds:56
static ProxyGenerator=>ElapsedMilliseconds:81   new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:124  new ProxyGenerator=>ElapsedMilliseconds:59
static ProxyGenerator=>ElapsedMilliseconds:155  new ProxyGenerator=>ElapsedMilliseconds:59
static ProxyGenerator=>ElapsedMilliseconds:171  new ProxyGenerator=>ElapsedMilliseconds:58
static ProxyGenerator=>ElapsedMilliseconds:190  new ProxyGenerator=>ElapsedMilliseconds:58
static ProxyGenerator=>ElapsedMilliseconds:210  new ProxyGenerator=>ElapsedMilliseconds:59
static ProxyGenerator=>ElapsedMilliseconds:243  new ProxyGenerator=>ElapsedMilliseconds:57
static ProxyGenerator=>ElapsedMilliseconds:255  new ProxyGenerator=>ElapsedMilliseconds:57

@stakx
Copy link
Member

stakx commented Mar 24, 2020

I've run some tests by wrapping various DynamicProxy code segments with Stopwatch, trying to identify those that get gradually slower. From what I can tell, the performance degradation does not happen in DynamicProxy's own code, but in the runtime — System.Reflection.Emit, to be more precise.

For example, this line of code which calls ModuleBuilder.DefineType gets gradually slower over time.

If you look at the types you're proxying, you'll notice that they have a ton of methods. The base class System.Web.Mvc.Controller defines more than 100 methods. IIRC, DynamicProxy will create a separate invocation type (implementations for IInvocation) for each and every one of them. So if you have 100 controller types, and every one of them has 100 methods, DynamicProxy will over time emit 10,000 invocation types (plus 100 proxy types).

I don't think there's much we can do here. Without knowing your usage scenario in detail, I would suggest that you check whether it is truly necessary to create Controller proxies. Perhaps there's a way to proxy much slimmer interface types instead (perhaps System.Web.Mvc.IController?), then inject the resulting proxies somewhere?

@maliming
Copy link
Author

@stakx Thanks for your guidance 👍 I will try another way to avoid the above problems.

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

No branches or pull requests

2 participants