C#:设计一个更快的Assert函数,它只在触发时格式化错误字符串



在我每秒调用数万次的代码的一部分中,我有几个断言如下:

Assert(value1 > value2, $"{value1} was not greater than {value2}");

我的性能很差,因为它不断调用我的错误字符串$"{value1} was not greater than {value2}"上的String.Format()。我运行了评测程序来验证这一点:这些调用的String.Format()占用了我总时间的50%。

我想设计我自己的断言函数,在实际触发断言之前,它不需要格式化错误字符串。当然,最简单的方法是:

if (value1 > value2) 
{
Debug.LogError($"{value1} was not greater than {value2}");
}

但我希望我可以在一行中完成这项工作,我可以在发布版本中轻松删除它。通过一个函数,我可以使用ConditionalAttribute使它们仅在调试构建时处于活动状态。

也许表达式中有一些技巧可以让我推迟创建错误字符串?

一如既往,过早优化是万恶之源。从您的问题中也不清楚为什么不简单地使用[Conditional("DEBUG")]属性——如果您想在发布版本中删除日志,这就是方法,句号

(更新(来源。NET 4.6以后,您可以将参数指定为FormattableString,如您的问题下面的注释所述:

void Assert(bool condition, FormattableString message)
{
if (condition)
DoSomething(message.ToString());
}
// this will actually create an instance of FormattableString
// under the hood, which will not be formatted until you 
// call FormattableString.ToString() inside the method
Assert(value1 > value2, $"{value1} was not greater than {value2}");

如果这个条件真的很少得到满足,并且你想避免创建FormattableString实例,并且你愿意再向前迈出疯狂的一步,那么你可以创建一大堆类似于的通用方法

void Assert<T1>(bool condition, string format, T1 a);
void Assert<T1, T2>(bool condition, string format, T1 a, T2 b);
void Assert<T1, T2, T3>(bool condition, string format, T1 a, T2 b, T3 c);
...
// if value1 is `int`, and value2 is `double`, this will resolve
// into Assert<int, double>(bool, string, int, double) -- all parameters
// are simply passed into the method, and nothing is instantiated
Assert(value1 > value2, "{0} was not greater than {1}", value1, value2);

由于这些将被编译成接受实际的强类型参数的方法,因此不会以这种方式进行任何额外的堆实例化,因此如果条件不满足,您实际上没有性能负担。

快速基准测试表明,当条件满足50%的时间(random.NextDouble() > 0.5(时,第一次更改(使用FormattableString而不是string(快约1.6倍。采用通用方法的方法比快2倍

  • 5000000次迭代,在50%的情况下进行日志记录:
    • 字符串插值:5 s
    • FormattableString:3秒
    • 通用方法:2.5秒

如果条件很少得到满足(5%的时间(,第一次优化会导致约8倍的加速,而通用方法的速度约为14倍,分配更少:

  • 5000000次迭代,在5%的情况下进行日志记录:
    • 字符串插值:3.5秒
    • FormattableString:0.5秒
    • 通用方法:0.25秒

如果您没有使用System.Diganostics函数的特定要求,我建议您使用日志库。

日志记录库提供了这些功能,并为您提供了登录文件和选择要在日志中获得的信息级别的选项。此外,您还可以在应用程序运行时通过更改配置来更改日志级别。

一些流行的是:

  • NLog
  • Serilog
  • log4Net

通过使用参数化参数而不是字符串插值,可以显著提高性能。

static void Main(string[] args)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < 10000000; i++)
{
Assert(i % 1000000 != 700, $"Oops on {i}");
}
Console.WriteLine("{0}", sw.Elapsed);
}
[System.Diagnostics.Conditional("DEBUG")]
static void Assert(bool cond, string msg, params object[] args)
{
if (!cond)
Console.WriteLine(msg, args);
}

这段代码在我的系统上输出00:00:01.7812783,表明运行大约需要1.8秒。

如果将Assert行更改为:

Assert(i % 1000000 != 700, "Oops on {0}", i);

您得到了相同的行为,但性能更好,报告:00:00:00.1127145

相关内容

最新更新