Geeks With Blogs

News

View Anthony Trudeau's profile on LinkedIn

Add to Technorati Favorites


Anthony Trudeau

The SQL Server TIMESTAMP data type is represented as a byte array in the .NET Framework (byte[] in C#). You may have the need for any number of reasons to provide a representation of the timestamp that can be displayed to the user or for debugging purposes.

This may seem complicated at first glance, but the .NET Framework already includes methods to complete the operation in a few lines of code. The only trick that you need to know is if the helper object treats the bits as big endian or little ending (meaning the array needs to be reversed). There’s a property for that, so it’s not a problem.

private static string TimeStampToString(byte[] tstamp)
{
    var val = GetTimeStampValue(tstamp);
    return "0x" + val.ToString("X").PadLeft(16, '0');
}

private static long GetTimeStampValue(byte[] tstamp)
{
    if (tstamp == null || tstamp.Length == 0)
        throw new ArgumentNullException("tstamp");

    byte[] buffer = new byte[tstamp.Length];
    tstamp.CopyTo(buffer, 0);

    if (BitConverter.IsLittleEndian)
        Array.Reverse(buffer);

    return BitConverter.ToInt64(buffer, 0);
}

You pass the byte array to the TimeStampToString method and you’ll get back a string in the format: 0x0000000000000000. This is the format that you get out of SQL Server Management Studio.

Posted on Saturday, January 5, 2013 7:41 PM .NET , SQL Server | Back to top


Comments on this post: Timestamp to String

# re: Timestamp to String
Requesting Gravatar...
return "0x" + val.ToString("X016");
or
return string.Format("0x{0:X016}",val);

And for GetTimeStampValue, why do extra work when you do have to?:

private static string TimeStampToString2(byte[] tstamp)
{
var val = GetTimeStampValue(tstamp);
return string.Format("0x{0:X016}",val);
// return "0x" + val.ToString("X016");
// return "0x" + val.ToString("X").PadLeft(16, '0');
}
private static long GetTimeStampValue(byte[] tstamp)
{
if (tstamp == null || tstamp.Length == 0)
throw new ArgumentNullException("tstamp");

var buffer = tstamp;
if (BitConverter.IsLittleEndian)
{
buffer = new byte[tstamp.Length];
tstamp.CopyTo(buffer, 0);
Array.Reverse(buffer);
}

return BitConverter.ToInt64(buffer, 0);
}
Left by James Curran on Jan 09, 2013 10:56 AM

# re: Timestamp to String
Requesting Gravatar...
Thank you for your feedback.

I did what I did with the array more out of habit than anything to protect the source array. Although I don't believe BitConverter will do anything to it (it shouldn't), it doesn't promise me it won't either. Your example avoids a possibly needless initialization; although I believe it's going to be little endian in most cases -- meaning the array will need to be reversed.

I wasn't aware that you could add the 016 after the X in the format string. But, I'm not onboard with the internal parsing that is going to have to require. I prefer the fluent approach which has a clear intent; and I'm pretty sure it performs better. After all, internally it's going to have to do something similar.

I put together a test program and sure enough the results should a 30-50% improvement on my x64. That said the time difference is so small that I don't think it really matters. Here is the test program:

static void Main(string[] args)
{
// pre-JIT; ignore these results
var fluentResult = TimeFluentStringFormatting();
var patternResult = TimePatternStringFormatting();

// run for real
fluentResult = TimeFluentStringFormatting();
patternResult = TimePatternStringFormatting();

Console.WriteLine("Fluent timing = {0}", fluentResult.ToString());
Console.WriteLine("Pattern timing = {0}", patternResult.ToString());

if (fluentResult >= patternResult)
Console.WriteLine("Fluent formatting took longer by {0}", fluentResult - patternResult);
else
Console.WriteLine("Pattern formatting took longer by {0}", patternResult - fluentResult);

Console.Write("Done. Press ENTER to exit program.");
Console.ReadLine();
}

private static TimeSpan TimeFluentStringFormatting()
{
Stopwatch timer = new Stopwatch();
string s;

timer.Start();
for (int i = 0; i <= 1000000; i++)
{
s = "0x" + i.ToString("X").PadLeft(16, '0');
}

timer.Stop();
return timer.Elapsed;
}

private static TimeSpan TimePatternStringFormatting()
{
Stopwatch timer = new Stopwatch();
string s;

timer.Start();
for (int i = 0; i <= 1000000; i++)
{
s = string.Format("0x{0:X016}", i);
}

timer.Stop();
return timer.Elapsed;
}

The IL for the two methods is interesting. The main difference is in that the fluent method calls PadLeft and Concat whereas Format is called in the pattern method. But, what's notable is that the value is boxed in the call to Format. By specifying i.ToString() in the Format call I can cut the time cost, but it still performs measurably worse.

I ran the test with VS performance analysis. This provides additional insight beyond time. The fluent method allocated less memory at about 229 MB vs. 424 MB which was allocated by the pattern method. String object allocations were nearly identical, but about 3MB more memory was used for string allocations in the fluent example. It appears the pattern method used StringBuilders internally. Which in this example accounted for over 2 million allocations. It also had to allocate another 2 million objects. Those two things likely accounted for the time and byte differences.
Left by Anthony Trudeau on Jan 11, 2013 7:21 PM

# re: Timestamp to String
Requesting Gravatar...
The additional 2 million objects I mentioned were due to the boxing. The boxing is required, because an integer is passed to string.Format() in James' example.

Also note, the numbers in the analysis are doubled, because I run each method twice to make sure JIT'ing is done to get a more accurate timing.
Left by Anthony Trudeau on Jan 11, 2013 7:28 PM

Your comment:
 (will show your gravatar)


Copyright © Anthony Trudeau | Powered by: GeeksWithBlogs.net