This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Compiler optimisation

Hello,

1.when i use printf with no var_args then the compiler should call puts instead of printf.

ex1:


#include <REGX51.H>
#include <stdio.h>

void main(void)
{
        printf("This must call puts instead of printf");
}


Program Size: data=30.1 xdata=0 code=1103

ex2:


#include <REGX51.H>
#include <stdio.h>

void main(void)
{
        puts("This must call puts instead of printf");
}


Program Size: data=9.0 xdata=0 code=168

The above code links the printf function from the library which is huge(produces 1103 bytes).But the compiler can use puts when there is no var_args given which is much smaller than printf(produces 168 bytes).

2.The Compiler must find and remove the duplicate constant strings

ex3:


#include <REGX51.H>
#include <stdio.h>

void main(void)
{
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
        puts("This string gets duplicated as many time as i use it");
}


Program Size: data=9.0 xdata=0 code=334

ex4:


#include <REGX51.H>
#include <stdio.h>

void main(void)
{
        puts("This string gets duplicated as many time as i use it");
}


Program Size: data=9.0 xdata=0 code=183

3.Bit Test instructions are not used when i actually test for the bit

ex5:


#include <REGX51.H>
#include <stdio.h>


void main(void)
{
        if(P0^1)
        {
                P1 = 10;
        }

}


ASSEMBLY LISTING OF GENERATED OBJECT CODE

             ; FUNCTION main (BEGIN)
                                           ; SOURCE LINE # 6
                                           ; SOURCE LINE # 7
                                           ; SOURCE LINE # 8
0000 E580              MOV     A,P0
0002 6401              XRL     A,#01H
0004 6003              JZ      ?C0002
                                           ; SOURCE LINE # 9
                                           ; SOURCE LINE # 10
0006 75900A            MOV     P1,#0AH
                                           ; SOURCE LINE # 11
                                           ; SOURCE LINE # 13
0009         ?C0002:
0009 22                RET
             ; FUNCTION main (END)

In the above assembly output it should have used a single instruction JNB instead of three MOV,XRL and JZ.This is very basic anybody would object the assembly code produced.

I have not used the compiler much.But the compiler needs a look by the programmers at keil.

The above programs were all compiled with compiler optimisation level set to 9 & favour speed.

About 5 years back i compiled a c51 source code using keil.
Now i recompiled the same source code with the latest compiler from keil and compared the two output .hex files.
Unfortunately it produced exactly the same output.Here i was expecting some code and data size reduction as the compiler must be capable of optimising more.

It seems there was no improvement on the compiler side.

It is not a complaint but in the interest of improving the compiler.

regards,

S.Sheik mohamed

  • if the optimiser uses puts instead printf only when possible

    What you still don't get is that it in the case at hand, it is not possible.

    then we can reduce the size of the code

    No we can't. That only works if you can replace all printf()s by puts() --- not just some of them. The call to printf() uses exactly as much code space as the one to puts(), and the code for printf() itself will not go away as long as even a single irreducible use of printf() remains.

    5.Let me know what harm it will do if the optimser replaces (only when possible)printf("Testing"); with puts("Testing");.

    Since that's never possible, the question is moot.

    5.Again the compiler knows that i am testing for the bit.

    No, it doesn't. Because you're not. The compilers knows what you actually wrote, not what you think you did.

    And you've been told that about half a dozen times now. It's high time you start listening, instead of just repeating your incorrect claims.

  • Ok,

    In that case atleast the compiler could even have different versions of printf and use the one which is appropriate for the current project.

    That is if i have never used float inside printf in my project then float to string part of the printf library is not neccessary.

    The comipler/linker during optimization can decide which printf library would be suitable for my project.

  • "That is if i have never used float inside printf in my project then float to string part of the printf library is not neccessary"

    That is exactly what happens already!

    Have you now understood why the code that you thought did a single-bit test does not actually do a single-bit test?

  • Note that it isn't trivial for the linker to analyze object files and try to figure out which of several printf() functions to use.

    Remember that not all printf() calls needs to look like:

    printf("formatting string",param,param,...);
    


    You can also have:

    void function(const char* fmt) {
        printf(fmt,int1,int2);
    }
    


    and you can have:

    char fmt[100];
    sprintf(fmt,"xxx",...);
    printf(fmt,...);
    

    The linker runs at link time. It doesn't know what happens at run time. A program could have multiple sets of strings, to allow it to print the same messages in english, italian, german, ...

    And remember that printf() and sprintf() shares the same background "engine", so it isn't enough to look at all printf() calls.

    The only one who really knows everything about your program - or is expected to - is you.

  • Not to continue the the discussion about the C language.

    But a look at the 8051 architecture. Please note, that
    there are port access instructions that read the latch
    and others that read the port pin. You will find this
    in every description of standard 8051.

    What does that mean? This means, that under certain
    circumstances (please think about that yourself) you
    will have

     if (P0 ^ 1) // the if-condition is FALSE
       P0.0 = 0;
    

    and

     if (_testbit(P0.0)) // the if-condition is TRUE.
    


    So, already on the hardware level, these both are NOT the same!

  • Hello All,

    Thank you all for keeping patience with me.

    I agree that all my allegations were complete wrong.

    1.when i use printf with no var_args then the compiler should call puts instead of printf.

    puts Adds extra linefeed to the string so it cannot be used instead of printf.So i was wrong here.

    But i think the compiler could be supplied with different versions of printf and let the user decide which printf version is best for him.This way compiler & linker need not struggle to find the best printf.

    The optimiser should have used a counter and repeated the following block

    
        MOV     R3,#0FFH
        MOV     R2,#HIGH ?SC_0
        MOV     R1,#LOW ?SC_0
        LCALL   _puts
    
    

    2.The Compiler must find and remove the duplicate constant strings

    No the compiler allocates the string only once.it was again my mistake

    3.Bit Test instructions are not used when i actually test for the bit

    I must have used (P0 & 1) instead of (P0 ^ 1) again my mistake.

    But when i use (P0 & 1) the compiler understood my intention of bit testing but it has assembled it in a different way.

    #include <REGX51.H>
    #include <stdio.h>
    
    
    void main(void)
    {
            if(P0 & 1)
            {
                                    if(P0_1)
                                    {
                    P1 = 10;
                                    }
            }
    
    }
                 ; FUNCTION main (BEGIN)
                                               ; SOURCE LINE # 5
                                               ; SOURCE LINE # 6
                                               ; SOURCE LINE # 7
    0000 E580              MOV     A,P0
    0002 30E006            JNB     ACC.0,?C0003
                                               ; SOURCE LINE # 8
                                               ; SOURCE LINE # 9
    0005 308103            JNB     P0_1,?C0003
                                               ; SOURCE LINE # 10
                                               ; SOURCE LINE # 11
    0008 75900A            MOV     P1,#0AH
                                               ; SOURCE LINE # 12
                                               ; SOURCE LINE # 13
                                               ; SOURCE LINE # 15
    000B         ?C0003:
    000B 22                RET
                 ; FUNCTION main (END)
    
    

    where it could have simply put "JNB P0_1" instead of "mov a,P0" & "jnb ACC.0"

    But overall if you compile a source file using an old version of the compiler and again with the new version of the compiler the produced hex file is byte to byte same.

    Why the compiler or its optimizer has not improved in reducing the code & data size for many years.

    Once again thank for all your patience

    S.Sheik mohamed

  • "The ^ may _only_ be used when declaring bit variables"

    Here is where i got confused.

    Because i had used something like P0^1 so i thought that is the only way to reference a bit.
    i do not know why keil chose to use P0^1 to declare bits instead of P0.1
    I just used keil only after about 5 years.in fact i do not use MCS51.

    Thanks

    Sheik mohamed

  • Yes, it is a very common source of confusion - it caught me out when I first started with C51!

    It does seem to be a rather poor choice on Keil's part, and it is certainly not well explained in the manual.

    :-(

    "I must have used (P0 & 1) instead of (P0 ^ 1)"

    No - that is still a whole byte operation!

    if you want to use the 8051's single-bit features, then you have to define a single-bit variable:

    sbit P0_1 = P0 ^ 1; // Define a single-bit variable
    
    :
    
    if( P0_1 )          // Test the single-bit variable
    
    :
    
    P0_1 = 1;           // Set the single-bit variable
    
    :
    
    P0_1 = 0;           // Clear the single-bit variable
    

    http://www.keil.com/support/man/docs/c51/c51_le_sbit.htm

  • That's all very well to say - but what features would go in the "small" printf?

    Every user would have their own opinion of what is an "important" feature to have in printf, and what is a "luxury".

    In the end, it's probably easiest for the compiler to provide a "full" printf, and, if the user has specific requirements, then they code their own according to those requirements.

  • The Keil C51 tools have been around for many years - decades, in fact - so, obviously, all the easy optimisations were done years ago.

    However, since Keil was acquired by ARM, one might tend to suspect that the non-ARM tools might be receiving less focus...

  • Yes, I got your point.

    But when i use (P0 & 1) the compiler knew that i was testing for bit and it has assembled the right bit instruction for testing bit and not "AND" instruction for testing the whole byte.That is very nice & wise of the compiler.But the compiler did not check if the address is bit-addressable or not.If it had found the address is bit-addressable then it could have assembled more specific "jb" instruction.

    Sheik mohamed

  • Simple,

    One with full functionality.
    Another one with floating point support removed.I think this will reduce the code dramatically.

    In majority of the application the float may not be neccessary.

    Sheik mohamed

  • No, you clearly didn't!

    "when i use (P0 & 1) the compiler knew that i was testing for bit"

    No, it does not!!

    P0 is an 8-bit value;

    1 is an integral constant.

    In strict ANSI 'C', the integral constant is considered an int, and the 8-bit value would be promoted to an int before doing a bitwise 'AND' of all bits and giving an int result.
    Effectively, the expression is:

    ( P0 & 0x0001 )
    

    Keil C51 gives you the option to disable this promotion, so that the expression becomes just an 8-bit operation.

    But the only way to get Keil C51 to operate on a single bit is to use the specific bit operations.

    Again, ANSI 'C' bitwise operators have noting to do with the 8051's single-bit operations!

  • In strict ANSI 'C', the integral constant is considered an int, and the 8-bit value would be promoted to an int before doing a bitwise 'AND' of all bits and giving an int result.

    With the integer promotion, the semantics of the operation do not change: the code is still testing for a bit. In this particular case, if the compiler knew that the register is bit-addressable and chose not to ignore this information it could test for this bit directly.

    But the only way to get Keil C51 to operate on a single bit is to use the specific bit operations.

    Well, that's the point. A smarter compiler will use faster and more compact code constructs where appropriate. This is clearly one of those cases.

    Again, ANSI 'C' bitwise operators have noting to do with the 8051's single-bit operations!

    I'm not sure what you mean by that. I've seen the Green Hills compiler for Coldfire generate BSET and BCLR instructions when I was using bitwise OR and bitwise AND operators to set or clear bits. Why wouldn't C51 do the same? Especially since the 8051 core does a read-modify-write internally to set or clear bits, so semantics are the same.