Thursday, April 16, 2009

Delphi 2009 Implicit String Conversion Penalties

When migrating existing source code from previous versions of Delphi to Delphi 2009, you may encounter warnings about implicit conversions from AnsiString to Unicode Strings or vice versa (the other way may also have a potential data loss) and while it's easy to ignore these warnings, ignorance comes with a price!

The particular real-world example I want to talk about in this post comes from a third-party library (who shall remain nameless) compatible with different versions of Delphi, including Delphi 2009. When I migrated my project from Delphi 2007 to Delphi 2009, the resulting application worked fine except when calling the third-party routine to perform a certain task. Response times went up from 2 seconds to almost 20 minutes (I'm not joking), which is a long time to wait for an answer to be formulated.

By the way, originally we thought the process was just hanging, until I allowed it to continue to run and it came back with an answer 20 minutes later. At that point, I made sure to add the source code of the third-party library to my search path, and started to step into the third-party source code. Doing so, caused the compiler to show a number of hints and warnings (that were obviously not fixed in this library), mainly concerning the Ansi vs. Unicode strings and implicit conversions. Warnings that could be skipped - or so the original developers thought.

One of the tasks this code was doing, involved writing a series of hexadecimal values to a stream, and it did this by adding them to a local string variable. The local string variable was of type AnsiString, and the loop was coded as follows (slightly altered to protect the innocent):
   for i:=0 to LongList.Count-1 do
begin

LocalAnsiString := LocalAnsiString +
'<' + IntToHex(Integer(LongList.Items[i]), 4) +
'> <' + IntToHex(Integer(LongList.Items[i]), 4) + '> ' +
IntToStr(Integer(LongList.Items[i])) + CRLF;
end;

The LocalAnsiString is an AnsiString, and the parts that are added are Unicode Strings (especially the result of the IntToHex function) which need to be converted to AnsiStrings. This happened all over the place in this third-party library, and is usually no big deal, unless you're doing it in a loop for more than 38 thousand items (with a string growing to half a megabyte in size).

I've managed to make the application several times faster (going from 20 minutes to 2 seconds again - not just for the loop) by adding a single cast:
   for i:=0 to LongList.Count-1 do
begin

LocalAnsiString := LocalAnsiString + AnsiString( // BS
'<' + IntToHex(Integer(LongList.Items[i]), 4) +
'> <' + IntToHex(Integer(LongList.Items[i]), 4) + '> ' +
IntToStr(Integer(LongList.Items[i])) + CRLF);
end;

That way, only one - explicit - conversion of Unicode to AnsiString will happen instead of implicit ones (originally probably casting the LocalAnsiString up to a Unicode String first, then perform all the string additions and casting the result back to an AnsiString which will become slower and slower when the string content grows). Note that this single change will still compile with older versions of Delphi, and no longer produces a warning using Delphi 2009. Or loss of speed.

Needless to say, I sent the original author of this third-party library a friendly suggestion to compile the code with Delphi 2009 and try to fix all warnings, especially concerning the (Unicode)String to AnsiString issues.
They were pleased with my findings, and quickly updated their code.

No comments: