Update 2.9.2010
The content of this post regards DateTime.Now's implementation until v3.5. At the release of v4.0, its internal implementation was updated to avoid unnecessary boxing.
However, looking into the new implementation details revealed a new source for dynamic memory allocations in DateTime.Now. The issue is discussed in detail in the updated post: DateTime.Now in v4.0 Causes Dynamic Memory Allocations.
Perhaps you weren't aware of it, but every time you access DateTime.Now, you cause to a dynamic memory allocation through the boxing of an Int32 variable.
The reason for this lays deep inside the implementation of the Now property. If we will look a bit closer using Reflector, we could see that it actually wraps a call to UtcNow and then converts it to local time using the ToLocalTime method.
While there's nothing wrong with the call to UtcNow (it just wraps a call to the Win32 GetSystemTimeAsFileTime), there is some trouble with ToLocalTime, or actually, with the class CurrentSystemTimeZone that is being used. During the conversion the method GetDaylightChanges is called that causes the boxing. You can look for yourself:
public override DaylightTime GetDaylightChanges(int year) { object key = year; if (!this.m_CachedDaylightChanges.Contains(key)) { // ..lots of code } return (DaylightTime)this.m_CachedDaylightChanges[key]; }
This is a good example showing that even in latest versions of the .Net Framework, some types still uses non-generic data structures, from the days of 1.0/1.1. In this case, the data member m_CachedDaylightChanges is actually a Hashtable).
This means that you might find plenty more of cases in which unnecessary boxing occurs without your knowing. But what makes DateTime.Now case especially serious is that it's a very common property to access, especially by logging frameworks for instance.
All of those boxings comes with a price, and it's not cheap. Due to the high frequency of dynamic memory allocations, we could cause some serious stress for the GC.
In order to demonstrate the impact on memory usage, I'll use the following benchmark:
while (true) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) { DateTime now = DateTime.Now; } Console.WriteLine(sw.ElapsedMilliseconds); }After executing this code, we can learn two things. The first, is that it takes on average 456ms to complete each iteration on the while loop (I'll address this result in a moment), and the other, is that we can learn the magnitude of the affect of calling DateTime.Now on our application's memory usage patterns. In this case, I've used perfmon to monitor the application's memory allocation rate:
How to Avoid The Boxing?
After we've seen why we shouldn't call DateTime.Now (in certain scenarios), raises the question "How could we avoid those unnecessary memory allocations?".
First, there's Microsoft's side, which needs to make sure to update their code to make sure they avoid boxing (e.g, using a Dictionary instead of a Hashtable). But until this happens, we'll need to take matters to our own hands and workaround this issue. My suggestion is to write a wrapper method that is almost identical to UtcNow's implementation (that simply wraps a call to the Win32 API). All we need to do is to call GetLocalTime that will return use the the in SYSTEMTIME format, which we will need to convert back to a DateTime type.
For example:
public static class TimeUtil { [DllImport("kernel32.dll")] static extern void GetLocalTime(out SYSTEMTIME time); [StructLayout(LayoutKind.Sequential)] private struct SYSTEMTIME { public ushort Year; public ushort Month; public ushort DayOfWeek; public ushort Day; public ushort Hour; public ushort Minute; public ushort Second; public ushort Milliseconds; } public static DateTime LocalTime { get { SYSTEMTIME nativeTime; GetLocalTime(out nativeTime); return new DateTime(nativeTime.Year, nativeTime.Month, nativeTime.Day, nativeTime.Hour, nativeTime.Minute, nativeTime.Second, nativeTime.Milliseconds, DateTimeKind.Local); } } }
Running the benchmark from before with this updated implementation, we could see that we completely got rid of the memory allocations, and as a bonus, every iteration takes only 370ms. That is, an 18% improvement over the performance of DateTime.Now's implementation.
Hi,
ReplyDeletethanks for an interesting blog post. I seriously recommend you to notify the BCL team through Microsoft Connect about the issue. With enough votes, they should take it under consideration and refactor the implementation.
Anders Borum (MCP)
Denmark
@Anders,
ReplyDeleteFollowing your suggestion, I've posted a feedback on Microsoft Connect.
Anyone wanting to track the progress of this item, can find it here: https://connect.microsoft.com/VisualStudio/feedback/details/584669/datetime-now-causes-boxing
Most of the code in the BCL that was created prior to the introduction of generics into the framework still uses non-generic collections. I'm sure you can find many more examples of unnecessary boxing. The question is, do these unnecessary boxing operations have a significant negative effect on your application? Dynamic memory allocation and gen 0 collections are incredibly cheap, and very likely are not impacting your application in any measurable way.
ReplyDelete@David,
ReplyDeleteYou are completely right. It's obviously a no-brainier to understand that this code was original written during the 1.0/1.1 phase, so this explains why non-generic data structures were used. Hench, we can presume that there's plenty of more places that are filled with this kind of "obsolete" code that could cause some unnecessary memory allocations.
Personally I believe that most of the, this kind of allocations are so minimalistic that they have nearly no effect on the application's performance. However, DateTime.Now have a particularly high chance to affect performance since at some applications it might be used extensively (in comparison to other APIs in the BCL that might cause boxing).
[ http://connect.microsoft.com/VisualStudio/feedback/details/584669/ ]
ReplyDeleteIn .NET Framework versions 3.5 and earlier, DateTime.Now used the TimeZone class under the hood and that class causes some unnecessary boxing. However, in .NET Framework version 4.0 DateTime.Now was changed such that it is now using the TimeZoneInfo class. Thus, this issue will not occur in .NET Framework versions 4.0 or later.
By the way, the class TimeZone should generally not be used any more for a number or reasons [http://msdn.microsoft.com/en-us/library/system.timezone.aspx].
Instead, the class TimeZoneInfo should be used whenever possible [http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx].
@Greg,
ReplyDeleteThanks for your comment.
Even though the boxing is gone in v4.0, DateTime.Now actually became more memory-consuming due to the allocations of DaylightTime instances.
I've posted a followup regarding this issue here: http://blog.liranchen.com/2010/09/datetimenow-in-v40-causes-dynamic.html