Severe performance drop after migrating RO SDK from 8.3 to 9.x

We have a RO server created with Delphi XE2 and RO SDK version 8.3.93.1183

Later this year year we migrated to SDK version 9.4.107.1363

Several methods return (sometimes big) json packets (as strings) and these strings are declared as Utf8String in the RODL interface.

After migrating to 9.4 one of our customers reported that our application server was significantly slower than previous version (built with SDK 8.3). Took us several days to figure out that the migration to newer RO SDK was the culprit.

Then I decided to create and run isolated test cases, so I could compare the performance. I created a simple project (both server and client) and exposed 5 methods. This is the server interface:

TNewService = class(TRORemoteDataModule, INewService)
public
function Sum(const A: Integer; const B: Integer): Integer; virtual;
function GetServerTime: DateTime; virtual;
function GetAnsiString: ROAnsiString; virtual;
function GetUtf8String: ROUTF8String; virtual;
function GetWideString: UnicodeString; virtual;
end;

The same RODL file was used to create the exact same server/client with both versions. They are based on Synapse super TCP server/channel.

All 3 string methods create a big string using the same underlying data type as the result (i.e. there is no implicit conversion of string types) and are called in a loop from the client. Nothing else. This is the performance data (numbers were obtained using TStopwatch.ElapsedTicks):

8.3 (client and server)
GetAnsiString = 44029563
GetUtf8String = 43973187
GetWideString = 90918905
Sum = 385212
GetServerTime = 384935

8.3 (server) / 9.4 (client)
GetAnsiString = 53672772
GetUtf8String = 54033906
GetWideString = 93295080
Sum = 416417
GetServerTime = 405752

9.4 (client and server)
GetAnsiString = 63895384
GetUtf8String = 62832517
GetWideString = 94493844
Sum = 403969
GetServerTime = 410536

9.6 (client and server)
GetAnsiString = 62941979
GetUtf8String = 61733501
GetWideString = 92866095
Sum = 432470
GetServerTime = 422576

9.6 (client and server - built with $DEFINE CODEGEN4_LEGACYSTRINGS )
GetAnsiString = 70931538
GetUtf8String = 69630090
GetWideString = 96666900
Sum = 421897
GetServerTime = 424205

As you can see, methods that return AnsiString/Utf8String are significantly slower (around 22%) in 9.x branch, even if I enable legacy strings through CODEGEN4_LEGACYSTRINGS compiler directive.

Please notice that both client and server are slower when built with new version (see mixed case above).

This imposes a HUGE problem for us because:
1- The performance impact is not acceptable
2- we had to rollback RO SDK and restore older version
3- we can’t deploy our new branch built with Delphi 10.3 because there is no RO SDK 8.3 for it (i.e. we are stuck in Delphi XE2 until RO finds a solution for this problem)

If you need I can provide all source code used in the tests and all collected data but we definitely need to find a solution for this.

Cheers

Hi,
can you share your testcase, pls? we will check it on our side
you can send it to support@ if you want to keep it privately

Test project sent through e-mail. Hopefully you can fix this, because we are stuck.

A side note: You guys should think on how to make it possible to install multiple versions of RO SDK on a single computer. Reality is that developers need to work with different projects which require different IDEs/development tools. The only framework in our projects which impose such restriction is RO SDK.

One way we follow to overcome this is the use of virtual machines (vmware). This allows easy recover in case of catastrophes, test of different versions of our tools and easy portability of full development environments.

try to update uROStreamSerializer.pas as

  TROStreamSerializer = class(TROSerializer)
..
    {$IFDEF ROAllowLegacyStringTypes}
    procedure WriteUTF8String(const aName: string; const Ref; ArrayElementId: integer = -1);override;
    procedure WriteAnsiString(const aName: string; const Ref; ArrayElementId: integer = -1);override;
    procedure ReadUTF8String(const aName: string; var Ref; ArrayElementId: integer = -1; iMaxLength: Integer = -1); override;
    procedure ReadAnsiString(const aName: string; var Ref; ArrayElementId: integer = -1; iMaxLength: Integer = -1); override;
    {$ENDIF}

...
{$IFDEF ROAllowLegacyStringTypes}

procedure TROStreamSerializer.ReadUTF8String(const aName: string; var Ref; ArrayElementId: integer = -1; iMaxLength: Integer = -1);
var
  sze : integer;
begin
  fStream.ReadBuffer(sze, SizeOf(sze));

  //ToDo: we need some code here to avoid hacker attachs with too long strings
  if ((iMaxLength > -1) and (sze > iMaxLength)) or
     (sze > fStream.Size) then
    RaiseError(err_InvalidStringLength,[sze]);

  if (sze>0) then begin
    SetLength(UTF8String(Ref), sze);
    fStream.ReadBuffer(UTF8String(Ref)[1], sze);
  end
  else
    UTF8String(Ref) := '';
end;

procedure TROStreamSerializer.ReadAnsiString(const aName: string; var Ref; ArrayElementId: integer = -1; iMaxLength: Integer = -1);
var
  sze : integer;
begin
  fStream.ReadBuffer(sze, SizeOf(sze));
  if ((iMaxLength > -1) and (sze > iMaxLength)) or (sze > fStream.Size) then
    RaiseError(err_InvalidStringLength,[sze]);

  if (sze>0) then begin
    SetLength(AnsiString(Ref), sze);
    fStream.ReadBuffer(AnsiString(Ref)[1], sze);
  end
  else
    AnsiString(Ref) := '';
end;

procedure TROStreamSerializer.WriteUTF8String(const aName: string; const Ref; ArrayElementId: integer = -1);
var
  sze : integer;
begin
  sze := Length(UTF8String(Ref));
  fStream.Write(sze, SizeOf(sze));
  if (sze>0) then fStream.Write(UTF8String(Ref)[1], sze);
end;

procedure TROStreamSerializer.WriteAnsiString(const aName: string; const Ref; ArrayElementId: integer = -1);
var
  sze : integer;
begin
  sze := Length(AnsiString(Ref));
  fStream.Write(sze, SizeOf(sze));
  if (sze>0) then fStream.Write(AnsiString(Ref)[1], sze);
end;
{$ENDIF}

note: this will work only if {$DEFINE CODEGEN4_LEGACYSTRINGS} is used

I wonder if .NET libraries were like that: You needed one VM for each version of the library or combination of libraries…

1 Like

Thanks for the update. I’ll test it later today.

I can confirm that the fix works, as long as you enable {$DEFINE CODEGEN4_LEGACYSTRINGS} compiler directive.
Once you build the packages during install, can’t you make this option available during setup? Otherwise, we will be forced to rebuild from sources at each new release which becomes painful if you have several developers working on a project.

Regards

i’m interested in the ‘why’ why it is slower…

Mobile platforms (and NEXTGEN compiler) don’t have AnsiString type.
as a result, we store AnsiString as usual string and during serialization convert string to TBytes with standard TEncoding.Default/UTF8 and write it.
during deserialization, we convert back TBytes to string.

looks like, this conversion causes some slowness because on windows it calls WideCharToMultiByte/MultiByteToWideChar (WinAPI method)

For one, everyone should be using WideString. it’s 2019; 7 or 8-bit strings really aren’t safe in any but the most controlled environments where you can make sure the code page on both client and server are always the same.

That said, Eugene, should CODEGEN4_LEGACYSTRINGS be maybe the default for non-NEXTGEN?

no, we can’t.
w/o this define, codegen generates ROAnsiString/ROUTF8String that are aliases for String type. with define, it generates AnsiString/UTF8String i.e. one byte strings.
these types are generated in _Intf, _Invk and _Impl

Ah, ok.

Let me understand. For compatibility with Delphi mobile only you need to do that conversion. But if I never, ever, use Delphi mobile can I compile RO/DA to use CODEGEN4_LEGACYSTRINGS only ?

BTW, ARC compiler will be removed from mobile in next version according to what EMB says in the past.

that’s how i read it to, and if so then this should be the default imho, after all who wants to do mobile with delphi :smile:

if you are using Windows only, you can enable this define and recompile RO packages.
after this, codegen4 will generate files with AnsiString/UTF8String instead of ROAnsiString/ROUTF8String

can you provide a link for me with this info, pls?

No, I use linux servers. Why is a problem with windows servers if latest Delphi linux compiler not use ARC?

Not have at hand but was announced when EMB announce the drop to ARC support for linux compilers from RIO version. They state will do the same on Mobile platform. And they state was a huge mistake taking that road as far as I remember.

Best regards.

according to http://docwiki.embarcadero.com/RADStudio/Rio/en/Migrating_Delphi_Code_to_Mobile_from_Desktop#Eliminate_Data_Types_that_Are_Not_Supported_by_the_Delphi_Mobile_Compilers

they suggested to replace System.AnsiString with array of byte so we did this for serialization/deserialization one byte strings.

so when you want to create universal application for desktop and mobile platforms that uses the same code base, our solution with ROAnsiString/ROUTF8String will work.

they are going to remove ARC in Delphi 10.4
it will be in next half year or a year.
this is a quite tricky action that can mean that we should rewrite a lot of code in case they are going to remove ARC from mobile platform…

Is clear now, thanks. I use Elements/Fire form mobile. Just Windows and linux with RIO. Can I use legacy strings then?

Agree.

Best regards.

yes, why no.
as you see, we haven’t removed it completely and it can be enabled via define

1 Like