【问题标题】:Do compiled expression trees leak?编译的表达式树会泄漏吗?
【发布时间】:2017-08-19 20:27:57
【问题描述】:

据我了解,JIT 代码在程序运行时永远不会从内存中释放出来。这是否意味着在表达式树上重复调用.Compile() 会泄漏内存?

这意味着只在静态构造函数中编译表达式树或以其他方式缓存它们,这可能不那么简单。对吧?

【问题讨论】:

  • 为什么要重复编译同一个表达式?也许提供一些代码示例?
  • 为什么假设.Compile() 在同一个表达式树上?
  • @Evk 因为有时您不会将它们缓存在Dictionary<> 或静态变量中...这是一个有趣的问题。
  • 我没有代码示例,我认为不需要它。问题是编译表达式树是否会不可逆地占用一些内存。这应该意味着,例如,每当用户在文本框中键入内容时,开发人员都需要小心生成和编译新树。

标签: c# .net memory-leaks expression expression-trees


【解决方案1】:

我尝试通过在后台连续生成表达式树然后在 GUI 线程中收集所有垃圾并监控已用空间来测试这一点。

几个小时后,内存使用量似乎稳定在 655000 字节左右。所以我会说使用表达式树是安全的。

如果有人想要我的 hacky 测试代码,这里是:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Windows.Forms;

namespace Experiments
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // Ensuring that always the same amount of memory is used for point storage.
            bytesUsed = new Queue<long>(1000);
            var points = chart1.Series[0].Points;
            for (var i = 0; i < 1000; ++i)
            {
                bytesUsed.Enqueue(0);
                points.Add(0);
            }


            thread = new Thread(ThreadMethod);
            thread.Start();
            timer1.Interval = 10000;
            timer1.Enabled = true;
            timer1_Tick(null, null);
        }

        private readonly Queue<long> bytesUsed;
        private void timer1_Tick(object sender, EventArgs e)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            bytesUsed.Dequeue();
            bytesUsed.Enqueue(GC.GetTotalMemory(false));

            var points = chart1.Series[0].Points;
            points.Clear();
            foreach (var value in bytesUsed)
                points.Add(value);
        }

        private Thread thread;
        private volatile bool stopping;
        private void ThreadMethod()
        {
            var random = new Random();

            while (!stopping)
            {
                var constant = Expression.Constant(random.Next(), typeof(int));
                var param = Expression.Parameter(typeof(int));

                var mul = Expression.Multiply(param, constant);
                var add = Expression.Multiply(mul, param);
                var sub = Expression.Subtract(add, constant);

                var lambda = Expression.Lambda<Func<int, int>>(sub, param);
                var compiled = lambda.Compile();
            }
        }

        protected override void Dispose(bool disposing)
        {
            stopping = true;
            if (thread != null && disposing)
                thread.Join();

            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

【讨论】:

    【解决方案2】:

    它们可能是 GCed...LambdaExpression.Compile() 使用 LambdaCompiler.Compile(LambdaExpression, DebugInfoGenerator) 类,through one of the LambdaCompiler constructors 使用 DynamicMethod,来自 MSDN:

    定义并表示可以编译、执行和丢弃的动态方法。 丢弃的方法可用于垃圾回收。

    【讨论】:

    • 所以编译后的表达式没有被 GC 处理?它读起来就像只有丢弃的表达式被 GC'ed。
    • @PatrickHofman 恰恰相反......他们 GCed......对于“dicarded”,我认为他们的意思是“不再被引用”。任何地方都没有“Discard()”方法,所以对描述的任何其他阅读都是没有意义的。
    猜你喜欢
    • 2017-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-08
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    相关资源
    最近更新 更多