Inlining Library functions

25 days ago by da_cow (she/her) to c/cpp

So I have the following problem. I am currently writing some Libraries in C++ as a way to get started with the language. In my library I want to use the inline keyword for a function to reduce the overhead caused by lots of function calls. The problem I have is, that it seems like it is not possible to inline functions, if I dont define them in my libraries header file.

So I got three files, that I am working with. My library (lib.cpp), its header file (lib.h) and my main code (main.cpp). My testing code for my library looks like this (this is just example code that has the same structure, but with different functionality):

main.cpp

#include <iostream>
#include "lib.h"
using namespace std;

int main(){
  int count = 5;
  float sum = get_sum(count);
  cout << "Sum of 0-" << count << ": " << sum << endl;


  int num = get_num(6);
  cout << "Got num: " << num << endl;
  return 0;
  return 0;
}

And my library looks like this:
lib.cpp

#include "lib.h"
inline int get_num(int num){
  return num;
}

int get_sum (int count){
  int sum = 0;
  for(int i = 0; i < count; i++){
    sum = sum + get_num(i);
  }
  return sum;
}

Naturally I created my header file like this:
lib.h

#ifndef LIB_H
#define LIB_H
inline int get_num(int num);
int get_sum (int count);
#endif 

The problem with this is, that this code will throw an compile error that theres no definition for get_num in main.cpp. This can be fixed by simply moving the entire function with its body into the header file.

The problem I have with doing it like this is, that this causes my inline function to not show up inside my lib.cpp file and gets hidden away in the header file. Is there a way that I can define my inline functions in my libraries source code, and expose it as a public function through my header file?

eleijeep 6 points 25 days ago

inline is a hint to the compiler and doesn't guarantee that your function will be inlined, even when it's declared as such in the same compilation unit.

You can read the GCC docs about the inline keyword: https://gcc.gnu.org/onlinedocs/gcc/Inline.html

However it is possible to inline functions across compilation units. This is called Link-Time Optimization (LTO) and you can enable this in GCC using -flto. You must pass this flag at both compile and link time. Using LTO, you don't need to even declare the function as inline or define it in the same compilation unit. The compiler and linker can choose the optimization based on the function size and whatever optimization heuristics they use.

I tested this with a toy example:

spoiler
$ cat main.cpp 
#include <iostream>  
#include "lib.hpp"  

int main(int argc, char* argv[])  
{  
    set_int(13);  
    int x = get_int();  
    std::cout << x << std::endl;  
    return 0;  
}  
$ cat lib.hpp 
#ifndef __LIBHPP  
#define __LIBHPP  

void set_int(int x);  
int get_int();  

#endif  
$ cat lib.cpp 
#include "lib.hpp"  

static int val = 0;  

void set_int(int x)  
{  
    val = x;  
}  

int get_int()  
{  
    return val;  
}  
$ cat Makefile 

CPPFILES=main.cpp lib.cpp  
OBJFILES=${CPPFILES:.cpp=.a}  

CFLAGS=-O3 -flto  
LDFLAGS=-O1 -flto  

all: main  

.PHONY: clean  
clean:  
    rm -f main ${OBJFILES}  

main: ${OBJFILES}  
    g++ ${LDFLAGS} -o $@ $^  

%.a: %.cpp  
    g++ ${CFLAGS} -o $@ -c $<  

$ make clean && make  
rm -f main main.a lib.a  
g++ -O3 -flto -o main.a -c main.cpp  
g++ -O3 -flto -o lib.a -c lib.cpp  
g++ -O1 -flto -o main main.a lib.a  
$ objdump -S main | grep -A 11 "<main>:"  
0000000000000770 <main>:  
 770:	48 8d 3d a9 08 20 00 	lea    0x2008a9(%rip),%rdi        # 201020 <_ZSt4cout@@GLIBCXX_3.4>  
 777:	48 83 ec 08          	sub    $0x8,%rsp  
 77b:	be 0d 00 00 00       	mov    $0xd,%esi  
 780:	c7 05 aa 09 20 00 0d 	movl   $0xd,0x2009aa(%rip)        # 201134 <_ZL3val>  
 787:	00 00 00 
 78a:	e8 91 ff ff ff       	callq  720 <_ZNSolsEi@plt>  
 78f:	48 89 c7             	mov    %rax,%rdi  
 792:	e8 59 ff ff ff       	callq  6f0 <_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@plt>  
 797:	31 c0                	xor    %eax,%eax  
 799:	48 83 c4 08          	add    $0x8,%rsp  
 79d:	c3                   	retq  

The disassembly shows that there are no calls to set_int or get_int in main.

Compare this to the disassembly without optimization:

spoiler
$ make clean && make  
rm -f main main.a lib.a  
g++  -o main.a -c main.cpp  
g++  -o lib.a -c lib.cpp  
g++  -o main main.a lib.a  
$ objdump -S main | grep -A 21 "<main>:"  
000000000000088a <main>:  
 88a:	55                   	push   %rbp  
 88b:	48 89 e5             	mov    %rsp,%rbp  
 88e:	48 83 ec 20          	sub    $0x20,%rsp  
 892:	89 7d ec             	mov    %edi,-0x14(%rbp)  
 895:	48 89 75 e0          	mov    %rsi,-0x20(%rbp)  
 899:	bf 0d 00 00 00       	mov    $0xd,%edi  
 89e:	e8 93 00 00 00       	callq  936 <_Z7set_inti>  
 8a3:	e8 a1 00 00 00       	callq  949 <_Z7get_intv>  
 8a8:	89 45 fc             	mov    %eax,-0x4(%rbp)  
 8ab:	8b 45 fc             	mov    -0x4(%rbp),%eax  
 8ae:	89 c6                	mov    %eax,%esi  
 8b0:	48 8d 3d 69 07 20 00 	lea    0x200769(%rip),%rdi        # 201020 <_ZSt4cout@@GLIBCXX_3.4>  
 8b7:	e8 a4 fe ff ff       	callq  760 <_ZNSolsEi@plt>  
 8bc:	48 89 c2             	mov    %rax,%rdx  
 8bf:	48 8b 05 0a 07 20 00 	mov    0x20070a(%rip),%rax        # 200fd0 <_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GLIBCXX_3.4>  
 8c6:	48 89 c6             	mov    %rax,%rsi  
 8c9:	48 89 d7             	mov    %rdx,%rdi  
 8cc:	e8 6f fe ff ff       	callq  740 <_ZNSolsEPFRSoS_E@plt>  
 8d1:	b8 00 00 00 00       	mov    $0x0,%eax  
 8d6:	c9                   	leaveq 
 8d7:	c3                   	retq   
path: 0 25940845, hotness: undefined, score: 6, children: 2
cows_are_underrated 3 points 25 days ago

Thanks, that helps a lot. Seems like I gotta go and figure out compilation toolchains way earlier than I thought (although it is not too bad to learn how to manually compile stuff)

path: 0 25940845 25941377, hotness: undefined, score: 3, children: 0
RovingDiary 1 point 12 days ago

I thought LTO gets automatically enabled for release build configs of popular build system? Honestly I haven't started a project from scratch in years.

path: 0 25940845 26158023, hotness: undefined, score: 1, children: 0
irishPotato 1 point 25 days ago

I’m no C/++ expert, but my understanding is inlining only works in header files cause the rest is linked, could you have another header file inlining the thing and then import that to your .cpp?

path: 0 25940321, hotness: undefined, score: 1, children: 1
tunetardis 2 points 25 days ago

Yeah, you want to define the whole function in the header if you are using inline.

Note that in this day and age, whether or not a function actually gets inlined is determined by the optimizing compiler, so the inline key word doesn't carry the weight it once did. The main reason to use is if you want the possibility of inlining to occur across multiple source files, in which case you need to define the whole thing in the header.

With link time optimization, inlining may even happen automatically with a regular function defined in a .cpp with no inline, but you're writing a standalone library here, so that may not work in this case? I'm not sure.

path: 0 25940321 25941151, hotness: undefined, score: 2, children: 0
cpp
cpp

@programming.dev

login for more options
2380
415
47

The center for all discussion and news regarding C++.

Rules

  • Respect instance rules.
  • Don't be a jerk.
  • Please keep all posts related to C++.

go to feed...