C# 日志记录的性能技巧
Posted
技术标签:
【中文标题】C# 日志记录的性能技巧【英文标题】:Performance Tricks for C# Logging 【发布时间】:2011-02-06 20:34:50 【问题描述】:我正在研究 C# 日志记录,如果消息低于日志记录阈值,我不希望我的日志消息花费任何时间进行处理。我能看到 log4net 所做的最好的事情是在评估日志参数之后进行阈值检查。
例子:
_logger.Debug( "My complicated log message " + thisFunctionTakesALongTime() + " will take a long time" )
即使阈值高于 Debug,仍然会评估 thisFunctionTakesALongTime。
在 log4net 中你应该使用 _logger.isDebugEnabled 所以你最终会得到 p>
if( _logger.isDebugEnabled )
_logger.Debug( "Much faster" )
我想知道是否有更好的 .net 日志记录解决方案,无需在每次我想记录时都进行检查。
在 C++ 中我可以这样做
LOG_DEBUG( "My complicated log message " + thisFunctionTakesALongTime() + " will take no time" )
因为我的 LOG_DEBUG 宏会自行检查日志级别。这让我可以在整个应用程序中使用 1 行日志消息,这是我非常喜欢的。有人知道在 C# 中复制这种行为的方法吗?
【问题讨论】:
【参考方案1】:如果您可以面向 .NET 3.5 (C# 3.0),则可以使用 extension methods 包装 if
语句。
所以你可以做等效的“宏”:
logger.Log_Debug("Much faster");
logger.Log_Debug(() => "My complicated log message " + thisFunctionTakesALongTime() + " will take no time" );
通过在这个方法中包装检查:
public class Log4NetExtensionMethods
// simple string wrapper
public void Log_Debug(this log4net.ILog logger, string logMessage)
if(logger.isDebugEnabled)
logger.Debug(logMessage);
// this takes a delegate so you can delay execution
// of a function call until you've determined it's necessary
public void Log_Debug(this log4net.ILog logger, Func<string> logMessageDelegate)
if(logger.isDebugEnabled)
logger.Debug(logMessageDelegate());
【讨论】:
但是如果我正在评估函数以获取日志消息,这不会同样慢吗?如果日志级别低于我的阈值,我想避免所有函数评估。 您是正确的,您需要延迟函数评估。查看我使用匿名委托的更新。 D'oh,这也是我的回答。我应该警告你,如果你正在寻找速度,你不想使用代表。如果if (logger.isDebugEnabled)
围绕任何昂贵的东西,您应该接受打击并使用。没有既高性能又优雅的解决方案。
@bobbymcr:性能是相对的。委托调用与任何接口函数调用处于同一级别,thisFunctionTakesALongTime()
可以运行需要数天的蒙特卡罗模拟。
@Jeff:我指的是使用委托进行条件评估与简单的布尔检查之间的性能。请参阅下面的答案——在未启用日志记录时使用 lambda 会降低 20% 以上的性能。简单的规则是,如果性能至关重要,则不要使用后期绑定。【参考方案2】:
17.4.2 条件属性
Conditional 属性启用条件方法的定义。 Conditional 属性通过测试条件编译符号来指示条件。根据在调用点是否定义了此符号,是否包含或省略对条件方法的调用。如果定义了符号,则包含调用;否则,调用(包括调用参数的评估)将被省略。
[ Conditional("DEBUG") ]
public static void LogLine(string msg,string detail)
Console.WriteLine("Log: 0 = 1",msg,detail);
public static void Main(string[] args)
int Total = 0;
for(int Lp = 1; Lp < 10; Lp++)
LogLine("Total",Total.ToString());
Total = Total + Lp;
【讨论】:
这很有趣,我会做更多的研究。 MSDN:msdn.microsoft.com/en-us/library/aa664622%28VS.71%29.aspx 我认为更好的日志库已经使用了这种技术。 仅仅因为您正在调用“logger.debug”方法并不意味着您实际上是在调试模式下运行。名称“调试”实际上只是应用程序开发人员定义的任意阈值的方法名称。如果 Log4Net、EnterpriseLibrary 等...实际上使用任何条件属性,我会感到惊讶,因为该属性依赖于编译符号的存在。 创建两个版本的 log 方法 - 有和没有条件编译,即 LogLine() 和 LogLineConditional(),因此您可以选择使用哪一个,例如在性能敏感区域使用 LogLineConditional()。使用 not "DEBUG" 的条件字符串也很有用,因此可以与 DEBUG 构建分开控制。【参考方案3】:这里的问题是所有方法参数都必须在方法被调用之前进行评估。鉴于您使用的语法,没有办法解决这个问题。由于 C# 没有真正的预处理器或宏,因此您无法执行“LOG_DEBUG”之类的操作。你能做的最好的就是按照建议使用if (logger.isDebugEnable)
。
我唯一能想到的可能是使用 lambda 表达式之类的东西来延迟评估。但我要警告你,这几乎肯定最终会对性能产生更大影响。
internal class Sample
private static void Main(string[] args)
DelayedEvaluationLogger.Debug(logger, () => "This is " + Expensive() + " to log.");
private static string Expensive()
// ...
internal static class DelayedEvaluationLogger
public static void Debug(ILog logger, Func<string> logString)
if (logger.isDebugEnabled)
logger.Debug(logString());
【讨论】:
lambda 表达式有那么贵吗? @Charles:就后期绑定而言,lambda 相对便宜,但比布尔检查和方法调用贵得多。在我超过 10 亿次迭代的简单测试中(此处的代码:pastebin.com/aRtMtsxz),当未启用日志记录时,我测量了 lambda 与布尔检查的大约 25% 开销。使用缓存的 lambda(将 lambda 保存到一个字段中,这样它就不会每次都重新生成),开销略少(低于 20%)。【参考方案4】:没有预处理器,您就是 SOL。当然,在将代码提供给 C# 编译器之前,没有什么可以阻止您使用它。
【讨论】:
匿名代表和额外的课程 - 哇,打字量很大。以上是关于C# 日志记录的性能技巧的主要内容,如果未能解决你的问题,请参考以下文章