Middleware, development tools, realtime operating system
software and services for superior embedded design


Home
QNX Community Resources
Technical Articles

QNX Technical Articles

QNX Software Systems
Developer Resources
Blogs
Board support packages
Foundry27 projects
Forums
Hardware support listing
Online video tutorials
Product documentation
Technical Articles

 

Global variables under QNX Neutrino

by Yves Charron

Global variables were often used under QNX 4 for slightly faster execution times, but you should use them sparingly under QNX Neutrino, if at all.

Give yourself a moment to relax and breathe before unearthing your war hammer and reviewing all the cool code you've recently written for QNX Neutrino, code that's literally patched with global variables. You can still use global variables, but not everywhere, without one undesirable side effect -- read on to find out how!

Development


Although global variables are a bane in most programming environments (including C/C++ under QNX Neutrino, our focus for this article), their use doesn't always affect a program's execution time. Unfortunately, under QNX Neutrino, using global variables in shared objects (.so files, the equivalent of .dlls under Windows) will mean a performance hit.

In general, writing code that works is on the minds of most students, but writing code that works well is on the minds of most professional/experienced coders. Not all professional coders are assembler gurus of old, resurrected into C coders still counting cycles. Most rely on their greatest boon: knowledge.

Linking an object

How you link an object can affect performance:

Static linking
Statically linking an object (library) has no performance hit when using global variables, because it's actually linked inside the linking app.
Shared linking
Linking to a shared object has a performance hit when using global variables. By how much? It all depends on the access rate to the global variable in question. Currently, three extra instructions are processed for each access to a global variable. You do the math. Testing your code is the best way to witness this performance hit.

Here's an example

You might be thinking, "Thanks for the knowledge, bud, but I'm not convinced and I'm too lazy to test it out!" All right, here's an example for you to try and convince yourself!

NOTE: I've included a compiled version here: example.tar.gz

    [so_example.c] - compile to libso_example.so
   
    int so_global_variable;
   
    void test_so_global_access(void)
    {
        so_global_variable = 0xFFFFFFFF;
    }
    [EOF]

    [a_example.c] - compile to liba_example.a
   
    int a_global_variable;
   
    void test_a_global_access(void)
    {
        a_global_variable = 0xFFFFFFFF;
    }
    [EOF]

    [example.c] - compile to example
   
    #include <dlfcn.h> // For dlopen().
    #include <stdio.h>
    #include <time.h>
   
    int global_variable;

    void test_global_access(void)
    {
        global_variable = 0xFFFFFFFF;
    }
   
    void output_timing(char *msg, clock_t start, clock_t end)
    {
    int timing;
   
        timing = (end - start) / CLOCKS_PER_SEC;
       
        printf(msg);
        printf("nttest result : %i.%i(in secs)\n", timing, timing%10);
       
    }

    int main(void)
    {
    void (*test_so_global_access)(void)=NULL;
    clock_t start, end;
    void *so;
    int i, j;
       
        // Setup and link shared object.
        if ((so = dlopen("libso_example.so", RTLD_NOW)) == NULL)
        {
            perror("dlopen");
            printf("dlerror: %sn", dlerror());
            exit(1);
        }
       
        if ((test_so_global_access = dlsym(so, "test_so_global_access")) == NULL)
        {
            printf("Failed to get test func from shared object.");
            exit(1);
        }
       
        // Run tests...

        // Inside program(.o): no performance hit.
        start = clock();

        for (i=0; i < 500; i++)
            for (j=0; j < 1000000; j++)
                test_global_access();

        end = clock();
       
        output_timing("Inside program :", start, end);

        // Inside statically linked library(.a): no performance hit.
        start = clock();

        for (i=0; i < 500; i++)
            for (j=0; j < 1000000; j++)
                test_a_global_access();

        end = clock();

        output_timing("Inside statically linked library :", start, end);

        // Inside shared object(.so): no performance hit.
        start = clock();

        for (i=0; i < 500; i++)
            for (j=0; j < 1000000; j++)
                test_so_global_access();
               
        end = clock();

        output_timing("Inside shared object :", start, end);
       
        return 0;
    }
    [EOF]

Test results


Here are the results from a test run on my PC:

Test machine


Intel Pentium III 550
Banshee Voodoo 3 video adapter
128 Megs RAM

Output

Location of global variable: Test result:
Inside program 7.7 secs
Inside statically linked library 7.7 secs
Inside shared object 17.7 secs

As you can see from the results, the theory is proven. Using global variables in a shared object is slower. Use them with care! When analyzing the results, don't forget the overhead of the function call, but a substantial gain in speed is present on a high access rate (500 * 1000000) in this case. Also, if you take a look at the assembly file using objdump -d libso_example.so and compare that to objdump -d example, you can clearly see the three extra instructions for the shared object file.

Conclusion


As a rule, when using shared objects under QNX Neutrino, avoid global variables for critical code, especially if they have a high access rate. Use them sparingly elsewhere -- but that's up to you (e.g. for a quick patch, flags, etc.) I'm sure you'll have lots of ideas on how to do this.

Follow up


Still to come on global variables: Global variables under QNX Neutrino: Avoiding them in Photon and standard apps.