Skip to Content
Author's profile photo Juergen Gatter

BPath. Lesson 06. Unstructured returns, sorting and performance considerations

<<05: Sub, String, Get-function(s), global variables 07: Model Check Mode, Exceptions and longtext>>

Last BPath lesson was presented some time ago. The performance investigations took slightly more time then expected, mainly due to other work. But let us concentrate on Bpath and the current lesson.

BPath. Lesson 05. Sub, String & Get-function(s) plus global variables brought us a view over quite a lot of functions. This time we have only limited content in respect to new functionality but a bigger chapter in respect to performance.

Unstructured Returns & Sorting

To allow easy access to the data we introduced the possibility to work on unstructured return types, such as integer values and strings. The rule is simple, if the declared return type is not structured; every write access to it is treated as writing to the value itself (irrespective of the target name used). Read access is not supported (use global vars instead).

The following example returns the index of the first passenger beginning with “S”.

~INT2/SearchResFlightRel/FlightBookRel[@PASSNAME~=”S*”]{!RETURN=INDEX()}
Code Example 30, ~INT2 or ~STRING, unstructured return data

Another useful feature is the possibility to sort the result table according to your needs. The feature includes the sorting of sub query result tables. Sorting is treated as feature of the structure definition. This means, in case you want to sort the result the structure has to be stated explicitly. Dynamic structure definitions as ~* are allowed. The sorting field names are added encapsulated by [ … ], the sort-order (‘A’ or ‘D’) might be added as option triggered with a colon (default is ascending order). ‘T’ option can be used to sort data as text. The star operator is used to indicate the current order, this means, if a star is part of the sort order definition, stable sorting is done.

~*CRMS_BOL_UIF_TRAVEL_BOOK_ATT[!FORCURKEY,!FORCURAM:AT,*]/SearchResFlightRel{!DS:=SUB(~*[!CONNID:D]/FlightConnectionRel/*$)}/FlightBookRel/*$
Code Example 31, ~*[!FIELD:AT,*], sorting

The main sorting is done on the foreign currency (ascending, since sort order is not specified) and then the amount (in foreign currency), ascending as text. Sorting is done stable.

The inner sort order is just for display purposes – the sub query returns anyway only one entry in the table (as every flight has only one connection).

The sorting is done at the very end of the evaluation. The sorting used for filtering, assignments, etc remains as defined by the underlying relations.

Direct Indexing

Up to version 2.1 (a BPath version not released to customers) any filter condition which returned a numeric value (e.g. n= 2 or n=-1) was implicitly evaluated with a check against the current index (means [ i =index()]). This is theoretically fine, but results in a very poor performance in case the numerical value does not depend on the attribute values.

With the released version any filter returning a numeric value is directly indexed, in case it fulfills certain conditions. Directly indexed means that only the requested entity is evaluated, all other datasets are ignored and not further processed.

The conditions are:

  • no access to any attribute in the filter
  • no subquery in the filter
  • no assignment block between relation and filter

The result of any query should not change, but the performance should be significantly better in case direct indexing can be used within large collections.

Please note that a negative indexing refers to entities counted from the end of the list, -1 is the last object in the collection. The syntax does not allow to use negative literals directly, [-1] has to be specified as [0-1].

A rather crude example:

~STRING/SearchResFlightRel{!!Dummy=0-1}/FlightBookRel[!!Dummy]/@PASSNAME$
Code Example 32, [0-1], direct indexing

Performance Evaluations

Unfortunately it is not easy to discuss performance seriously, because of the following reasons:

  • Absolute performance is highly machine dependent.
  • The BPath execution is dependent on the performance of the used relations, which can vary a lot.
  • Caching effects have to be encountered.
  • Even if the performance of a BPath statement execution might be pretty fast in terms of “doing all things it does pretty fast”, but it may trigger actions which can be avoided if a different way would be chosen.
  • It is difficult to judge what “an optimal” performance would be, to which process we compare to. Practically all code examples can be further optimized, especially when you do special coding ignoring standard layers.

The following simple evaluations were done in one of our systems, the goal of the investigations was to have an idea which performance you might expect and which guidelines may be considered. The machine where I did the tests was running in a solaris zone (virtual machine) with 2.6 GHz and 10Gb of RAM. It is a non-Unicode system.

See below the table of results. The first column contains the BPath query, which was always executed against a collection of 10 “flights”. The “result” column contains the number of datasets returned, whereas the “touched” column also contains all entities which were touched during execution. The “Frst” column holds a checkbox whether we measure the complete execution (“X”) or whether we measure without the startup time (means we reuse the parser). Only the first example is measured with startup performance, but it is anyway only about 3 ms.

The next column holds the needed time for the execution in microseconds, followed by the time required by the parser plus the stack.

The second last column contains the percentage of time elapsed in BPath compared to the time elapsed in the lower layers (GenIL, application specific coding as relation loading) whereas the last column simply holds time in BPath divided by number of touched entities. In other words the time required for BPath to evaluate an object lying on the execution path.

image

Performance Learnings:

  1. Start up performance is about 3ms (can be avoided with parser reuse except for the first call).
  2. An easy BPath statement needs about 300 micro seconds per object it accesses during the execution.
  3. Complexity costs. In case the BPath statement is formulated with more complexity, e.g. with the usage of subs and functions, execution time rises, e.g. to 2500 micro seconds with the last example.
  4. Typical usages which evaluate at least one real relation and assemble data into a result table and work on uncached data need about 20-40% of the complete time within BPath itself. At least within the implementation of our Flights.
  5. Direct indexing can pay off as with the eighth example which needs substantially less time in BPath itself.
  6. Scaling should be fine, there seem to be no effects ruining the performance with big collections.
  7. Stack processing costs a significant proportion of time, there might be room for improvement (on the cost of maintainability unfortunately).

Well, all these evaluations are done against “Flights”, which are kind of artificial demo object structure. It is difficult to judge, how it looks when we work on real life objects.

So I did some more tests in another system, which contains better data. System characteristica are comparable.

My demo program reads a list of contacts with the BuilContactAdvancedSearch query where with telephone covers ‘0*’ and country is ‘DE’ to avoid working on irregular data. Then my BPath query simply tries to collect all addresses with the following query:

  ./BuilContactPersonAddressRel/*$

The query returns 33 entries, exactly one address per contact.

An execution of the BPath requires 683 ms against uncached data with 25.5 ms in our BPath code itself. This makes it to 0.38 ms per touched entity (we have 2*33  = 66 entities) within our BPath classes.

On cached data the whole execution time shrinks down to 32 ms, still with 25.5 ms in our BPath code. This means in this case Bpath directly causes for about 80% of the required time.

So, in the end this example fits to the results from the table above and I hope you now have an idea about the performance you can expect from a BPath query.

That’s it for today. Lesson 7 will cover the model check mode and parser reuse plus some small language topics as attributed parent relations and attribute longtext.

Assigned Tags

      10 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Former Member
      Former Member
      Hello Jürgen,

      I really appreciate the blog series you started and have read all articles about BPath you published so far.

      Now I found that BPath is a really lean way to get access to attributes of underlying relations in the GET_* methods of SAP WebClient. However I was wondering if it is not only lean by the lines of code, but as well in its execution times.
      In this blog you gave some numbers and I was wondering how they compare to the "traditional" way to access attributes in BOL entities.

      In my situation it is accessing condition entries of items in a OneOrder. My BPath looks like this:
      {code}
      BTItemConditionSet/BTCondICondLineAll[@KSCHL="PN00"]/@KOEIN
      {code}
      Now I could as well go for this:
      {code}
      DATA:
        lr_iter type ref to if_bol_bo_col_iterator,
        lr_col type ref to if_bol_bo_col,
        lr_entity type ref to cl_crm_bol_entity.

      lr_entity = lr_entity->get_related_entity( 'BTItemConditionSet' ).
      lr_col = lr_entity->get_related_entities( iv_relation_name = 'BTCondICondLineAll' ).
      lr_iter = lr_col->get_iterator( ).
      lr_entity = lr_iter->find_by_property( iv_attr_name = 'KSCHL' iv_value = 'PN00' ).
      lr_data = lr_entity->get_property( 'KOEIN' ).
      {code}

      As you see BPath will be considerably smaller and in my opinion a lot easier to understand. But what about performance when this method is rapidly executed?

      Author's profile photo Juergen Gatter
      Juergen Gatter
      Blog Post Author
      Hello Carsten,

      it is really difficult to provide a general answer in respect to performance questions. In a sense BPath can be an optimization in respect to maintainability, but when it comes tough performance requirements, straight ABAP coding will return faster.

      As all steps which you mentioned in your ABAP code are basically also executed internally with the BPath execution, the BPath way is of course slower. The question is how much. Typically the fetching of the related entities requires much time (in case it is not yet cached) so the BPath related execution times fall not so much in the picture.

      In general I would recommend to view it the other way round. Simply use BPath to code your requirements. Check the performance in real life and against the numbers from my blog. If performance (and other parameters) is sufficient, you may leave the code as it is, otherwise use the BPath statement as template / high level design for the real ABAP coding.

      Hope that's fine.

      Best regards, Juergen

      Author's profile photo Former Member
      Former Member
      Hi Jürgen,

      thanks for your opinion!

      > As all steps which you mentioned in your ABAP code are basically also executed internally with the BPath execution,...

      This was the part where I was unsure. I thought maybe BPath hat a different way to access the BOL layer. Hoped for some "FRIEND" relation ship.

      > otherwise use the BPath statement as template / high level design for the real ABAP coding.

      This is an interesting suggestion. Is it possible to generate the corresponding ABAP code for a BPath statement already? I am quite sure the BPath interpreter has coding like this in it. At least it ought to when it is working on the BOL layer.
      Maybe this is a great idea for a new Wizard in the WebClient (hint) 🙂

      cheers Carsten

      Author's profile photo Juergen Gatter
      Juergen Gatter
      Blog Post Author
      Hi Carsten,

      as for the first part. There were some hopes to make it even faster with BPath since the complete task is known and not only a fragment. Frankly speaking I never really shared these hopes. There were some attempts to introduce something for more complex statements, but they were never completed. As for easy statements as the one above there is practically no way to achieve substantically better performance by internal ways, as long as I know.

      As for the second question I have to disappoint you a bit. A compiler (to ABAP) is actually a completely different thing (e.g. for the implicite loops, the structure generations) and would require quite a lot of effort. I also thought in the beginning that at least for the main parts (relations, filters, ..) a compiler would be a funny thing, but after a while it turned out, that the effort is too high.

      Best regards, Juergen

      Author's profile photo Former Member
      Former Member
      Guten Tag Herr Gatter,

      bisher habe ich mir die Parameterwerte immer durch das Traversieren über die BOL-Relationen geholt.
      Die BPath-Methode würde viel unübersichtlichen Code ersparen, nur habe ich nach vielen Versuchen immer noch nicht geschafft, an die entsprechenden Werte zu kommen.

      Meine Beziehungen sehen so aus:

      Ausgangslage:
      BOL-Suche liefert 0..n Objekte

      BPath für:

      o Buch
        -> Verlag 
           o Mitarbeiter
             -> Ansprechpartner
                o Adresse
                  -> Kontaktperson
                     o Wohnhaft (Wert x)
                     o Wohnhaft (Wert x)
                     o Wohnhaft (Wert x)

                  -> nicht relevant
                o...nicht relevant
           o...nicht relevant
      o Buch ...relevant

      Die X-Werte möchte ich abfragen.
      Wie kann ich diese Werte mittels BPath überhaupt erreichen?

      Vielen Dank im Voraus.

      Author's profile photo Juergen Gatter
      Juergen Gatter
      Blog Post Author
      Hallo,

      also generell hört sich das schon wie eine Aufgabe an, für die sich BPath gut eignet. Leider ist es etwas schwer obige Beschreibung in eine konkrete Objektbeschreibung zu übersetzen, ich näher mich mal einfach, indem ich ein paar Annahmen treffe, in der Hoffnung, dass das dann weiterhilft.

      Wenn ich das richtig sehe sind die Entitäten mit dem Kreis-Symbol konkrete Objekte, die mit den Pfeilen sind die Relationen zwischen ihnen. BPath selbst interessiert sich nur für die Relationen, die Namen der Objekte selbst sind ohne Bedeutung. Zuallererst müsste man also zu dem Objekt (Wohnhaft!?) navigieren, ausgehend von dem Objekt das die Suche liefert (Buch!?). Wenn ich ihre Beschreibung als Hierarchie interpretiere, wäre das : "./Verlag/Ansprechpartner/Kontaktperson", wobei natürlich die technischen Namen der Relationen notwendig sind.
      Das Feld x des Zielobjektes kann man dann einfach mit "@x" auslesen. Wenn man nur diesen Wert lesen will kann man einfach ein "/@x$" anhängen. Das $ ist notwendig um alle x-Werte zu lesen und nicht nur den ersten gefundendenen. Typischer ist aber vielleicht ein Aufbau einer neuen Struktur mit folgender Konstruktion:
      "~*/Verlag/Ansprechpartner/Kontaktperson{!x=@x;!y=@y}$"

      Für das Erstellen eines BPath ist es vielleicht das einfachste sich Relation über Relation vorwärtszuhangeln, also hier erstmal testweise ein Statement "./Verlag/*$" zu feuern und zu schauen ob der Parser es akzeptiert und es Resultate liefert (eine Tabelle von Mitarbeitern !?).

      Ich hoffe ich konnte ihnen etwas weiterhelfen.

      Grüsse aus Walldorf, Juergen

      Author's profile photo Devashish Bhattacharya
      Devashish Bhattacharya

      Hi Juergen Gatter,

      What about error handling in Bpath?

      Regards,.

      Devashish

      Author's profile photo Juergen Gatter
      Juergen Gatter
      Blog Post Author

      Hi Devashish,

      just by your reply I realized, that I never described the model check mode possibilities here, so maybe I do briefly describe it here in this comment.

      In general BPath should be designed in a way that a syntactical correct statement has a defined output and never runs into errors. I hope that I have not overseen any possibility in one of the features where this is not valid.

      But of course not all statements are syntactically correct, then an ABAP exception is raised:

      Exception
      Syntax Check
      Model Check
      Execution
      Description
      Example
      CX_WCF_BPATH_PARSING_ERROR
      X
      X
      X
      General parsing error
      ~*/+-*^1
      CX_CRM_UNSUPPORTED_RELATION
      X
      X
      relation unknown
      ~*/PlumpaQuatsch
      CX_CRM_GENIL_MODEL_ERROR2
      X
      attribute unknown
      ~*/./@NoAttribute

      CX_WCF_BPATH_PARSING_ERROR can have one of the following TEXT_IDs:

      CX_WCF_BPATH_PARSING_ERROR

      X

      X

      X

      '~*/$'

      Current token is not allowed at this place (possible entries will be listed)

      INCOMPATIBLE_OPERATION_TYPES

      X

      X

        '~*/.{!A=Today()*"Hello"}'

      operation is not supported with specified types

      NUMBER_OF_PARAMS_MISSMATCH

      X

      X

      '~*/.{!A=Upper()}'

      Number of parameters in statement does not match function declaration

      PARAMETER_WRONG

      X

      X

      '~*/.{!A=Upper(1)}'

      Parameter type does not match function declaration

      PARENT_OBJECT_NAME_INCORRECT

      X

      *)

      '~*/.._NoNsEnSe'

      specified parent object name is incorrect

      PARENT_REL_NOT_UNAMBIGOUS

      X

      *)

      '~*/..'

      parent is either not there (we are already on root) or not unambiguous

      UNSUPP_TYPE_IN_TARGET_STRUCT

      X

      X

      creation of a target of used type is not supported

      UNSUPPORTED_FUNCTION

      X

      X

      '~*/.{!A=Test(1)}'

      There is no such thing as a function with this name

      As you may have noticed I refered to a Model Check mode along these tables. I am afraid that I never have described this mode, but in fact it is pretty useful on designing BPath statements as it not only checks the syntactical correctness, but also:

      • validity of used relations and attributes against model
      • returns the (empty) data structure
      • in case the return type is a table, the table will contain exactly one row.

      There are some specialities where the result data structure can not be fully determined at design time, i.e.

      • The parent relation leads to an unambiguous object type only with an instantiated object. In cases where it is ambigous, it must be made unambigous using .._ParentObjectName
      • The return type of some functions may be unknown at design time, e.g. in the following example:~*/SearchResFlightRel{!RetVal:=if(@CONNID=14,1,"Hello")} At run time the type is determined with the first access.
      • Dereferenced targets on the left side of an assignment are leading to fields where the name is not known at design time, as in:
        ~*/SearchResFlightRel{!1:="F_"+@FLDATE;!1^!=1}
        The model check generates a field named 'UNKNOWNFIELDNAME n ' into the result structure, the type information should be correct. Note that several dereferenced assignments will lead to several entries as above and that one dereferenced assignment may lead to more than one added field at run time but only to one within model check.
      • Dereferenced targets on the right side of an assignment are leading to fields where the type is not known at design time, as in:
        ~*/SearchResFlightRel{!1:=if(@CONNID=14,"CONNID","FLDATE");!Ret=!1^!}
        In these cases the model check creates a field with type string containing the value: 'UNKNOWN DATATYPE'.

      Syntax of model check is:

      DATA: lv_bol_core TYPE REF TO cl_crm_bol_core.
      lv_bol_core = cl_crm_bol_core=>get_instance( ).
      MyResult = lv_bol_core->GET_PROPERTIES_BY_BPATH(
         IV_BPATH_STATEMENT = LV_SOURCECODE
         IV_BASE_NAMESPACE  = ''
         IV_BASE_OBJECTNAME = 'UIFSearchResFlight'
         IV_EVAL_MODE = 2 ).

      To use the syntax check or model check the exceptions caused by snytax errors have to be caught. For an explanation of all possible exceptions see above.

      Well, I guess this is more of another blog, I will write it down into a new blog. I guess there are still some things left worth to be published, so they may follow.

      Best regards, Juergen

      Author's profile photo Devashish Bhattacharya
      Devashish Bhattacharya

      HiJuergen Gatter, 

      Thanks for such a detailed explanation. I would be looking forward for your next blog.

      However I am facing an issue regarding the exceptions for which I will start a new thread.

      Regards,

      Devashish

      Author's profile photo Jacob Vennervald
      Jacob Vennervald

      Hi Juergen Gatter

      Thanks for a fantastic blog on BPath.

      I can't get the sorting to work. I am trying with marketing permissions on a customer and sort them by CHANNEL and VALID_FROM descending, but no sorting happens. It just returns the list like if I do no sorting...?
      Can you give a hint?

      DATA(permissions) = buil_header->get_related_entities_by_bpath( iv_bpath_statement = "~*CRMT_BUT_MKTPERM_ALL[!CHANNEL,!VALID_FROM:D,*]/BuilMarketingPermissionRel/*$' ).
      

      Best regards,

      Jacob Vennervald