Giter Site home page Giter Site logo

ubench.h's Introduction

⏱️ ubench.h

Actions Status Build status Sponsor

A simple one header solution to benchmarking for C/C++.

Usage

Just #include "ubench.h" in your code!

The current supported platforms are Linux, macOS and Windows.

The current supported compilers are gcc, clang, MSVC's cl.exe, and clang-cl.exe.

It also works with tcc but with a caveat: the latest release of the tcc compiler (version 0.9.27) lacks a feature for UBENCH to work. Make sure to use a tcc that is patched with the constructor attribute extension. Recent Ubuntu and Debian Linux distros ship tcc with that patch already included. If you compile tcc yourself, use the trunk version and it will work as expected.

Command Line Options

ubench.h supports some command line options:

  • --help to output the help message
  • --filter=<filter> will filter the benchmarks to run (useful for re-running one particular offending benchmark).
  • --list-benchmarks will list benchmark names, one per line. Output names can be passed to --filter.
  • --output=<output>will output a CSV file of the results.
  • --confidence=<confidence> will change the confidence cut-off for a failed test.

Design

ubench.h is a single header library to enable all the fun of benchmarking in C and C++. The library has been designed to provide an output similar to Google's googletest framework:

[==========] Running 1 benchmarks.
[ RUN      ] foo.bar
[       OK ] foo.bar (mean 536.235us, confidence interval +- 1.457878%)
[==========] 1 benchmarks ran.
[  PASSED  ] 1 benchmarks

Benchmarks can fail if their confidence exceeds a certain threshold. The default threshold is 2.5% - anything above this is usually a sign that there is too much variance to trust the benchmark result. The confidence interval in use is the 99% confidence interval. This means that 99% of the samples will occur within the percentage range of the mean result. So a mean of 100us with a confidence interval of 2% means that 99% of the samples will occur in the range [98us..102us]. This is a good measure of a consistent result - it eliminates the 1% of samples that could easily skew the standard deviation, while giving us a good confidence on the data.

You can change the default threshold of 2.5% by specifying the --confidence=<confidence> option, but it is recommended that you keep a value that is sufficiently low to enable reproducible results.

The number of iterations ran for each test is proportionate to the time taken to run each benchmark. The framework aims to run a single set of iterations of the benchmark in 100ms - so if you benchmark takes 1ms to run, it'll start with 100 samples. The number of samples will increase on a failed run (a run of the benchmark that does not meet the confidence interval cutoff) up to a maximum of 500 samples. At least 10 samples are always done even for longer running tests to ensure some meaningful confidence interval can be computed.

UBENCH_MAIN

In one C or C++ file, you must call the macro UBENCH_MAIN:

UBENCH_MAIN();

This will call into ubench.h, instantiate all the benchmarks and run the benchmark framework.

Alternatively, if you want to write your own main and call into ubench.h, you can instead, in one C or C++ file call:

UBENCH_STATE();

And then when you are ready to call into the ubench.h framework do:

int main(int argc, const char *const argv[]) {
  // do your own thing
  return ubench_main(argc, argv);
}

Define a Benchmark

To define a benchmark to run, you can do the following;

#include "ubench.h"

UBENCH(foo, bar) {
  usleep(100 * 1000);
}

The UBENCH macro takes two parameters - the first being the set that the benchmark belongs to, the second being the name of the benchmark. This allows benchmarks to be grouped for convenience.

Define a Benchmark with setup

In some cases it might be beneficial to setup your benchmark state locally in your benchmark. This can be done like so:

#include "ubench.h"

UBENCH_EX(foo, short_string) {
  const char* short_string = alloc_test_string(16);

  UBENCH_DO_BENCHMARK() {
    to_test(short_string);
  }

  free(short_string);
}

UBENCH_EX(foo, long_string) {
  const char* long_string = alloc_test_string(16 * 1024);

  UBENCH_DO_BENCHMARK() {
    to_test(long_string);
  }

  free(long_string);
}

The benchmark will keep running and measuring within the {} after UBENCH_DO_BENCHMARK() so all code before and after that scope is only executed once. This might be the right thing to do in cases where you only want the setup in one benchmark. If you want some setup for en entire set a 'fixture' might be a better way to go, maybe you need to do some system-setup for each benchmark in a set.

Define a Fixtured Benchmark

Fixtures are a way to have some state that is initialized once and then used throughout a benchmark. They let you take the cost of setting up the benchmark out of the cost of testing whatever function you want to use it with:

// Declare a struct that holds the state for your benchmark.
struct foo {
  char *data;
};

// Use UBENCH_F_SETUP to initialize your struct (called ubench_fixture in here).
UBENCH_F_SETUP(foo) {
  const int size = 128 * 1024 * 1024;
  ubench_fixture->data = (char *)malloc(size);
  memset(ubench_fixture->data, ' ', size - 2);
  ubench_fixture->data[size - 1] = '\0';
  ubench_fixture->data[size / 2] = 'f';
}

// Use UBENCH_F_TEARDOWM to destroy your struct when complete.
UBENCH_F_TEARDOWN(foo) { free(ubench_fixture->data); }

// Define a benchmark that uses your fixture.
UBENCH_F(foo, strchr) {
  UBENCH_DO_NOTHING(strchr(ubench_fixture->data, 'f'));
}

// You can define as many benchmarks that use the same fixture.
UBENCH_F(foo, strrchr) {
  UBENCH_DO_NOTHING(strrchr(ubench_fixture->data, 'f'));
}

Note that fixtures are not guaranteed to be constructed only once - but they are guaranteed to not impede on the collection of accurate metrics for the running of your benchmark.

It is also possible to use a fixture in combination with a benchmark with a setup as follows:

UBENCH_EX_F(my_sys, short_string) {
  const char* short_string = alloc_test_string(16);

  UBENCH_DO_BENCHMARK() {
    my_sys_work(ubench_fixture->sys, short_string));
  }

  free(short_string);
}

UBENCH_EX_F(my_sys, long_string) {
  const char* long_string = alloc_test_string(16 * 1024);

  UBENCH_DO_BENCHMARK() {
    my_sys_work(ubench_fixture->sys, long_string));
  }

  free(long_string);
}

Testing Macros

We currently provide the following macros to be used within UBENCHs:

UBENCH_DO_NOTHING(x)

The helper macro UBENCH_DO_NOTHING takes a single pointer argument. It's purpose is to ensure that the compiler safetly does nothing with the pointer - meaning that the compiler cannot optimize the data away. This is incredibly useful for benchmarks because they generally want to run some code for timing and not have the compiler optimize the code away.

License

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.

In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to http://unlicense.org/

ubench.h's People

Contributors

aganm avatar sheredom avatar slowriot avatar wc-duck avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ubench.h's Issues

godbolt build requires -lm

https://godbolt.org/z/T43nexEdr

Without -lm , ld will not link and it will complain about not finding sqrt ...

I seem to remember I solved this one a few months ago?

ps: If someone wishes me to be exact: using -std=c17 -pedantic, UBENCH will not compile on Godbolt.

Issue is POSIX time "shenanigans", and asm becoming an "undeclared identifier"; all a lot of "fun".

-std=gnu17 or lower makes UBENCH compile.

Thus it is apparently enough to use: -lm, for UBENCH to compile, as -std=gnu17 is the current default for GCC (and clang).

Funny results on huge data sets

Code is available online https://godbolt.org/z/cYnaoxM58 . Admittedly with much smaller matrix sizes. My code has design issues and is needlessly complex, but correct and works ok for smaller matrixes.

If one takes that offline and declares large matrix sizes, funny things will happen:

image

It seems that is an overflow issue in UBENCH. One quick-and-dirty approach might be to report negative timings as zeroes? longjump out of the ubench_main? Will look into it.

Incorrect measurements using Windows + mingwx86_64 with GNU/GCC compiler

Hello,

First of all I would like to thank you for sharing single header benchmark library for C, unfortunately there is very little or no benchmark tools like that. I do have one problem though. On Windows with GCC the measurements are incorrect, yet while using Linux and GNU compiler everything works like a charm.

Here is the sample output, please notice incorrect mean and confidence interval

Screenshot_1

I was able to fix it adding the correct define if defined(__linux__) || defined(__GNUC__) this would ensure that time.h is included as well as UBENCH_USE_CLOCKGETTIME gets defined to use clock_gettime.

image

And the output looks more or less correct except it breaks DO_NOTHING call
image

If you would like to reproduce the issue let me know, I may attach a sample repo. The only requirement would be to have mingw installed on Windows machine and CMake 3.0+

Thanks!

Is there an option to remove the output of the code being benchmarked

Thanks for your support earlier. When I ran the benchmark on the functions:

UBENCH(func1,using_cout) {
 cout<<"Hello World"<<endl;
 }

 UBENCH(func2,using_printf) {
 printf("Hello World\n");
 }

I get a lot of output on the screen due to several runs of the functions. As a result, the result of the first benchmark(func1) was hidden in between a lot of text. Is there an option to remove all the output of the code being benchmarked so that I see only the benchmark results? Or maybe get the summarised results of all the benchmarks in the end?
Also is there a way to control the number of runs/ tweak the allowed standard deviation?

Add Visual Studio / clang-cl testing

I am using clang-cl.exe on Windows. (that is clang 10)

image

AFAIK __asm is MSVC and asm is GCC .. also with different syntax?

NOTE: I have not tried -std=gnu99 (or whatever it is called) and the rest

How to improve confidence or reduce variance

Hello there!

I've been having a little play around with ubench.h but have run into the problem where my benchmarks exceed the default confidence threshold by some margin (they're usually between 20% and 30%).

Is there any way to improve this without having to just said a high confidence threshold? For example, say I wanted to profile something like push_back in a custom vector with the std::vector implementation. Would increasing the number of iterations help (and I imagine calling reserve first to reduce any spikes). Or should a single call suffice and ubench.h internally figures out how many iterations are needed?

Thanks very much for your time

Tom

UBENCH_F -- just cant make it work?

The fixture mechanism just does not work in this file ...

UBENCH_F goes into endless recursion or whatever similar to that, UBENCH_F_SETUP does not get called? UBENCH in the same file works.

There is a little Win framework in that project, that catches everything and generates a minidump from where one can pinpoint the problem. And the problem is stack exhaustion upon entering the UBENCH_F

that is VS Code build using CL

How do I use this to benchmark my functions?

I have got 3 files: func.hpp, func.cpp and main.cpp. func.hpp declares 2 functions, func1() and func2(). The implementation is in func.cpp. I want to benchmark these 2 functions. How shall I do it?
I tried the below, but I'm getting various errors:

func.hpp

#ifndef __FUNC_HPP__
#define __FUNC_HPP__

void func1();
void func2();

#endif

func.cpp

#include "func.hpp"
#include "ubench.h"
#include <iostream>

using namespace std;

void func1() {
  UBENCH(func1,using_cout) {
  cout<<"Hello World"<<endl;
  }
}

void func2() {
  UBENCH(func2,using_printf) {
  printf("Hello World\n");
  }
}

main.cpp

#include "ubench.h"
#include "func.hpp"

UBENCH_STATE();

int main(int argc, char** argv) {
  return ubench_main(argc, argv);
}

MSVC color output bug ?

when I using CLion as IDE,with MSVC 2019 as compiler,just compile&run below code

#include <ubench.h>
#include <windows.h>

UBENCH(foo, bar) {
    Sleep(1000);
}

UBENCH_MAIN();

get some garbled out put

image

but linux gcc/clang work good

provide private sqrt() or mandate -lm ?

https://godbolt.org/z/EfEvq6nh3

Please see that Godbolt.

For well-known reasons linker complains and compilation is not done
linker complains sqrt() can not be found, thus '-lm' is required.

yes I know this is C only a Linux issue. I added some sqrt() implementation and that code links ok, with no -lm linker requirement "drama".

This seems like an easy way out ...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.