1.18 Code Optimization in C Programming

Module 1.18 • Algorithmic Efficiency, Compiler Flags, Loop Invariants & Memory Alignment

1.18.1 Introduction

Code optimization is the process of improving a program so that it executes faster, consumes less memory, and utilizes system resources more efficiently without changing the program's output.

Optimization is not simply about reducing the number of lines of code. Sometimes a longer program can execute faster than a shorter one. Effective optimization focuses on improving performance, memory usage, maintainability, and scalability.

Optimization is especially important in:

1.18.2 Why Optimization Matters

A poorly optimized program may:

An optimized program:

1.18.3 Types of Optimization

Code optimization can be divided into:

  1. Algorithm Optimization
  2. Compiler Optimization
  3. Loop Optimization
  4. Mathematical Optimization
  5. Memory Optimization
  6. Function Optimization
  7. Data Structure Optimization
  8. Parallel Processing Optimization

1.18.4 Algorithm Optimization

Choosing the correct algorithm provides the largest performance improvement.

Example

Searching an element in:

10 Elements
Both methods work similarly.

Searching in: 1,000,000 Elements
The difference becomes significant.

Algorithm Complexity
Linear SearchO(n)
Binary SearchO(log n)

Example: Linear Search

#include<stdio.h>
 
int main()
{
    int arr[6]={12,24,36,48,60,72};
    int key=60;
    int found=0;
 
    for(int i=0;i<6;i++)
    {
        if(arr[i]==key)
        {
            found=1;
            break;
        }
    }
 
    printf("%s",found?"Found":"Not Found");
 
    return 0;
}

Example: Binary Search

#include<stdio.h>
 
int main()
{
    int arr[]={10,20,30,40,50,60,70};
    int low=0;
    int high=6;
    int key=60;
 
    while(low<=high)
    {
        int mid=(low+high)/2;
 
        if(arr[mid]==key)
        {
            printf("Found");
            break;
        }
        else if(arr[mid]<key)
            low=mid+1;
        else
            high=mid-1;
    }
 
    return 0;
}

Binary Search performs much better on large sorted datasets.

1.18.5 Compiler Optimization

Modern compilers automatically optimize code.

Common GCC optimization levels:

-O0   No Optimization
-O1   Basic Optimization
-O2   Recommended Optimization
-O3   Aggressive Optimization
-Os   Optimize for Size

Example:

gcc -O2 program.c -o program

Compiler optimizations include:

1.18.6 Loop Optimization

Loops consume significant CPU time. Optimizing loops can greatly improve performance.

Example: Inefficient Loop

for(int i=0;i<100;i++)
{
    values[i]=length*width*i;
}

The expression: length*width is calculated 100 times.

Optimized Version

int area = length * width;
 
for(int i=0;i<100;i++)
{
    values[i]=area*i;
}

Now the multiplication is performed only once.

1.18.7 Loop Unrolling

Loop unrolling reduces loop overhead.

Normal Loop

for(int i=0;i<8;i++)
{
    data[i]*=2;
}

Unrolled Loop

for(int i=0;i<8;i+=4)
{
    data[i]*=2;
    data[i+1]*=2;
    data[i+2]*=2;
    data[i+3]*=2;
}

Advantages:

1.18.8 Avoid Calculations Inside Loops

Bad Practice:

for(int i=0;i<500;i++)
{
    result[i]=x*(40-y/2)*i;
}

Optimized Version:

int factor=x*(40-y/2);
 
for(int i=0;i<500;i++)
{
    result[i]=factor*i;
}

This reduces unnecessary computations.

1.18.9 Mathematical Optimization

Some mathematical operations are slower than others. Speed order:

  1. Addition
  2. Subtraction
  3. Multiplication
  4. Division
  5. Modulus

Division is usually slower.

Example

Instead of:

result=a/b/c;

Use:

result=a/(b*c);

This reduces one division operation.

1.18.10 Bitwise Optimization

Bit operations are extremely fast.

Multiplication by Power of 2

Instead of:

value=value*16;

Use:

value=value<<4;

Because: 16 = 2⁴

Division by Power of 2

Instead of:

value=value/8;

Use:

value=value>>3;

Because: 8 = 2³

1.18.11 Expression Simplification

Reduce unnecessary operations.

Original

price*tax + tax*5

Simplified

(price+5)*tax

One multiplication operation is eliminated.

1.18.12 Function Optimization

Function calls create overhead. Each function call requires:

Example

Small functions can be declared as:

inline int square(int x)
{
    return x*x;
}

This reduces call overhead.

1.18.13 Use Function Prototypes

Always declare function prototypes.

int calculateTotal(int,int);

Benefits:

1.18.14 Efficient Parameter Passing

Bad Practice:

void display(struct Employee emp)

Entire structure gets copied.

Better Practice:

void display(struct Employee *emp)

Only an address is passed.

Benefits:

1.18.15 Register Variables

Frequently used variables can be suggested for register storage.

register int counter;

Example:

for(register int i=0;i<10000;i++)
{
    sum+=i;
}

Registers are faster than RAM. Note: Modern compilers automatically perform register allocation.

1.18.16 Memory Optimization

Memory operations are expensive.

Bad Practice

for(int i=0;i<100;i++)
{
    int *ptr=(int*)malloc(sizeof(int));
    free(ptr);
}

Memory is repeatedly allocated and freed.

Better Practice

int *ptr=(int*)malloc(100*sizeof(int));
 
for(int i=0;i<100;i++)
{
    ptr[i]=i;
}
 
free(ptr);

Allocate once and reuse memory.

1.18.17 Arrays vs Dynamic Allocation

When size is known, prefer:

int marks[100];

Instead of:

int *marks= malloc(100*sizeof(int));

Advantages:

1.18.18 Pre-Increment vs Post-Increment

Prefer: ++i Instead of: i++ when the previous value is not needed.

Example:

for(int i=0;i<100;i++)

can be written as:

for(int i=0;i<100;++i)

This may generate slightly more efficient machine code.

1.18.19 Switch Statement Optimization

Good Practice:

switch(choice)
{
    case 1:
    case 2:
    case 3:
        printf("Valid");
        break;
 
    default:
        printf("Invalid");
}

Grouping common cases reduces code duplication.

1.18.20 Pointer Optimization

Repeated pointer dereferencing can reduce performance.

Less Efficient:

for(int i=0;i<n;i++)
{
    *ptr = *ptr + 1;
}

Better:

int temp=*ptr;
 
for(int i=0;i<n;i++)
{
    temp++;
}
 
*ptr=temp;

1.18.21 String Comparison Optimization

String comparisons are relatively slow. Instead of immediately calling: strcmp(str1,str2);, Check: strlen(str1)==strlen(str2) first. If lengths differ, comparison can be skipped.

1.18.22 Variable Declaration Order

Proper ordering can reduce padding and alignment overhead.

Less Efficient:

char a;
double b;
int c;

Better:

double b;
int c;
char a;

This helps memory alignment.

1.18.23 Short-Circuit Evaluation

Logical operators stop evaluating when the result is already known.

OR Operator

A || B

If A is true, B is not evaluated. Place the condition most likely to be true first.

AND Operator

A && B

If A is false, B is skipped. Place the condition most likely to be false first.

1.18.24 Parallel Processing

Modern processors contain multiple cores. Workloads can be divided among multiple threads.

Example Concept:

#pragma omp parallel for

Applications:

1.18.25 Profiling Before Optimization

Never optimize blindly. First measure performance.

Tools:

gprof
perf
Valgrind
Visual Studio Profiler

Optimization should focus on bottlenecks.

1.18.26 Common Optimization Mistakes

Premature Optimization

Optimizing code before identifying performance problems.

Over-Optimization

Making code difficult to understand.

Ignoring Readability

Readable code is often more valuable than tiny performance gains.

Memory Leaks

Optimized programs must still release resources properly.

1.18.27 Best Practices

Summary

Verify Comprehension: Technical Knowledge Assessment

Click your choice for each question to view feedback immediately. Complete all questions to evaluate your metric score.