过度使用委托对性能来说是一个坏主意吗?

发布于 2024-08-01 15:03:15 字数 641 浏览 4 评论 0原文

考虑以下代码:

if (IsDebuggingEnabled) { 
   instance.Log(GetDetailedDebugInfo()); 
}

GetDetailedDebugInfo() 可能是一个昂贵的方法,因此我们只想在调试模式下运行时调用它。

现在,更干净的替代方案是编写如下代码:

instance.Log(() => GetDetailedDebugInfo());

其中 .Log() 定义如下:

public void Log(Func<string> getMessage)
{
    if (IsDebuggingEnabled) 
    {
        LogInternal(getMessage.Invoke());
    }
}

我关心的是性能,初步测试并没有显示第二种情况特别昂贵,但我不想如果负载增加,会遇到任何意外。

哦,请不要建议条件编译,因为它不适用于这种情况。

(PS:我直接在 StackOverflow 提问文本区域中编写了代码,所以如果有细微的错误并且无法编译,请不要责怪我,你明白了:)

Consider the following code:

if (IsDebuggingEnabled) { 
   instance.Log(GetDetailedDebugInfo()); 
}

GetDetailedDebugInfo() may be an expensive method, so we only want to call it if we're running in debug mode.

Now, the cleaner alternative is to code something like this:

instance.Log(() => GetDetailedDebugInfo());

Where .Log() is defined such as:

public void Log(Func<string> getMessage)
{
    if (IsDebuggingEnabled) 
    {
        LogInternal(getMessage.Invoke());
    }
}

My concern is with performance, preliminary testing doesn't show the second case to be particularly more expensive, but I don't want to run into any surprises if load increases.

Oh, and please don't suggest conditional compilation because it doesn't apply to this case.

(P.S.: I wrote the code directly in the StackOverflow Ask a Question textarea so don't blame me if there are subtle bugs and it doesn't compile, you get the point :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

许仙没带伞 2024-08-08 15:03:15

不,应该不会有什么不好的表现。 毕竟,您只会在性能不是最重要的调试模式下调用它。 实际上,您可以删除 lambda 并仅传递方法名称,以消除不必要的中间匿名方法的开销。

请注意,如果您想在调试版本中执行此操作,您可以添加 [Conditional("DEBUG")] 日志方法的属性。

No, it shouldn't have a bad performance. After all, you'll be calling it only in debug mode where performance is not at the forefront. Actually, you could remove the lambda and just pass the method name to remove the overhead of an unnecessary intermediate anonymous method.

Note that if you want to do this in Debug builds, you can add a [Conditional("DEBUG")] attribute to the log method.

轮廓§ 2024-08-08 15:03:15

性能上有差异。 它的重要性取决于代码的其余部分,因此我建议在开始优化之前进行分析。

话虽如此,对于您的第一个示例:

if (IsDebuggingEnabled) 
{ 
    instance.Log(GetDetailedDebugInfo()); 
}

如果 IsDebuggingEnabled 是静态只读,那么检查将被丢弃,因为它知道它永远不会改变。 这意味着如果 IsDebuggingEnabled 为 false,上述示例对性能的影响为零,因为 JIT 完成后代码将消失。

instance.Log(() => GetDetailedDebugInfo());

public void Log(Func<string> getMessage)
{
    if (IsDebuggingEnabled) 
    {
        LogInternal(getMessage.Invoke());
    }
}

每次调用instance.Log时都会调用该方法。 哪个会更慢。

但在花时间进行这种微观优化之前,您应该分析您的应用程序或运行一些性能测试,以确保这实际上是您应用程序中的瓶颈。

There is a difference in performance. How significant it is will depend on the rest of your code so I would recommend profiling before embarking on optimisations.

Having said that for your first example:

if (IsDebuggingEnabled) 
{ 
    instance.Log(GetDetailedDebugInfo()); 
}

If IsDebuggingEnabled is static readonly then the check will be jitted away as it knows it can never change. This means that the above sample will have zero performance impact if IsDebuggingEnabled is false, because after the JIT is done the code will be gone.

instance.Log(() => GetDetailedDebugInfo());

public void Log(Func<string> getMessage)
{
    if (IsDebuggingEnabled) 
    {
        LogInternal(getMessage.Invoke());
    }
}

The method will be called every time instance.Log is called. Which will be slower.

But before expending time with this micro optimization you should profile your application or run some performance tests to make sure this is actually a bottle neck in your application.

山川志 2024-08-08 15:03:15

我希望获得一些有关这种情况下性能的文档,但似乎我得到的只是有关如何改进我的代码的建议......似乎没有人读过我的 PS - 没有给你分。

所以我写了一个简单的测试用例:

    public static bool IsDebuggingEnabled { get; set; }


    static void Main(string[] args)
    {
        for (int j = 0; j <= 10; j++)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i <= 15000; i++)
            {
                Log(GetDebugMessage);
                if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }

        Console.ReadLine();
        for (int j = 0; j <= 10; j++)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i <= 15000; i++)
            {
                if (IsDebuggingEnabled) GetDebugMessage();
                if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
        Console.ReadLine();
    }

    public static string GetDebugMessage()
    {
        StringBuilder sb = new StringBuilder(100);
        Random rnd = new Random();
        for (int i = 0; i < 100; i++)
        {
            sb.Append(rnd.Next(100, 150));
        }
        return sb.ToString();
    }

    public static void Log(Func<string> getMessage)
    {
        if (IsDebuggingEnabled)
        {
            getMessage();
        }
    }

两个版本之间的时序似乎完全相同。
我在第一种情况下得到 145 毫秒,在第二种情况下得到 145 毫秒

看起来我回答了我自己的问题。

I was hoping for some documentation regarding performance in such cases, but it seems that all I got were suggestions on how to improve my code... No one seems to have read my P.S. - no points for you.

So I wrote a simple test case:

    public static bool IsDebuggingEnabled { get; set; }


    static void Main(string[] args)
    {
        for (int j = 0; j <= 10; j++)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i <= 15000; i++)
            {
                Log(GetDebugMessage);
                if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }

        Console.ReadLine();
        for (int j = 0; j <= 10; j++)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i <= 15000; i++)
            {
                if (IsDebuggingEnabled) GetDebugMessage();
                if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
        Console.ReadLine();
    }

    public static string GetDebugMessage()
    {
        StringBuilder sb = new StringBuilder(100);
        Random rnd = new Random();
        for (int i = 0; i < 100; i++)
        {
            sb.Append(rnd.Next(100, 150));
        }
        return sb.ToString();
    }

    public static void Log(Func<string> getMessage)
    {
        if (IsDebuggingEnabled)
        {
            getMessage();
        }
    }

Timings seem to be exactly the same between the two versions.
I get 145 ms in the first case, and 145 ms in the second case

Looks like I answered my own question.

情释 2024-08-08 15:03:15

您还可以这样做:

// no need for a lambda
instance.Log(GetDetailedDebugInfo)

// Using these instance methods on the logger
public void Log(Func<string> detailsProvider)
{
    if (!DebuggingEnabled)
        return;

    this.LogImpl(detailsProvider());
}

public void Log(string message)
{
    if (!DebuggingEnabled)
        return;

    this.LogImpl(message);
}

protected virtual void LogImpl(string message)
{
    ....
}

You can also do this:

// no need for a lambda
instance.Log(GetDetailedDebugInfo)

// Using these instance methods on the logger
public void Log(Func<string> detailsProvider)
{
    if (!DebuggingEnabled)
        return;

    this.LogImpl(detailsProvider());
}

public void Log(string message)
{
    if (!DebuggingEnabled)
        return;

    this.LogImpl(message);
}

protected virtual void LogImpl(string message)
{
    ....
}
长发绾君心 2024-08-08 15:03:15

我相信委托会创建一个新线程,因此您关于它提高性能的说法可能是正确的。
为什么不像 Dav 建议的那样设置测试运行,并密切关注应用程序生成的线程数量,您可以使用 Process Explorer 来实现这一点。

不挂断! 我已经纠正了! 当您使用“BeginInvoke”时,委托仅使用线程...因此我的上述评论不适用于您使用它们的方式。

I believe delegates create a new thread, so you may be right about it increasing performance.
Why not set up a test run like Dav suggested, and keep a close eye on the number of threads spawned by your app, you can use Process Explorer for that.

Hang on! I've been corrected! Delegates only use threads when you use 'BeginInvoke'... so my above comments don't apply to the way you're using them.

一袭白衣梦中忆 2024-08-08 15:03:15

直接调用 getMessage 委托,而不是对其调用 Invoke。

if(IsDebuggingEnabled)
{
  LogInternal(getMessage());
}

您还应该对 getMessage 添加 null 检查。

Call getMessage delegate directly instead of calling Invoke on it.

if(IsDebuggingEnabled)
{
  LogInternal(getMessage());
}

You should also add null check on getMessage.

拿命拼未来 2024-08-08 15:03:15

标准答案:

  • 如果你必须这样做,你就必须这样做。
  • 循环 10^9 次,查看秒表,& 这告诉你需要多少纳秒。
  • 如果您的程序很大,那么其他地方可能会遇到更大的问题。

Standard answers:

  • If you gotta do it, you gotta do it.
  • Loop it 10^9 times, look at a stopwatch, & that tells you how many nanoseconds it takes.
  • If your program is big, chances are you have bigger problems elsewhere.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文