Thursday, 24 June 2021

Profiling C/C++ Code Using GCC And Gprof

I recently had to profile a large C code project, to identify functions to target for further optimization and I used gprof for this task.

In this blog post I'm going to take the dot-product code from one of my earlier posts and use that to demonstrate how to profile an application. The original code is available here: https://blog.numerix-dsp.com/2013/01/lets-do-some-digital-signal-processing.html.

Gprof is part of the GNU Binutils package (https://www.gnu.org/software/binutils/) and in general this is installed alongside the standard GCC (build-essential) package with:

    sudo apt install build-essential

In order to generate the profile in a human friendly format (a .png image) we're going to use dot, which is part of the graphviz package. Graphviz can be installed using:

sudo apt install graphviz

We're also going to use gprof2dot (https://pypi.org/project/gprof2dot/). Depending on your Python installation you can use either of the following commands:

    pip:

        pip install gprof2dot

    conda:

        conda install -c conda-forge gprof2dot

I use Anaconda so gprof2dot was installed to:

    ~/anaconda3/lib/python3.8/site-packages/gprof2dot.py

Gprof is enabled at compile time using the '-pg' command-line option and inserts instrumentation code into the application to create a statistical approximation of the code execution by, typically, sampling the code execution every 10 milliseconds. This means that to get a good approximation of the function load then the code needs to run for a number of seconds to allow the statistics to be generated accurately. 

Here is the example code, in which main() calls two functions called dotp1() and dotp2(), which in turn call the main dotp() function multiple times to allow the profiler to do its work.

#include <stdio.h>
#define COUNT  48           // The filter size must be modulo 8
#define USE_ARRAY_INDEX 1   // Set to '1' to use array indexing or '0' to use pointers
float a[] = {
 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0,
 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0,
 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0,
 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0
 };
float b[] = {
 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0,
 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0,
 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0,
 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0,
 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0
 };
float dotp (const float *, const float *, const int count);
float dotp1 (const float *, const float *, const int count);
float dotp2 (const float *, const float *, const int count);

void main (void)
{
 float sum;
 sum = dotp1 (a, b, COUNT);
 sum = dotp2 (a, b, COUNT);
 printf ("Dot product = %f\n", sum);
}

float dotp (const float *x, const float *y, const int count)
{
 float sum = 0.0;
 for (int i = 0; i < count; i++) {
#if USE_ARRAY_INDEX
  sum += x[i] * y[i];
#else
  sum += *x++ * *y++;
#endif
 }
 return sum;
}
float dotp1 (const float *x, const float *y, const int count)
{
 float sum = 0.0;
 for (int i = 0; i < 10000000; i++) {
   sum = dotp (x, y, count);
 }
 return sum;
}
float dotp2 (const float *x, const float *y, const int count)
{
 float sum = 0.0;
 for (int i = 0; i < 12000000; i++) {
   sum = dotp (x, y, count);
 }
 return sum;
}

Here is the shell script to build and execute the app then generate the png output file:

#!/bin/bash
# Shell script for compiling app with GCC then profiling the executable

# Compile the app with profiling enabled
gcc dotp_gprof.c -Wall -Wno-main -std=c99 -pg -o dotp_gprof

# Run the app
./dotp_gprof

# Move gmon.out
mv gmon.out dotp_gprof.out

# Extract profile data and write to .dot file
gprof dotp_gprof dotp_gprof.out -Qdotp100 -Qdotp120 | python ~/anaconda3/lib/python3.8/site-packages/gprof2dot.py > dotp_gprof.dot

# Convert from dot to png file format
dot -Tpng -odotp_gprof.png dotp_gprof.dot

And here is the .png output:


In the image we can see the following information:

  1. The call graph structure
  2. The number of times each function is called
  3. The percentage of total executation time each function uses, overall and at each level

In this simple application we observe that the majority of the time is spent in the dotp() function, which is to be expected. We can also see the relative time split betweeen the dotp1() and dotp2() functions, given the number of iterations in their 'for' loops.


Friday, 26 March 2021

The 29th Annual Running Of The University Of Oxford Digital Signal Processing Course Will Now Be Held Online In 2021

The 29th annual running of the University Of Oxford Digital Signal Processing course has been over subscribed so we are re-running the course in the Autumn.

The online course was first held in 2020 and had excellent reviews from the attendees.

The next course will be run over a period of 6 weeks between Monday 27th September and Friday 5th November 2021.

Based on the classroom course, Digital Signal Processing (Theory and Application), this online course consists of weekly live online tutorials and also includes a software lab that can be run remotely. We'll include all the same material, many of the existing labs and all the interaction of the regular course.

Online tutorials are delivered via Microsoft Teams once each week and practical exercises are set to allow you to practice the theory during the week. 

You will also have access to the course VLE (virtual learning environment) to communicate with other students, view and download course materials and tutor support is available throughout.

Code examples will be provided although no specific coding experience is required. 

The live tutorials will be on Wednesday each week from 13:00 - 14:30 and 15:00 - 16:30 (GMT) with a 30-minute break in between.

You should allow for 10 - 15 hours study time per week in addition to the weekly lessons and tutorials.

After completing the course, you should be able to understand the workings of the algorithms we explore in the course and how they can solve specific signal processing problems.

Full Details Are Available Here


Wednesday, 3 March 2021

Integrating Visual Studio Compiler Tools With Visual Studio Code

Integrating Visual Studio compiler tools with Visual Studio Code is a really powerful solution for developing and debugging C/C++ code.

Assuming you have installed Visual Studio Code and Visual Studio compiler tools then before continuing I highly recommend installing the C/C++ for Visual Studio Code extension.

Here are the steps I have for the integration process:

Enable a "VS 2019 Community Prompt Here" integration with Explorer. I've based my solution on Daniel Cazzulino's excellent CommandPromptHere solution that is available here: https://github.com/kzu/CommandPromptHere.

This supports x86 compiler configuration by default so I've modified it slightly to enable x64 compilation and called the file "VS201964BitComCmdHere.inf"

;
; "CMD Prompt Here" PowerToy
;
; Copyright 1996 Microsoft Corporation
[version]
signature="$CHICAGO$"
[DefaultInstall]
CopyFiles = VS2019ComCmdHere.Files.Inf
AddReg    = VS2019ComCmdHere.Reg
[DefaultUnInstall]
DelFiles  = VS2019ComCmdHere.Files.Inf
DelReg    = VS2019ComCmdHereUninstall.Reg
[SourceDisksNames]
55="%VS2019ComCmdHereName%","",1
[SourceDisksFiles]
vs2019cmdhere-enterprise.inf=55
[DestinationDirs]
VS2019ComCmdHere.Files.Inf = 17
[VS2019ComCmdHere.Files.Inf]
VS2019ComCmdHere.INF
[VS2019ComCmdHere.Reg]
HKLM,%UDHERE%,DisplayName,,"%VS2019ComCmdHereName%"
HKLM,%UDHERE%,UninstallString,,"rundll32.exe syssetup.dll,SetupInfObjectInstallAction DefaultUnInstall 132 %17%\VS2019ComCmdHere.inf"
HKCR,Directory\Background\Shell\VS2019ComCmdHere,,,"%VS2019ComCmdHereAccel%"
HKCR,Directory\Background\Shell\VS2019ComCmdHere\command,,,"cmd.exe /k call ""C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"" && pushd ""%V"""
HKCR,Directory\Shell\VS2019ComCmdHere,,,"%VS2019ComCmdHereAccel%"
HKCR,Directory\Shell\VS2019ComCmdHere\command,,,"cmd.exe /k call ""C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"" && pushd ""%V"""
HKCR,Drive\Shell\VS2019ComCmdHere,,,"%VS2019ComCmdHereAccel%"
HKCR,Drive\Shell\VS2019ComCmdHere\command,,,"cmd.exe /k call ""C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"" && pushd ""%1"""
[VS2019ComCmdHereUninstall.Reg]
HKLM,%UDHERE%
HKCR,Directory\Shell\VS2019ComCmdHere
HKCR,Drive\Shell\VS2019ComCmdHere
HKCR,Directory\Background\Shell\VS2019ComCmdHere
[Strings]
VS2019ComCmdHereName="Developer Command Prompt Here for VS2019 Community"
VS2019ComCmdHereAccel="VS 2019 Community Prompt"
UDHERE="Software\Microsoft\Windows\CurrentVersion\Uninstall\VS2019ComCmdHere"

To use VS Code with Visual Studio, in the project folder you need to create a .vscode/launch.json file:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "cl.exe - Build and debug active file",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "console": "externalTerminal",
            //"console": "internalConsole",
            "preLaunchTask": "C/C++: cl.exe build active file"
        }
    ]

}

and a .vscode/tasks.json file:

{
"version": "2.0.0",
"tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: cl.exe build active file",
            "command": "cl.exe",
            "args": [
                "/Zi",
                "/EHsc",
                "/nologo",
                "-W4",
                "-D" "_CRT_SECURE_NO_WARNINGS=1",
                "/Fe:",
                "${fileDirname}\\${fileBasenameNoExtension}.exe",
                "${file}",
                "gnuplot_c.lib"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$msCompile"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "compiler: cl.exe"
        }
    ]

}

Now open VSCode and open the project folder: File | Open Folder ...

Save the workspace: File | Save Workspace As ...

Close VSCode

Now to use VSCode with Visual Studio, right click on the project folder in Explorer and choose: "VS2019 Community Prompt"

Type the following:

code workspace.code-workspace

You can now compile and debug your application, with full break point and single stepping support.


Monday, 11 January 2021

The 29th Annual Running Of The University Of Oxford Digital Signal Processing Course Will Now Be Held Online In 2021

As part of the University Of Oxford Summer Engineering Program for Industry, the 29th running of the Digital Signal Processing course is moving online.
The online course was first held in 2020 and had excellent reviews from the attendees.

The next course will be run over a period of 6 weeks between Wednesday 28th April and Tuesday 8th June 2021.

Based on the classroom course, Digital Signal Processing (Theory and Application), this online course consists of weekly live online tutorials and also includes a software lab that can be run remotely. We'll include all the same material, many of the existing labs and all the interaction of the regular course.


Online tutorials are delivered via live video once each week and practical exercises are set to allow you to practice the theory during the week. 
You will also have access to the course VLE (virtual learning environment) to communicate with other students, view and download course materials and tutor support is available throughout.
Code examples will be provided although no specific coding experience is required. 
The live tutorials will be on Wednesday each week from 13:00 - 14:30 and 15:00 - 16:30 (GMT) with a 30-minute break in between.
You should allow for 10 - 15 hours study time per week in addition to the weekly lessons and tutorials.
After completing the course, you should be able to understand the workings of the algorithms we explore in the course and how they can solve specific signal processing problems.


Sunday, 10 January 2021

A Simple And Portable C Command Line Option Parser

I recently had to write some cross platform code with a simple command line parser.

Rather than get caught up in any open source licensing issues I wrote my own.

To use the code, just add your options into parse_command_line () and update the messages in show_help (). 

Here it is, to use as you wish.

// Command line parser
// Copyright (c) 2021 Sigma Numerix Ltd
#include <stdlib.h>
#include <stdio.h>

// Command line options
int     d_number = 0;
int     f_flag = 0;
int     l_flag = 0;
char    s_string[80] = "";

void show_help (void);
void parse_command_line (int argc, char *argv[]);
void main (int argc, char *argv[])
{
    parse_command_line (argc, argv);                        //  Parse command line options
    printf ("d_number  : %d\n", d_number);
    printf ("f_flag    : %d\n", f_flag);
    printf ("l_flag    : %d\n", l_flag);
    printf ("s_string[]: \"%s\"\n", s_string);
    exit(0);
}
void parse_command_line (int argc, char *argv[])
{
    for (int argNum = 1; argNum < argc; argNum++) {
        if (*(argv[argNum]) == '-') {
            switch (*(argv[argNum]+1)) {                    // Get command letter
                case 'd':
                    d_number = (unsigned int)atoi(argv[argNum+1]);
                    if ((d_number < 0) || (d_number > 9)) {
                        printf ("Command line error: Debug number range 0..9\n");
                        exit(-1);
                    }
                    argNum++;
                    break;
                case 'f':
                    f_flag = 1;
                    break;
                case 'l':
                    l_flag = 1;
                    break;
                case 'S':
                    strcpy (s_string, argv[argNum+1]);
                    argNum++;
                    break;
                case 'h':
                    show_help ();
                    exit (0);
                    break;
                default:
                    printf ("Invalid parameter combination\n");
                    show_help ();
                    exit (0);
                    break;
            }
        }
        else {
            printf ("Invalid parameter combination\n");
            show_help ();
            exit (0);
        }
    }
}

void show_help (void)
{
    printf ("cmdline Example Program\n\n");
    printf ("usage: cmdline [-fhl] [-d debug_number]\n");
    printf ("   [-s debug_string]\n");
    printf ("       -d  Debug number (default: 0, range 0-9)\n");
    printf ("       -f  f option flag\n");
    printf ("       -l  l option flag\n");
    printf ("       -S  Debug string (default: empty)\n");
    printf ("       -h  Help\n\n");
}



Tuesday, 22 December 2020

Version 9.00 Of The SigLib DSP Library Released

Version 9.00 is the latest version of the SigLib Digital Signal Processing (DSP) library and is available now from http://www.numerix-dsp.com/siglib.html.

V9.00 now includes functions for training and infering Artificial Intelligence and Machine Learning Convolutional Neural Networks (CNNs). The SigLib ML functions are designed for embedded applications such as vibration montioring etc. They are architected for Edge-AI applications and have been written for the highest level of MIPS and memory optimization.

To evaluate the Numerix-DSP Libraries and these new AI algorithms, the latest version can be downloaded from here : http://www.numerix-dsp.com/eval/.