OPEN CATALOG

Best practices for performance

PWR012: Pass only required fields from derived data types as parameters

Issue #

Pass only used fields from derived data types as parameters to promote data hiding.

Relevance #

Derived data types (such as structs in C) are convenient constructs to group and move around related variables. While in many cases this is an effective method to organize data, the compilers can have a hard time optimizing this code because increased visibility of data also renders optimizations more complex.  

Functions having derived data types used as parameters should make use of most if not all its fields. Ensuring that all fields from derived types passed as function parameters are used in the function body has several benefits: promotes data hiding, makes inputs and outputs more explicit, helps to prevent unintended variable modifications, and also contributes to improve compiler and static analyzer code coverage.

In parallel programming, derived data types are often discouraged when offloading to the GPU  because they may inhibit compiler analyses and optimizations due to pointer aliasing. Also, it can cause unnecessary data movements impacting performance or incorrect data movements impacting correctness and even crashes impacting code quality.

Actions #

Pass the used fields as separate parameters instead of the whole derived type.

Code example #

In the following example, a struct containing two arrays is passed to the foo function, which only uses one of the arrays:

#include <stdlib.h>
 
typedef struct
{
   int A[1000];
   int B[1000];
} data;
 
int foo(data *d)
{
   int result = 0;
   for (int i = 0; i < 1000; i++)
   {
       result += d->A[i];
   }
   return result;
}
 
void bar()
{
   data *d = (data *)malloc(sizeof(data));
   for (int i = 0; i < 1000; i++)
   {
       d->A[i] = d->B[i] = 1;
   }
   int result = foo(d);
}

This can be easily addressed by only passing the required array and rewriting the function body accordingly:

#include <stdlib.h>
 
typedef struct
{
   int A[1000];
   int B[1000];
} data;
 
int foo(int *A)
{
   int result = 0;
   for (int i = 0; i < 1000; i++)
   {
       result += A[i];
   }
   return result;
}
 
void bar()
{
   data *d = (data *)malloc(sizeof(data));
   for (int i = 0; i < 1000; i++)
   {
       d->A[i] = d->B[i] = 1;
   }
   int result = foo(d->A);
   free(d);
}

Related resources #

References #