0
\$\begingroup\$

This is a follow-up on my first question, Little program to measure costly operations, thanks for all answers and comments, esp. @JDługosz. I made a second attempt adapting a teaching program I found on the web.

It already helps me to 'get a handle' on things normally hidden, e.g. i can see the effort for 'POW' compared to divide and e.g the influence of compiler optimizations (besides they mostly result from optimizing away 'dead' calculations).

And as on repeated execution the results vary, rather in 'jumps' (up to ~1 : 2,5) than smoothly - there are conditions / circumstances which trigger bad or good performance which I can start to investigate (CPU throttling, memory alignment, caching, thread priority, a weak core or whatever). Although I'm not getting any clue about what is happening in detail, I get a clue that something is different. And I can check the influences of things I change. :-) :-) :-)

Any suggestions for further improvements welcome, and the questions:

  • Are better tools available? which?
  • How to port to C?
  • How to get the compiler doing its optimizations but not omitting the calculations to be timed?
/*
    gettime - get time via clock_gettime
    N.B.: OS X does not support clock_gettime

    Paul Krzyzanowski

    many thanks for the template, i hope i din't rape it too much, newbie-02
*/

#include <stdio.h>  /* for printf */
#include <stdint.h> /* for uint64 definition */
#include <stdlib.h> /* for exit() definition */
#include <time.h>   /* for clock_gettime */

#include <thread>   // reg. thread, 
#include <math.h>   // reg. pow( x, y ), 
#include <iostream> // reg. cout, 
 
#define BILLION 1000000000L

using namespace std::literals; // enables the usage of 24h, 1ms, 1s instead of
                               // e.g. std::chrono::hours(24), accordingly

int localpid(void) 
{
    static int a[9] = { 0 };
    return a[0];
}

int main(int argc, char **argv)
{
    int i = 0;                  // counter, 
    int amount = 1000000;               // size of testfield, 
    double testme[amount];              // array with test values,          
    struct timespec start, end, start_1, end_1; // special high precision time values, 
    double x1;                  // working value, 
    uint64_t diff, diff_1;              // 'big numbers' for high nanosecond values, 

// preparing testvalues for timing, 

    for( i = 0; i < amount; ++i )
    {
        testme[i] = rand() / pow( 2, 16 );
    }


// timing ' x / 2 ', 
    /* measure monotonic time */
    clock_gettime(CLOCK_MONOTONIC, &start);         /* mark start time */
    /* measure 'CPU_TIME' */
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_1);  /* mark start time */

    for( i = 0; i < amount; ++i )
    {
        x1 = testme[i] / 2;
//      std::cout << "' x / 2:     " << x1 << "\n"; // uncomment to see that real values are processed, careful with high 'amounts', 
    }
//  std::this_thread::sleep_for(1000ms);            // uncomment to see different 'busy' and 'total' timings, 
    clock_gettime(CLOCK_MONOTONIC, &end);           /* mark the end time */
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_1);    /* mark the end time */

    diff = BILLION * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec;
    diff_1 = BILLION * (end_1.tv_sec - start_1.tv_sec) + end_1.tv_nsec - start_1.tv_nsec;

    std::cout
        << "calculating " << amount << " times ' = x / 2 ' took         " 
        << diff << " nanoseconds "
        << " from which the CPU 'used' " << diff_1 << " nanoseconds \n";


// timing ' pow( 10, x ) ', 
    /* measure monotonic time */
    clock_gettime(CLOCK_MONOTONIC, &start);         /* mark start time */
    /* measure 'CPU_TIME' */
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_1);  /* mark start time */

    for( i = 0; i < amount; ++i )
    {
        x1 = pow( 10, testme[i] );
//      std::cout << "' x1 = pow( 10, testme[i] )' :     " << x1 << "\n";   // uncomment to see that real values are processed, careful with high 'amounts', 
    }
//  std::this_thread::sleep_for(1000ms);            // uncomment to see different 'busy' and 'total' timings, 
    clock_gettime(CLOCK_MONOTONIC, &end);           /* mark the end time */
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_1);    /* mark the end time */

    diff = BILLION * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec;
    diff_1 = BILLION * (end_1.tv_sec - start_1.tv_sec) + end_1.tv_nsec - start_1.tv_nsec;

    std::cout
        << "calculating " << amount << " times ' x1 = pow( 10, testme[i] ) ' took         " 
        << diff << " nanoseconds "
        << " from which the CPU 'used' " << diff_1 << " nanoseconds \n";

    exit(0);
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Asking how to port this to C is a separate topic which would probably better be left out from this particular question, and arguably not asked on this site. \$\endgroup\$
    – tripleee
    Oct 20, 2021 at 6:58

2 Answers 2

2
\$\begingroup\$

You are including several C headers that have C comments on the lines, and then several C++ headers. Don't use C headers. Sometimes, don't use the C library stuff in C++ (e.g. printf; you are also using cout! and this is a small piece of code! Is it just pasted up from fragments of C and C++ of different ages?)

See Cppreference on C compatibility headers: (emphasis mine)

For some of the C standard library headers of the form xxx.h, the C++ standard library both includes an identically-named header and another header of the form cxxx (all meaningful cxxx headers are listed above). The intended use of headers of form xxx.h is for interoperability only.

xxx.h headers are deprecated in C++98 and undeprecated in C++23. These headers are discouraged for pure C++ code, but not subject to future removal.

So,
#include <stdint.h> /* for uint64 definition */
is clearly pasted in from a C (not C++) program. You should instead write
#include <cstdint> // for uint64_t
though the use of this header is so targeted that the comment is unnecessary. I like these comments on grab-bag library headers that have tons of unrelated things in them.

So, before sending code out for review, review it yourself. Proof reading the whole file, preferably after a break, look for things that are not used, comments that don't match the code, extra spaces, leftover commented-out lines, inconsistent indentation, etc.

I appreciate (via comments on this thread) that you're not experienced enough to see the difference between different old languages/dialects/idioms, but in general that's something else to look for.

It's like pasting in a passage from Shakespeare and another from Grisham.


#define BILLION 1000000000L

NO.

Just don't use #define.

constexpr auto Billion = 1'000'000'000;

Though you probably don't even need this, since the chrono library includes SI prefix rational number stuff and directly knows about nanoseconds.

You may be interested in my article The Most Essential C++ Advice which covers this, and has pointers to other useful resources.


int localpid(void)

Strustrup calls that "an abomination". Empty parameter lists in C++ are (). (void) is for C and has no business being in a C++ implementation file.


struct timespec start, end, start_1, end_1;

In C++ you don't need to use struct in front of struct names. This is an indication that it was copy/pasted from C code.

You generally don't declare multiple variables in one declaration either, and note that these primitive C structures are not initialized. I don't see them used in the next few lines either. Don't declare variables all huddled together at the top; C doesn't even require that anymore! Declare variables where you are ready to give it a value.

You don't need to end main with a call to exit. Just return from it like any other function. You note that you are including a library header just to get exit to use, but you don't need to call it.

For main, you can omit the return completely and it returns 0. If you don't have anything useful to say, just leave it out.


int localpid(void) 
{
    static int a[9] = { 0 };
    return a[0];
}

What is this supposed to accomplish? How's it any different from just:

int localpid() { return 0; }

since the static value is never modified, and you don't use the other 8 values you made room for?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ thank you @JDługosz, looks like a good review. 'fragments ... ages?' - yes, as i didn't find something 'ready' I picked together snippets. see name in 'top_comment', where I can't even distinguish C and C++ I'm surprised myself to have managed something executable ;-) | comments - I like the '//' style better as it's easier to use for temporary code disabling, | 'define' - it's from a 'teaching' program | 'localpid' - as well, but i don't see it in use there | 'struct', 'variables', 'return' - will care for | 'declarations' in other projects I got bombarded! with warnings about the order, \$\endgroup\$ Oct 20, 2021 at 17:42
  • \$\begingroup\$ @user1018684 You might like to read my Most Essential C++ Advise article. I'll edit the Answer in light of your remarks. \$\endgroup\$
    – JDługosz
    Oct 21, 2021 at 13:48
  • \$\begingroup\$ thank you @JDługosz, 'reading' - did, sure about this: 'T foo (T x, T,y)' | 'declarations' - it didn't make it through to compilers, people or code i'd meet shortly | 'initializing' - YES | 'unsigned(f)' - 'long double(f)' -> error | are we really missing an 'upgrading' conversion e.g. 'float -> long double' keeping the 'corresponding decimal value' with the 'decimal precision' of the 'lower precise' representation rather than it's FP-conversion artifacts? :-( | rest - as of now above my scope ... | 'Grisham' - it was a quite quick! way for me to start and achieve sth i couldn't find 'ready'. \$\endgroup\$ Oct 22, 2021 at 12:19
1
\$\begingroup\$

Using gettimeofday may give you more precision than clock but is not likely to be any more accurate. When doing benchmarks, we are not so much concerned with how long a function takes in nanoseconds, but more with how long functionA takes compared to functionB.

Consider the following plain vanilla C code:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

int main()
{
    // working value, 
    double x1 = 0;                  

    // prepare some test data
    int amount = 100000;
    double testme[amount];
    for (int i = 0; i < amount; ++i)
        testme[i] = (double)rand();

    int repeapts = 1000;
    clock_t start;
    clock_t end;

    // first test
    start = clock();
    for (int r = 0; r < repeapts; r++)
    {
        for (int i = 0; i < amount; ++i)
        {
            x1 += testme[i] / 2;
        }
    }
    end = clock();
    printf("calculating %d times * %d repeats ' = x / 2 ' took %ld ticks.\n", amount, repeapts, end - start);

    // second test
    start = clock();
    for (int r = 0; r < repeapts; r++)
    {
        for (int i = 1; i < amount; ++i)
        {
            x1 += pow(10, testme[i]);
        }
    }
    end = clock();
    printf("calculating %d times * %d repeats ' x1 = pow( 10, testme[i] )' took %ld ticks.\n", amount, repeapts, end - start);


    printf("x1 = %f\n", x1);
}

You do not need to be concerned about how long a tick is, only that

x1 += testme[i] / 2

is roughly 20 times faster than

x1 += pow(10, testme[i]);

Also take note of the last line:

printf("x1 = %f\n", x1);

If we don't actually do anything with x1 most compilers are smart enough to skip over the loops and you won't measure any ticks at all!

\$\endgroup\$
3
  • \$\begingroup\$ thanks to @upkajdt! works out of the box, readable, and impressive insights: ~ g++ -O0 timing.c -o timing -lm ~./timing calculating 100000 times * 1000 repeats ' = x / 2 ' took 278604 ticks. x1 = 24993406.681235 calculating 100000 times * 1000 repeats ' x1 = pow( 10, testme[i] )' took 2160413 ticks. x1 = 390778681.759619 ~ g++ -Ofast -march=native timing.c -o timing -lm ./timing calculating 100000 times * 1000 repeats ' = x / 2 ' took 31046 ticks. x1 = 24993406.681235 calculating 100000 times * 1000 repeats ' x1 = pow( 10, testme[i] )' took 38313 ticks. x1 = 390778681.759360 \$\endgroup\$ Oct 20, 2021 at 22:02
  • \$\begingroup\$ @user1018684, Cool! Just be VERY careful about how you interpret the results. x1 = testme[i] / 2 might seem faster than x1 = testme[i] + 2 purely because of timing jitters when 90% of the time is actually spent accessing the array and have nothing to do with the calculation =) \$\endgroup\$
    – jdt
    Oct 20, 2021 at 22:34
  • \$\begingroup\$ 'Just be VERY careful about how you interpret ...' - YES SIR! - of course i'll do, it's just meant to have sth giving me some ... known inexact ... answers. E.g. if an optimization gives identical result to -O0 after 1000*100000 iterations it's likely not 'inexact', if timings vary in 'grains' at repeated calls there likely are conditions influencing it, if '__builtin_powi( x, y)' is faster than but identical results to 'POW(...)' it could be used ..., if -Ofast -march=native' is impressive fast but with small devias it's worth investigating, much better now 'than blind in the fog' :-) :-) \$\endgroup\$ Oct 22, 2021 at 12:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.