我试图避免计算字符串并将其传递给单独的静态类,如果该类中设置的标志无论如何都会跳过使用字符串的话.用System.Diagnostics.Stopwatch进行基本性能测量.一个C#.NET框架4.8库,它是语音识别应用程序的插件.

该插件对一个静态类进行多次调用,传递各种求值字符串.根据该类中设置的静态状态过滤不同的调用,因此仅当匹配的静态布尔值为真时才使用字符串.例如

只有当Logger.MenuItems为真时,Logger.MenuWrite(string msg)才会记录该字符串.

从秒表度量来看,我认为无论Logger类是否不会使用字符串,都会对它们进行计算(也就是说,我不认为JIT没有内联).虽然这对性能的影响很小,但我正在try 在扩展它的过程中尽可能地获得每一毫秒.

到目前为止,我try 和测试的内容如下:

我在一些循环周围添加了秒表测量,这些循环在Logger.MenuItems为假的情况下进行了大量的Logger.MenuWrite()次调用,然后使用Check for Logger.MenuItems对每个调用进行了内联,并看到了明确的、可重复的差异-使用只有一个求值字段的字符串,每Logger.MenuWrite()0次调用大约减少一毫秒.

我首先在Logger类中的静态方法上try 了[MethodImpl(MethodImplOptions.AggressiveInlining)],如下所示:

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void MenuWrite(string msg)
        {
            if (s_MenuItems )
            {   vaProxy.WriteToLog(s_Prefix + msg); }
        }

这将循环的时间减少了一半,但这仍然比我在循环中进行实际直接判断多出约1/2毫秒,例如:

if (Logger.MenuItems) { Logger.MenuWrite(msg); }

所以我try 使用Delegates,如下所示:

        static Action<string> LogIfMenu = (msg) =>
        {
            if (Logger.MenuItems) { Logger.MenuWrite(msg); }
        };

但使用LogIfMenu呼叫的性能似乎与使用[MethodImpl(MethodImplOptions.AggressiveInlining)]的性能相同或更差.

有没有想过是什么原因导致了Perf命中-字符串求值/创建、方法调用,或者其他什么?我会感激任何建议或 Select ,除了手动内联所有的呼叫.谢谢.

编辑:

  • 通过计算字符串,我的意思是拉入其他数据,如:$"Passed: {Cmd.Observable} and {Cmd.Dist}"
  • 我将try 查看列出的其他Perf工具,但确实需要测量发布版本中所用的时间
  • 恐怕我必须使用动态对象来记录日志(log),因为这是我的插件所提供的应用程序.也就是说,我不认为这是这个问题的一部分,所以从代码片段中删除了它.

编辑:将可重现的小示例修改为控制台应用程序.

// File1.cs
namespace CS_Console_Test_05
{
    static public class Logger
    {
        public static bool MenuItems = false;
        public static void MenuWrite(string msg)
        {
            if (MenuItems) { Console.WriteLine(msg); }
        }
    }
}

// File2.cs
namespace CS_Console_Test_05
{
    internal class Program
    {
        public static void LoopMessagesInline()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 10000; i++)
            {
                if (Logger.MenuItems)
                { Logger.MenuWrite($"Counting Down to the time {sw.Elapsed}"); }
            }
            sw.Stop();
            Console.WriteLine($"Inline Elapsed = {sw.Elapsed}");
        }

        public static void LoopMessagesCall()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 10000; i++)
            {
                Logger.MenuWrite($"Counting Down to the time {sw.Elapsed}");
            }
            sw.Stop();
            Console.WriteLine($"Called Elapsed = {sw.Elapsed}");
        }

        static void Main(string[] args)
        {
            do
            {
                Console.WriteLine("Enter Value for MenuItems:");
                string miRead = Console.ReadLine();
                Logger.MenuItems = (miRead.Equals("Kludge"));    // so JIT won't know if true or false
                Console.WriteLine("'x' to quit, SPACE for Inline, nothing for Call, then ENTER: ");
                string way = Console.ReadLine();
                way = way.ToLower();
                if (way.Equals(" "))
                { LoopMessagesCall(); }
                else if (way.Equals("x"))
                { return; }
                else
                { LoopMessagesInline(); }

            } while (true);
        }
    }
}

调用LoopMessageInline()大约需要7-8毫秒.调用LoopMessageCall()所需时间不到1毫秒.

如上所述,无论是MethodImplOptions.AggressiveInlining还是使用Delegates似乎都无济于事.

推荐答案

首先,使用适当的基准工具--如BenchmarkDotNet.

我提出了以下基准:

namespace CS_Console_Test_05
{
    static public class Logger
    {
        public static bool MenuItems = false;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void MenuWrite(string msg)
        {
            if (MenuItems)
            {
                Console.WriteLine(msg);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void MenuWriteFormattableString(FormattableString msg)
        {
            if (MenuItems)
            {
                Console.WriteLine(msg);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void MenuWriteFunc(Func<string> msg)
        {
            if (MenuItems)
            {
                Console.WriteLine(msg());
            }
        }
    }
}
[MemoryDiagnoser]
public class LoggerWrapperBench
{
    public static string Value = "TestVal";
    private const int Iterations = 1000;

    [Benchmark]
    public void LoopMessagesInline()
    {
        for (int i = 0; i < Iterations; i++)
        {
            if (Logger.MenuItems)
            {
                Console.WriteLine($"Counting Down to the time {Value}");
            }
        }
    }

    [Benchmark]
    public void LoopMessagesInlineFormatableString()
    {
        for (int i = 0; i < Iterations; i++)
        {
            Logger.MenuWriteFormattableString($"Counting Down to the time {Value}");
        }
    }
    
    [Benchmark]
    public void LoopMessagesInlineFunc()
    {
        for (int i = 0; i < Iterations; i++)
        {
            Logger.MenuWriteFunc(() => $"Counting Down to the time {Value}");
        }
    }

    [Benchmark]
    public void LoopMessagesCall()
    {
        for (int i = 0; i < Iterations; i++)
        {
            Logger.MenuWrite($"Counting Down to the time {Value}");
        }
    }
}

它在我的机器上提供了:

Method Mean Error StdDev Gen0 Allocated
LoopMessagesInline 524.7 ns 10.10 ns 10.37 ns - -
LoopMessagesInlineFormatableString 10,908.3 ns 215.37 ns 328.89 ns 10.1929 64000 B
LoopMessagesInlineFunc 1,031.8 ns 18.34 ns 21.12 ns - -
LoopMessagesCall 14,523.6 ns 286.28 ns 391.86 ns 14.0228 88000 B

使惰性函数方法最接近内联方法(尽管我有点奇怪它为什么没有分配任何东西).

请注意,内联在MenuWriteMenuWriteFormattableString的情况下对字符串计算没有太大影响,因为:

var s = DoSomething(); // like build string
if(...)
{
    Console.WriteLine(s);
}

if(...)
{
    Console.WriteLine(DoSomething());
}

在一般情况下(由于函数调用的可能副作用),它们在功能上不是等价的,所以内联不应该改变程序的正确性,所以调用字符串格式化(至少这是我关于这个主题的理论).

UPD

还有一种方法值得一提的是(尽管我不能让它执行得更快,在有多个内插元素的情况下,它甚至可以更慢地预成型)-从.NET 6开始,您可以创建一个custom interpolated string handler:

[InterpolatedStringHandler]
public readonly ref struct LogInterpolatedStringHandler
{
    readonly StringBuilder? _builder;

    public LogInterpolatedStringHandler(int literalLength, int formattedCount)
    {
        if (Logger.MenuItems)
        {
            _builder = new StringBuilder(literalLength);
        }
    }

    public void AppendLiteral(string s) => _builder?.Append(s);

    public void AppendFormatted<T>(T t) => _builder?.Append(t?.ToString());

    internal string GetFormattedText()
    {
        if (_builder is not null)
        {
            var format = _builder.ToString();
            Console.WriteLine(format);
            return format;
        }

        return string.Empty;
    }
}

和 usage:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MenuWriteInterpolatedStringHandler(ref LogInterpolatedStringHandler msg)
{
    if(MenuItems) msg.GetFormattedText();
}
[Benchmark]
public void LoopMenuWriteInterpolatedStringHandler()
{
    for (int i = 0; i < Iterations; i++)
    {
        Logger.MenuWriteInterpolatedStringHandler($"Counting Down to the time {Value}");
    }
}

它在我的机器上提供了:

Method Mean Error StdDev Allocated
LoopMenuWriteInterpolatedStringHandler 1,690.0 ns 32.63 ns 36.27 ns -
LoopMessagesInline 534.2 ns 10.39 ns 15.22 ns -

Csharp相关问答推荐

.NET框架4.7.2项目如何引用.NET Core 2.2库?

需要深入了解NpgSQL DateTimeOffset处理

有没有一种方法可以防止在编译时在MicrosoftC或非单线程上下文中调用方法?

ASP.NET MVC中创建视图的过滤器

如何在NodaTime中为Instant添加一年?

返回TyedResults.BadRequest<;字符串>;时问题详细信息不起作用

不带身份的Blazor服务器.Net 8 Cookie身份验证

WinForms在Linux上的JetBrains Rider中的应用

在不添加不必要的尾随零的情况下本地化浮点型?

当我没有此令牌时,为什么语法报告EOF错误?

如何管理Azure认证客户端响应和证书 fingerprint

源代码生成器:CS8795分部方法';Class1.GetS2(字符串)';必须有实现部分,因为它有可访问性修饰符?

如何避免在.NET中将日志(log)写入相对路径

如何从Entity Framework Core中填充ListIInterface

System.NotSupportdException:流不支持读取

C#命名管道-编码错误?

Visual Studio 17.8.0制表符自动完成问题--三缩进

使DefaultIfEmpty返回空

使用免费的DotNet库从Azure函数向Azure文件共享上的现有Excel文件追加行

无法使用直接URL通过PictureBox.ImageLocation加载图像