Wish Compile B4X constants to native Java "finals"

stanmiller

Active Member
Licensed User
Longtime User
Wish: Compile B4X constants to native Java "finals"

Today B4X constants are implemented by the IDE/precompiler only. If you try to reassign a value to a constant it's the IDE that raises the error.

When B4X compiles the app to Java the B4X constants are converted to regular Java variables and thus do not benefit from the Java compiler's ability to optimize constants with inlining.

ABMaterial applications greatly benefit from the use of constants to give meaning to the dozens of parameters used for building the grid, navigationbar, themes, menus, and so on... Each ABM page may have dozens of API calls executed each time the page is refreshed.

While using constants adds clarity to ABM pages, this adds overhead to load the B4X constant variables. If they were compiled to native Java constants their would be little to no performance penalty.

This routine uses four B4X constants.
B4X:
'*------------------------------------------------------------------- BuildPageGrid
'*
Sub BuildPageGrid()
    Private Const ROW_MARGIN_TOP=4, ROW_MARGIN_BOTTOM=4 As Int
    Private Const CENTER_IN_ROW=True As Boolean
    Private Const DEFAULT_THEME="" As String
    Private mt, mb, pl, pr As Int

    ' -----------------------------------------------------
    ' Notes
    '
    ' mt = margin top (px)
    ' mb = margin bot (px)
    ' pl = pad left   (px)
    ' pr = pad right  (px)
  
    ' -----------------------------------------------------
    ' 1. Category
    ' 2. Products
    ' 3. Project Dimensions
    ' 4. Length
    ' 5. Width
    ' 6. Depth
    ' -----------------------------------------------------
    page.AddRowsM( 6, CENTER_IN_ROW, ROW_MARGIN_TOP, ROW_MARGIN_BOTTOM, DEFAULT_THEME ).AddCellsOS(1, 0, 0, 0, 11, 6, 6, DEFAULT_THEME).AddCellsOS(1, 0, 0, 0, 1, 6, 6, DEFAULT_THEME)

    ' -----------------------------------------------------
    ' 7. Amount
    ' -----------------------------------------------------
    mt=2 : mb=0 : pl=16 : pr=0
    page.AddRowsM( 1, CENTER_IN_ROW, ROW_MARGIN_TOP, ROW_MARGIN_BOTTOM, DEFAULT_THEME ).AddCellsOSMP(1, 0, 0, 0, 11, 6, 6, mt, mb, pl, pr, DEFAULT_THEME).AddCellsOSMP(1, 0, 0, 0, 1, 6, 6, 0, 0, 0, 0,DEFAULT_THEME)

    ' -----------------------------------------------------
    ' 8. Calculate
    ' -----------------------------------------------------
    mt=12 : mb=0 : pl=0 : pr=0
    page.AddRowsM( 1, CENTER_IN_ROW, ROW_MARGIN_TOP, ROW_MARGIN_BOTTOM, DEFAULT_THEME ).AddCellsOSMP(1, 0, 0, 0, 11, 6, 6, mt, mb, pl, pr, DEFAULT_THEME).AddCellsOSMP(1, 0, 0, 0, 1, 6, 6, 0, 0, 0, 0,DEFAULT_THEME)

    ' -----------------------------------------------------
    '  9. Blank row
    ' 10. Blank row
    ' -----------------------------------------------------
    page.AddRowsM( 2, CENTER_IN_ROW, ROW_MARGIN_TOP, ROW_MARGIN_BOTTOM, DEFAULT_THEME ).AddCellsOS(1, 0, 0, 0, 11, 6, 6, DEFAULT_THEME).AddCellsOS(1, 0, 0, 0,  1, 6, 6,    DEFAULT_THEME)

End Sub

These could be optimized with Java finals.

1_b4x_native_java_constants_zps4cideqyf.jpg
 
Last edited:

stanmiller

Active Member
Licensed User
Longtime User
I don't think that you will be able to see any performance difference. The Java compiler knows that they are never assigned again anyway.

Perhaps the JIT may sort things out after launch, but the bytecode is loading the data with variables.

2_b4x_constant_bytecode_zps7z1ptlww.jpg
 

Attachments

  • prodcalc_decompile.txt
    101.3 KB · Views: 226

keirS

Well-Known Member
Licensed User
Longtime User
As I understand it a true constant is declared as static final in Java. If you are worried about efficiency why are you declaring a constant in a Sub? If you declared it at the Class level it would only be created once each time the class is instantiated rather than at the moment where it's created each time the sub is called.
 

stanmiller

Active Member
Licensed User
Longtime User
As I previously wrote you will never see any difference in a real application. Especially in a web application.

I'd expect you would want B4X to generate the best code possible. Particularly, when this seems like a simple change to the code generator.

We're not really concerned with a few extra instructions building a web page, but when we saw over 700 bytecodes for each iteration through a loop - that caught our attention.

Things get more interesting when those constants are wrapped in a B4X module.

We purposely wrote our MteEval expression compiler in B4X instead of Java or ObjectiveC to keep it accessible to everyone.

https://www.b4x.com/android/forum/t...-compiler-and-eval-library-open-source.70299/

B4X is promoted as a native code generator so we assumed when we implemented the virtual machine in a classic "switch/case" construct using B4X constants that it would be translated to a jump table as you find in C or Java. Instead we found that every PCODE constant is reloaded with each MteEval instruction.

Below is a snippet of the loop from RUN.BAS that executes MteEval pcodes. Executing a PCODE.PUSH in B4X requires over 700 bytecodes.
B4X:
    ' Set instruction pointer
    nIP = CODE_STARTS_HERE

    Do While ( bRun )

        ' get op code
        nPcode = Code.get( nIP )

        ' Execute
        Select ( nPcode )

        Case PCODE.PUSH

            ' Advance stack pointer and store
            nSP = nSP + 1

            ' Overflow?
            If ( nSP > STACK_SIZE ) Then
                StackOverFlowError( oCodeBlock, nIP, nAX, nSP )
                Return ( 0 )
            End If

            aStack( nSP ) = nAX

            'Mtelog.Dbg( "<--- Push AX=" & nAX )

        Case PCODE.NEG

            nAX = - (nAX)

        Case PCODE.ADD

            nAX = aStack( nSP ) + nAX
            nSP = nSP - 1                     ' pop

        Case PCODE.SUBTRACT

            nAX = aStack( nSP ) - nAX
            nSP = nSP - 1                      ' pop


    Loop

Much of this has to do with how Select/Case is implemented in B4X. From the code below, B4X first loads every possibility to evaluate a Select/Case then calls BA.switchObjectToInt() to do the compare.
B4X:
while ((_brun)) {
//BA.debugLineNum = 244;BA.debugLine="nPcode = Code.get( nIP )";
_npcode = (int)(BA.ObjectToNumber(_code.get(_nip)));
BA.debugLineNum = 247;BA.debugLine="Select ( nPcode )";

switch (BA.switchObjectToInt((_npcode),_pcode._push,_pcode._neg,_pcode._add,_pcode._subtract,_pcode._multiply,_pcode._divide,_pcode._modulo,_pcode._logical_or,_pcode._logical_and,_pcode._logical_not,_pcode._equal,_pcode._not_equal,_pcode._less_than,_pcode._less_equal,_pcode._greater_than,_pcode._greater_equal,_pcode._bit_and,_pcode._bit_or,_pcode._bit_xor,_pcode._bit_not,_pcode._bit_shift_left,_pcode._bit_shift_right,_pcode._jump_always,_pcode._jump_false,_pcode._jump_true,_pcode._loadconst,_pcode._loadvar,_pcode._storevar,_pcode._func_abs,_pcode._func_max,_pcode._func_min,_pcode._func_sqrt,_pcode._func_power,_pcode._func_round,_pcode._func_floor,_pcode._func_ceil,_pcode._func_cos,_pcode._func_cosd,_pcode._func_sin,_pcode._func_sind,_pcode._func_tan,_pcode._func_tand,_pcode._func_acos,_pcode._func_acosd,_pcode._func_asin,_pcode._func_asind,_pcode._func_atan,_pcode._func_atand,_pcode._func_number_format,_pcode._func_avg,_pcode._endcode)) {
case 0: {
//BA.debugLineNum = 252;BA.debugLine="nSP = nSP + 1";
_nsp = (int) (_nsp+1);
//BA.debugLineNum = 255;BA.debugLine="If ( nSP > STACK_SIZE ) Then";
if ((_nsp>_stack_size)) {
//BA.debugLineNum = 256;BA.debugLine="StackOverFlowError( oCodeBlock, nIP, nAX, nSP";
_stackoverflowerror(_ocodeblock,_nip,_nax,_nsp);
//BA.debugLineNum = 257;BA.debugLine="Return ( 0 )";
if (true) return (0);
};
//BA.debugLineNum = 260;BA.debugLine="aStack( nSP ) = nAX";
_astack[_nsp] = _nax;
break; }
case 1: {

Because all of the MteEval pcodes are constants the "select/case" possibilities only need to be loaded once. This would save the 700 bytecode penalty and wouldn't require any changes to how select/case is currently implemented.

The change below nearly triples the execution speed of the MteEval VM.
B4X:
// Load case values once
Object[] codes = {_pcode._push,_pcode._neg,_pcode._add,_pcode._subtract,_pcode._multiply,_pcode._divide,_pcode._modulo,_pcode._logical_or,_pcode._logical_and,_pcode._logical_not,_pcode._equal,_pcode._not_equal,_pcode._less_than,_pcode._less_equal,_pcode._greater_than,_pcode._greater_equal,_pcode._bit_and,_pcode._bit_or,_pcode._bit_xor,_pcode._bit_not,_pcode._bit_shift_left,_pcode._bit_shift_right,_pcode._jump_always,_pcode._jump_false,_pcode._jump_true,_pcode._loadconst,_pcode._loadvar,_pcode._storevar,_pcode._func_abs,_pcode._func_max,_pcode._func_min,_pcode._func_sqrt,_pcode._func_power,_pcode._func_round,_pcode._func_floor,_pcode._func_ceil,_pcode._func_cos,_pcode._func_cosd,_pcode._func_sin,_pcode._func_sind,_pcode._func_tan,_pcode._func_tand,_pcode._func_acos,_pcode._func_acosd,_pcode._func_asin,_pcode._func_asind,_pcode._func_atan,_pcode._func_atand,_pcode._func_number_format,_pcode._func_avg,_pcode._endcode };

while ((_brun)) {
//BA.debugLineNum = 244;BA.debugLine="nPcode = Code.get( nIP )";
_npcode = (int)(BA.ObjectToNumber(_code.get(_nip)));
BA.debugLineNum = 247;BA.debugLine="Select ( nPcode )";

// if ( true ) return ( 25d );
switch (BA.switchObjectToInt((_npcode),codes)) {

case 0: {
//BA.debugLineNum = 252;BA.debugLine="nSP = nSP + 1";
_nsp = (int) (_nsp+1);
//BA.debugLineNum = 255;BA.debugLine="If ( nSP > STACK_SIZE ) Then";
if ((_nsp>_stack_size)) {
//BA.debugLineNum = 256;BA.debugLine="StackOverFlowError( oCodeBlock, nIP, nAX, nSP";
_stackoverflowerror(_ocodeblock,_nip,_nax,_nsp);
//BA.debugLineNum = 257;BA.debugLine="Return ( 0 )";
if (true) return (0);
};
//BA.debugLineNum = 260;BA.debugLine="aStack( nSP ) = nAX";
_astack[_nsp] = _nax;
 
Last edited:

stanmiller

Active Member
Licensed User
Longtime User
As I understand it a true constant is declared as static final in Java. If you are worried about efficiency why are you declaring a constant in a Sub? If you declared it at the Class level it would only be created once each time the class is instantiated rather than at the moment where it's created each time the sub is called.

That code was just an example. You might declare a constant local to a Sub if that is the only place you are using said constant.

Constants provide clarity and shouldn't incur any execution speed penalty.

With
B4X:
page.AddRowsM( 6, CENTER_IN_ROW, ROW_MARGIN_TOP, ROW_MARGIN_BOTTOM, DEFAULT_THEME ).AddCellsOS(1, 0, 0, 0, 11, 6, 6, DEFAULT_THEME).AddCellsOS(1, 0, 0, 0, 1, 6, 6, DEFAULT_THEME)

Without
B4X:
page.AddRowsM( 6, true, 4, 4, "").AddCellsOS(1, 0, 0, 0, 11, 6, 6, "").AddCellsOS(1, 0, 0, 0, 1, 6, 6,"")
 
Last edited:

keirS

Well-Known Member
Licensed User
Longtime User
You are missing the point I was trying to make. In Java to have a compile time constant (one where the. variable reference is replaced with the actual value) you need to have a variable declaration that is static final. It also needs to be a primitive type.

You can't declare a static final in a method unless perhaps that method is also declared as static final.

So asking Erel to put final before the constant declaration is pointless because it will not achieve what you think it will.
 

stanmiller

Active Member
Licensed User
Longtime User
So asking Erel to put final before the constant declaration is pointless because it will not achieve what you think it will.

Let's look at an example.
B4X:
public class Main {
public static void main(String[] args) {
int y=7;
int x=5;
int j;
j= x + y;
System.out.println( "j=" + j);}
}

On the left is the bytecode for y and x declared as ints. On the right, x and y were prefixed with final. With final you get a 40 basis point improvement.

1_java_final_optimization_zpszsxe8qis.jpg


Instead of "inlining" (which is a form of optimization and needs all those things you said about static declaration) I should have said "optimize".

The gist is why not allow your code generator to take full advantage of the target platform's capabilities? Then let the Java compiler/JIT take it from there.
 
Last edited:

stanmiller

Active Member
Licensed User
Longtime User
The code generated for the "select case" block is a different issue. It is not related to the 'final' modifier.
The compiler checks whether it can general a simple select case or it needs to use the more complicated version because the JVM only supports a few data types.

Understood. Our select/case example is unusual as you don't typically have 50 or more choices. :)

For our next release we've moved the VM to inline Java. Also we're publishing a native Java edition that one can link to their B4X app.
 
Top