3

For reasons out of my control, I have to implement this function in my C code:

double simple_round(double u)
{
    return u;
}

When this function is called, is it ignored by the compiler, or does the call take place anyway? For instance:

int y;
double u = 3.3;

y = (int)simple_round(u*5.5); //line1
y = (int)u*5.5;               //line2

Will both lines of code take the same time to be executed, or will the first one take longer?

phuclv
  • 37,963
  • 15
  • 156
  • 475
Marcos
  • 107
  • 5
  • 4
    In practice: It depends on the optimization settings and whether the function is defined in the same file that uses it. – user253751 Jul 29 '22 at 16:50
  • 1
    We know near-nothing about the context setting up that call. Compile to release-optimized asm and check the code. – WhozCraig Jul 29 '22 at 16:50
  • this trivial code will be optimized to nothing. – 0___________ Jul 29 '22 at 16:51
  • @user253751 I'm using MinGW and the flag -O3 and the function is in a different C file – Marcos Jul 29 '22 at 16:58
  • *"For reasons out of my control"* Curious to know what are those reasons... – Zakk Jul 29 '22 at 16:58
  • 2
    @Zakk Part of the code is automatically generated and includes by default a rounding function that is very inefficient. The only thing that I can do is to replace that function with a simpler one. – Marcos Jul 29 '22 at 17:00
  • @Marcos So, you want that call to persist no matter what? Right? I.e., you don't want it to get optimized? – Zakk Jul 29 '22 at 17:15
  • I'd like to get rid of that call. However, the generated code uses that rounding function no matter what. The only option I have is to replace the default round function with something else... – Marcos Jul 29 '22 at 17:18
  • 2
    @Marcos Check [this answer](https://stackoverflow.com/q/22767523/16835308) and [the documentation](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#:~:text=always_inline,not%20be%20diagnosed.). – Zakk Jul 29 '22 at 17:27
  • @Zakk: `__attribute__((always_inline))` can't help across `.c` files, only if the function's in a header. Also, it's not necessary; a normal optimization level will get this to inline without attributes. But at `-O0` it's not sufficient (not that performance should matter for an un-optimized build). GCC still [makes asm that copies to a return-value object](https://stackoverflow.com/questions/54073295/why-is-this-c-wrapper-class-not-being-inlined-away). I guess it could help at an optimization level like `-Og` that might not inline functions but doesn't force spill/reload like `-O0`. – Peter Cordes Jul 30 '22 at 03:49

3 Answers3

2

Because the function is defined in a different C file from where it's used, if you don't use link-time optimization, when the compiler calls the function call it won't know what the function does, so it will have to actually compile the function call. The function will probably just have two instructions: copy the argument to the return value, then return.

The extra function call may or may not slow down the program, depending on the type of CPU and what else the CPU is doing (the other instructions nearby)

It will also force the compiler to consider that it might be calling a very complicated function that overwrites lots of registers (whichever ones are allowed to be overwritten by a function call); this will make the register allocation worse in the function that calls it, perhaps making that function longer and making it need to do more memory accesses.

user253751
  • 57,427
  • 7
  • 48
  • 90
2

When this function is called, is it ignored by the compiler, or does the call take place anyway?

It depends. If the function definition is in the same *.c file as the places where it's called then the compiler most probably automatically inlines it, because it has some criteria to inline very simple functions or functions that are called only once. Of course you have to specify a high enough optimization level

But if the function definition is in another compilation unit then the compiler can't help unless you use link-time optimization (LTO). That's because in C each *.c file is a separate compilation unit and will be compiled to a separate object (*.o) file and compilers don't know the body of functions in other compilation units. Only at the link stage the unresolved identifiers are filled with their info from the other compilation units

In this case the generated code in a *.c file calls a function that you can change in another *.c file then there are many more reliable solutions

  1. The most correct method is to fix the generator. Provide evidences to show that the function the generated code calls is terrible and fix it

  2. In case you really have no way to fix the generator then one possible way is to remove the generated *.c file from the compilation list (i.e. don't compile it into *.o anymore) and include it in your own *.c file

    #define simple_round(x) (x)
    #include "generated.c"
    #undef simple_round
    

    Now simple_round() calls in generated.c will be replaced with nothing

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • WRT your 'case 2', could that 'erasure' of the function name (simple-round) via a #define be done on the line of the makefile that compiles 'generated.c'??? Wouldn't this be easier and more direct? – Fe2O3 Jul 30 '22 at 04:18
  • @Fe2O3 not everyone uses makefile, and in the OP's build system it might not be easy/possible to define a symbol only for a specific file – phuclv Jul 30 '22 at 04:26
1

If the 'generated' code has to be compiled anyway, perhaps you can 'kludge' a macro, Macro, that redefines the call to the 'inefficient' rounding function made by that code.

Here's a notion (all in one file). Perhaps the #define can be 'shimmed in' (and documented!) into the makefile entry for that single source file.

int fnc1( int x ) { return 5 * x; }

void main( void ) {
    printf( "%d\n", fnc1( 5 ) );

#define fnc1(x) (x)

    printf( "%d\n", fnc1( 7 ) );
}

Output:

25
7
Fe2O3
  • 6,077
  • 2
  • 4
  • 20
  • I'd expect it's hard to use a `#define fnc1(x)` without breaking the declaration (prototype) in a .h, especially if you do it with a `gcc -D'fnc1(x)=(x)'` command-line arg. And if you are willing to edit the `.h` itself, when you should just put the function in the header and declare it `static inline`. That's better and avoids use of the C preprocessor. (Although debug builds will still not be as efficient.) – Peter Cordes Jul 30 '22 at 03:24
  • @PeterCordes Thanks for that... It's a 'kludge', no two ways about it... My understanding of the OP problem is that the provided `round()` is "ineffiecient" (and "(re-)generated code" would keep clobbering any attempted edit that source code...) I dunno... Any command line 'defines' would be used by the preprocessor before the compiler gets its teeth into the code... For the 2nd `printf()` in my example, the compiler would see merely `printf( "%d\n", 7 );` – Fe2O3 Jul 30 '22 at 03:36
  • What they can't change easily is all the auto-generated calls to it. There should only be one prototype and one implementation, which they are proposing to change. Since I don't think you can make a macro work without editing a file (because it would substitute for the prototype), you might as well just replace the prototype with a trivial inline definition instead of adding a `#define` after. Perhaps your idea has some merit, though, if you consider a `#define` in a different `.h`. – Peter Cordes Jul 30 '22 at 03:55
  • But if the prototype is for a non-static non-inline function, you can still provide an `inline double simple_round(double x){return x;}` definition in the same compilation unit. https://godbolt.org/z/MKnbvzW7d shows no warning. (Unlike if you try to define a `static inline int foo()` after an `int foo()` prototype; that's not allowed.) – Peter Cordes Jul 30 '22 at 03:57
  • Or you could `#define` the original name to something else before including the `.h` with the prototype, then `#undef` it and provide your own definition (or CPP macro), with no existing prototype for that name. – Peter Cordes Jul 30 '22 at 03:58