Saturday, March 15, 2008

dlopen: performance

When you use dynamic library loading you probably open library each time you want to load routine. As for loading/unloading routines on demand you should open library every time to ensure that library is loaded and you can access routine you are interested. This will heavily reduce performance and memory usage. Probably you can control whether library was opened or not but this requires more code from you. In glibc from 2.2 had appeared 2 very useful flags to cover this situation: dlopen now may get RTLD_NOLOAD and RTLD_NODELETE flags. With RTLD_NOLOAD you can check if library was loaded and with RTLD_NODELETE you say dlclose not to unload library. I've made some tests. W/o RTLD_NOLOAD and RTLD_NODELETE flags:

        void *handle;
        int i; 
        for (i=0;i<1000000;++i)
        {
                handle = dlopen("/usr/lib/libdl.so", RTLD_LAZY);
                if (handle == NULL)
                        break;
                dlclose(handle);
        }
This gave me
for i in 1 2 3; do time ./test; done
real 0m0.567s
user 0m0.568s
sys 0m0.000s

real 0m0.566s
user 0m0.556s
sys 0m0.000s

real 0m0.562s
user 0m0.556s
sys 0m0.000s
Pretty slow, I should say. And w/ RTLD_NOLOAD and RTLD_NODELETE flags:
        void *handle;
        int i; 
        for (i=0;i<1000000;++i)
        {
                handle = dlopen("/usr/lib/libdl.so", RTLD_LAZY|RTLD_NOLOAD|RTLD_NODELETE);
                if (handle == NULL)
                        handle = dlopen("/usr/lib/libdl.so", RTLD_LAZY|RTLD_NODELETE);
                if (handle == NULL)
                        break;
                dlclose(handle);
        }
This one gave me
for i in 1 2 3; do time ./test; done
real 0m0.542s
user 0m0.536s
sys 0m0.000s

real 0m0.570s
user 0m0.572s
sys 0m0.000s

real 0m0.554s
user 0m0.528s
sys 0m0.004s
The same. libdl is pretty small. Let's try something bigger. Tests results for /lib/libc-2.7.so: w/o RTLD_NOLOAD and RTLD_NODELETE flags:
for i in 1 2 3; do time ./test; done
real 0m0.656s
user 0m0.656s
sys 0m0.000s

real 0m0.649s
user 0m0.644s
sys 0m0.000s

real 0m0.647s
user 0m0.644s
sys 0m0.000s
w/ RTLD_NOLOAD and RTLD_NODELETE flags:
real 0m0.611s
user 0m0.612s
sys 0m0.000s

real 0m0.610s
user 0m0.600s
sys 0m0.000s

real 0m0.608s
user 0m0.604s
sys 0m0.000s
We have won ~0.040s. Not bad. And another one for /usr/lib/libdb_cxx-4.5.so[note, I have changed circumstances: loop ran only 1000 times]: w/o RTLD_NOLOAD and RTLD_NODELETE flags:
for i in 1 2 3; do time ./test; done
real 0m1.270s
user 0m1.016s
sys 0m0.244s

real 0m1.290s
user 0m1.084s
sys 0m0.188s

real 0m1.269s
user 0m1.036s
sys 0m0.236s
w/ RTLD_NOLOAD and RTLD_NODELETE flags:
for i in 1 2 3; do time ./test; done
real 0m0.003s
user 0m0.004s
sys 0m0.000s

real 0m0.003s
user 0m0.004s
sys 0m0.000s

real 0m0.003s
user 0m0.000s
sys 0m0.000s
Yay! They differ for more than 1.2 seconds. That's because /usr/lib/libdb_cxx-4.5.so had to load some extra libraries.
  • ldd /usr/lib/libdl.so
     linux-gate.so.1 =>  (0xb7fa2000)
     libc.so.6 => /lib/libc.so.6 (0xb7e53000)
     /lib/ld-linux.so.2 (0xb7fa3000)
  • ldd /lib/libc-2.7.so
     /lib/ld-linux.so.2 (0xb7f7f000)
     linux-gate.so.1 =>  (0xb7f7e000)
  • ldd /usr/lib/libdb_cxx-4.5.so
     linux-gate.so.1 =>  (0xb7f3b000)
     libpthread.so.0 => /lib/libpthread.so.0 (0xb7dcb000)
     libstdc++.so.6 => /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/libstdc++.so.6 (0xb7ce0000)
     libm.so.6 => /lib/libm.so.6 (0xb7cbb000)
     libc.so.6 => /lib/libc.so.6 (0xb7b86000)
     libgcc_s.so.1 => /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/libgcc_s.so.1 (0xb7b79000)
     /lib/ld-linux.so.2 (0x80000000)
Now we can see the difference. If your library has references to external library dlopen loads them each time if you haven't specified RTLD_NOLOAD and RTLD_NODELETE flags. I had better use RTLD_NOLOAD and RTLD_NODELETE. It requires some extra code from you and one extra open handle for the program instance per library. But it will produce much faster code. Be aware ;)

3 comments:

Android app development said...

By performance basic your blog is providing outstanding performance. This is one of the superior post.
Android app developers

Santosh said...

Why would someone close a handle if he intends to use it later? How it is beneficial to close a handle?

Ni@m said...

Hi Santosh,
it makes sense if you are bothering about the memory footprint and external function is called rarely.