I've written some small sample code because I wanted to see how bad the performance hit was between

                   if (log.IsDebugEnabled)
                       log.Debug(Message);

and

                       log.Debug(Message);

My thoughts on the issue were that the cleaner code is what I would like to have in my projects even if there were a slight performance issue. What I uncovered was that the performance was really bad in comparison to what I could write when the code was NOT logging. I haven't looked at performance when logging, concentrating my observations on the majority of cases where the code remains in, but does NOT log anything. What I found to me is a bit frightening even though it may not bother you at all.

Here are the #'s that I found. They are in ticks (milliseconds) and are based on 1 billion logging calls made that do nothing except decide NOT to log

My code Non-optimized: 1402
My code optimized: 1392

log4net Non-optimized: 71593
log4net Optimized: 69089

Notice the fact that log4net is 49-51 times slower than my code when it isn't logging. The configuration is being done in the assemblyInfo.cs and the loglevel is being set with:

       <root>
           <level value="ERROR" />
           <appender-ref ref="ColoredConsoleAppender" />
       </root>

The C# code I used is below.
namespace SpeedTest {
   class Program {
       static void Main(string[] args) {

           const int nIterations=1000000000;
           {
               logger log = new logger(int.Parse(args[0]));

               String Message = "This is my message";
               int nStart = Environment.TickCount;
               for (int i = 0; i < nIterations; ++i) {

                   log.Debug(Message);
               }
               int nEnd = Environment.TickCount;

Console.WriteLine("My code Non-optimized: " + (nEnd - nStart).ToString());

               nStart = Environment.TickCount;
               for (int i = 0; i < nIterations; ++i) {

                   if (log.IsDebugEnabled)
                       log.Debug(Message);
               }
               nEnd = Environment.TickCount;

Console.WriteLine("My code optimized: " + (nEnd - nStart).ToString());
           }

           {
               //log4net.Config.DOMConfigurator.Configure();
               log4net.ILog log = log4net.LogManager.GetLogger("root");

               Console.WriteLine("log4net");
               String Message = "log4net Non-optimized";

               int nStart = Environment.TickCount;
               for (int i = 0; i < nIterations; ++i) {

                       log.Debug(Message);
               }
               int nEnd = Environment.TickCount;

Console.WriteLine("log4net Non-optimized: " + (nEnd - nStart).ToString());

               Message = "log4net Optimized";
               nStart = Environment.TickCount;
               for (int i = 0; i < nIterations; ++i) {

                   if (log.IsDebugEnabled)
                       log.Debug(Message);
               }
               nEnd = Environment.TickCount;

Console.WriteLine("log4net Optimized: " + (nEnd - nStart).ToString());
           }
       }
   }
}


What I notice when I debug the release build of the log4net stuff is this:

log.Debug(x)

becomes

           Logger.Log(m_fullName, Level.DEBUG, message, t);

which becomes

   if (Logger.IsEnabledFor(Level.DEBUG))


which becomes

       virtual public ILogger Logger
       {
           get { return m_logger; }
       }

which returns the logger

so that it can do

                  if (m_hierarchy.IsDisabled(level))

which becomes

               if (((object)l) != null && ((object)r) != null)
               {
                   return l.m_level == r.m_level;
               }
               else
               {
                   return ((object)l) == ((object)r);
               }

And then

               if (Configured)
   .......

Which leads to:

               return level >= this.EffectiveLevel;

Which leads to:

               for(Logger c = this; c != null; c = c.m_parent)
               {
                   if (c.m_level != null)
                   {
                       return c.m_level;
                   }
               }
return null; // If reached will cause an NullPointerException.

We end up with function call after function call and a loop to figure out a simple x > y question that we could have done by exploiting some of the language features.

Now, what I'm thinking goes along the lines of making use of the referential nature of c#. Creating a LogLevel class:

class MyLogLevel {
       private int m_nLevel;

       public int Level {
           get {
               return m_nLevel;
           }
           set {
               m_nLevel = value;
           }
       }
       public MyLogLevel(int nInitLevel) {
           m_nLevel = nInitLevel;
       }
   }

Relying on the fact that every class that uses a logger uses a reference to that logger, and that the log level itself can be a reference to another variable we can do a little more work every time we process the log properties file (and only then) to set the log level reference to the correct one and to set the level within that reference to the correct value. So if logger A,B,C, and D are not specified in the log level, then they all reference the root logger. Then if the properties file is modified, and A is referenced, but B,C, and D are not. The the log level reference in A gets replaced (is not longer "root") with a new log level instance that is set to the new value and B,C,D all keep their reference to "root". This is extra overhead when the log properties is loaded, but it is loaded far fewer times than Log.Debug is called.

I modified my logger code to do something like this (this is a test class only, for proof of concept).

   class logger {
           private MyLogLevel m_nLogLevel;
           private String m_strName;

           public bool IsDebugEnabled {
               get {
                   return m_nLogLevel.Level > 1;
               }
           }

           public void Debug(object msg) {
               if (m_nLogLevel.Level > 1) {
                   Console.WriteLine(m_strName + ": " + msg.ToString());
               }
           }
           public logger(String strName, MyLogLevel nLevel) {
               m_strName = strName;
               m_nLogLevel = nLevel;
           }
           public MyLogLevel LogLevel {
               get {
                   return m_nLogLevel;
               }
               set {
                   m_nLogLevel = value;
               }
           }
       }

Sorry this was so long, but I wanted to throw it out for comment.



Reply via email to