Skip to content

feat: add C benchmark harness to @stdlib/bench - #15400

Open
0PrashantYadav0 wants to merge 8 commits into
stdlib-js:developfrom
0PrashantYadav0:bench-harness
Open

0PrashantYadav0 wants to merge 8 commits into
stdlib-js:developfrom
0PrashantYadav0:bench-harness

Conversation

@0PrashantYadav0

@0PrashantYadav0 0PrashantYadav0 commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Resolves #13480, stdlib-js/metr-issue-tracker#1378.

Description

What is the purpose of this pull request?

This pull request:

  • adds a header-only C benchmark harness (include/stdlib/bench.h) to @stdlib/bench, so C benchmark files no longer hand-roll TAP output, timing, PRNG helpers, and the length/iteration loop. The header depends only on the C standard library and emits the same TAP 13 output as the existing hand-written benchmarks.
  • adds a manifest.json (header-only, no dependencies), a C example, and a C APIs section to the @stdlib/bench README documenting every macro and helper.
  • updates tools/scripts/compile_c_benchmark to resolve the enclosing package name and pass it to make as BENCHMARK_NAME (overridable via the environment). The native C benchmark Makefile snippet turns a non-empty BENCHMARK_NAME into -DSTDLIB_BENCH_NAME="<package name>", so C benchmark names match the package-qualified names already used by JavaScript benchmarks (which read require( './../package.json' ).name). This addresses note 7 of the RFC: across files named benchmark.c there are currently 1,327 #define NAME lines but only 1,033 distinct values.
  • migrates blas/ext/base/zindex-of-truthy's C benchmark onto the harness as a pilot (200 → 105 lines). Its output name changes from # c::zindex_of_truthy:len=10 to # c::@stdlib/blas/ext/base/zindex-of-truthy:len=10.

Existing C benchmarks are untouched: their Makefiles ignore the extra BENCHMARK_NAME variable, and in Makefiles which include the new block an empty BENCHMARK_NAME yields no -D flag.

Example

#include "stdlib/blas/ext/base/zindex_of_truthy.h"
#include "stdlib/bench.h"

#ifndef STDLIB_BENCH_NAME
#define STDLIB_BENCH_NAME "zindex_of_truthy"
#endif

STDLIB_BENCHMARK( benchmark1 ) {
    int idx = -1;

    STDLIB_BENCHMARK_MALLOC_ARRAY_FLOAT64( x, len*2 );
    STDLIB_BENCHMARK_FILL_ARRAY( x, len*2, 0.0 );
    x[ (len*2)-1 ] = 1.0;

    STDLIB_BENCHMARK_LOOP_PREAMBLE {
        x[ (len*2)-4 ] = (double)( i % 4 );
        idx = stdlib_strided_zindex_of_truthy( len, (const stdlib_complex128_t *)x, 1 );
        if ( idx < 0 ) {
            printf( "unexpected result\n" );
            break;
        }
    }
    STDLIB_BENCHMARK_LOOP_EPILOGUE;
    STDLIB_BENCHMARK_FREE( x );
    STDLIB_BENCHMARK_EPILOGUE;
}

STDLIB_BENCH {
    STDLIB_BENCH_LENGTH_PREAMBLE( 3, 10000000, 1, 6 ) {
        STDLIB_BENCH_PRINT_NAME_F( "len=%d", len );
        STDLIB_RUN_BENCHMARK( benchmark1 );
    }
}

Differences from the RFC sketch

  • NAME is STDLIB_BENCH_NAME, so the build can define it without colliding with the unconditional #define NAME in existing benchmark files.
  • STDLIB_BENCH_EPILOGUE is not needed: STDLIB_BENCH generates main() around a wrapper function, so the TAP summary is printed automatically.
  • STDLIB_BENCHMARK_FILL_STRIDED_ARRAY_FLOAT64 is STDLIB_BENCHMARK_FILL_ARRAY (the sketch's signature had no stride, and assignment is type-agnostic).
  • Added STDLIB_BENCH_PREAMBLE( repeats, iterations ) for scalar benchmarks, STDLIB_BENCH_PRINT_NAME() / STDLIB_BENCH_PRINT_NAME_F( fmt, ... ) for the # c:: line (note 6 of the RFC), and STDLIB_BENCHMARK_UNUSED( x ) so scalar benchmarks compile cleanly under -Wextra.
  • The header is libc-only (note 8 of the RFC): a dependent's compile line gains a single -I and nothing is linked or compiled.

Verification

  • make examples-c EXAMPLES_FILTER=".*/@stdlib/bench/examples/c/.*" — passes.
  • make benchmark-c BENCHMARKS_FILTER=".*/blas/ext/base/zindex-of-truthy/benchmark/c/.*" — 36 benchmarks, 1..36, # ok, no compiler warnings (also clean under -Wextra).
  • BENCHMARK_NAME=custom make benchmark-c ... — override respected; compiling without the define falls back to the in-file name.
  • make benchmark-c on an unmigrated benchmark (math/base/special/abs) — unchanged behavior.
  • make lint-markdown-files FILES="lib/node_modules/@stdlib/bench/README.md" — passes.

Related Issues

Does this pull request have any related issues?

This pull request has the following related issues:

Questions

Any questions for reviewers of this pull request?

  • STDLIB_RUN_BENCHMARK keeps the name from the RFC sketch, which does not follow the STDLIB_BENCH_ / STDLIB_BENCHMARK_ prefix used by every other macro. It is documented as the one exception; happy to rename it to STDLIB_BENCHMARK_RUN if preferred.
  • The scaffold snippet tools/snippets/benchmark/c/benchmark.c is intentionally left unchanged so that adopting the harness for newly scaffolded packages can be decided separately.

Other

Any other information relevant to this pull request? This may include screenshots, references, and/or implementation notes.

  • The pre-commit shell lint (shellcheck without a severity filter) reports SC2153/SC2181 on tools/scripts/compile_c_benchmark. Nineteen of the twenty-one findings pre-exist this change; the two new ones follow the file's existing pattern (name="${NAME}" and if [[ "$?" -ne 0 ]]).
  • Mass migration of the remaining C benchmarks is left for follow-up once the harness API has settled.

Checklist

Please ensure the following tasks are completed before submitting this pull request.

AI Assistance

When authoring the changes proposed in this PR, did you use any kind of AI assistance?

  • Yes
  • No

If you answered "yes" above, how did you use AI assistance?

  • Code generation (e.g., when writing an implementation or fixing a bug)
  • Test/benchmark generation
  • Documentation (including examples)
  • Research and understanding

Disclosure

If you answered "yes" to using AI assistance, please provide a short disclosure indicating how you used AI assistance.

AI assistant reviewed the design decisions and the resulting changes, and the verification steps listed above were run locally.


@stdlib-js/reviewers

Signed-off-by: 0PrashantYadav0 <prashantyadav09783@gmail.com>
@0PrashantYadav0
0PrashantYadav0 requested a review from a team September 21, 2026 16:47
@stdlib-bot stdlib-bot added the Needs Review A pull request which needs code review. label Sep 21, 2026
@0PrashantYadav0 0PrashantYadav0 added Benchmarks Pull requests adding or improving benchmarks for measuring performance. C Issue involves or relates to C. Feature Issue or pull request for adding a new feature. labels Sep 21, 2026
Signed-off-by: 0PrashantYadav0 <prashantyadav09783@gmail.com>

---
type: pre_commit_static_analysis_report
description: Results of running static analysis checks when committing changes.
report:
  - task: lint_filenames
    status: passed
  - task: lint_editorconfig
    status: passed
  - task: lint_markdown_pkg_readmes
    status: na
  - task: lint_markdown_docs
    status: na
  - task: lint_markdown
    status: na
  - task: lint_package_json
    status: na
  - task: lint_repl_help
    status: na
  - task: lint_javascript_src
    status: na
  - task: lint_javascript_cli
    status: na
  - task: lint_javascript_examples
    status: na
  - task: lint_javascript_tests
    status: na
  - task: lint_javascript_benchmarks
    status: na
  - task: lint_python
    status: na
  - task: lint_r
    status: na
  - task: lint_c_src
    status: na
  - task: lint_c_examples
    status: na
  - task: lint_c_benchmarks
    status: na
  - task: lint_c_tests_fixtures
    status: na
  - task: lint_shell
    status: passed
  - task: lint_typescript_declarations
    status: passed
  - task: lint_typescript_tests
    status: na
  - task: lint_license_headers
    status: passed
---
Comment thread lib/node_modules/@stdlib/bench/include/stdlib/bench.h Outdated
Comment thread lib/node_modules/@stdlib/bench/include/stdlib/bench.h
@kgryte

kgryte commented Sep 22, 2026

Copy link
Copy Markdown
Member

A few comments:

  1. s/STDLIB_BENCH_NAME/BENCHMARK_NAME/
  2. We want both STDLIB_BENCHMARK_FILL_STRIDED_ARRAY and STDLIB_BENCHMARK_FILL_ARRAY, as the former is a generalization of the latter and there are instances where we want to fill elements according to a stride.
  3. s/STDLIB_BENCH_PRINT_NAME_F/STDLIB_BENCH_PRINT_NAME_FORMAT/

@kgryte kgryte added Needs Changes Pull request which needs changes before being merged. and removed Needs Review A pull request which needs code review. labels Sep 22, 2026
Signed-off-by: 0PrashantYadav0 <prashantyadav09783@gmail.com>

---
type: pre_commit_static_analysis_report
description: Results of running static analysis checks when committing changes.
report:
  - task: lint_filenames
    status: passed
  - task: lint_editorconfig
    status: passed
  - task: lint_markdown_pkg_readmes
    status: passed
  - task: lint_markdown_docs
    status: na
  - task: lint_markdown
    status: na
  - task: lint_package_json
    status: na
  - task: lint_repl_help
    status: na
  - task: lint_javascript_src
    status: na
  - task: lint_javascript_cli
    status: na
  - task: lint_javascript_examples
    status: na
  - task: lint_javascript_tests
    status: na
  - task: lint_javascript_benchmarks
    status: na
  - task: lint_python
    status: na
  - task: lint_r
    status: na
  - task: lint_c_src
    status: na
  - task: lint_c_examples
    status: passed
  - task: lint_c_benchmarks
    status: passed
  - task: lint_c_tests_fixtures
    status: na
  - task: lint_shell
    status: na
  - task: lint_typescript_declarations
    status: passed
  - task: lint_typescript_tests
    status: na
  - task: lint_license_headers
    status: passed
---
@0PrashantYadav0

Copy link
Copy Markdown
Member Author

@kgryte All three done in d97a61e:

  1. STDLIB_BENCH_NAME -> BENCHMARK_NAME throughout (header, README, example, the migrated benchmark, and the Makefile now passes -DBENCHMARK_NAME='"<pkg>"').
  2. Added STDLIB_BENCHMARK_FILL_STRIDED_ARRAY( x, n, stride, value ); STDLIB_BENCHMARK_FILL_ARRAY( x, n, value ) is now the unit-stride wrapper. A negative stride starts at (1-n)*stride, following the stride2offset convention. I kept both untyped, since the assignment is type-agnostic; if you would like _FLOAT64 / _FLOAT32 aliases as in the RFC sketch, those are easy to add.
  3. STDLIB_BENCH_PRINT_NAME_F -> STDLIB_BENCH_PRINT_NAME_FORMAT.

Also, per your inline comment, scalar and length-based benchmarks now use separate macros (STDLIB_BENCHMARK / STDLIB_LENGTH_BENCHMARK and STDLIB_RUN_BENCHMARK / STDLIB_RUN_LENGTH_BENCHMARK), so len is only passed where it is used.

@stdlib-bot stdlib-bot added the Needs Review A pull request which needs code review. label Sep 22, 2026
@anandkaranubc
anandkaranubc self-requested a review September 22, 2026 16:58
@0PrashantYadav0

Copy link
Copy Markdown
Member Author

@anandkaranubc as discussed in office hours, blas/ext/base/zindex-of-truthy will not be merged in this PR. I will revert them but I want your review on this as well. So for now I am not removing those changes. Looking forward for your review. Thanks!

@headlessNode
headlessNode self-requested a review September 23, 2026 16:16
@0PrashantYadav0 0PrashantYadav0 removed the Needs Changes Pull request which needs changes before being merged. label Sep 24, 2026
anandkaranubc and others added 2 commits September 26, 2026 17:57
---
type: pre_commit_static_analysis_report
description: Results of running static analysis checks when committing changes.
report:
  - task: lint_filenames
    status: passed
  - task: lint_editorconfig
    status: passed
  - task: lint_markdown_pkg_readmes
    status: na
  - task: lint_markdown_docs
    status: na
  - task: lint_markdown
    status: na
  - task: lint_package_json
    status: na
  - task: lint_repl_help
    status: na
  - task: lint_javascript_src
    status: na
  - task: lint_javascript_cli
    status: na
  - task: lint_javascript_examples
    status: na
  - task: lint_javascript_tests
    status: na
  - task: lint_javascript_benchmarks
    status: na
  - task: lint_python
    status: na
  - task: lint_r
    status: na
  - task: lint_c_src
    status: na
  - task: lint_c_examples
    status: na
  - task: lint_c_benchmarks
    status: passed
  - task: lint_c_tests_fixtures
    status: na
  - task: lint_shell
    status: na
  - task: lint_typescript_declarations
    status: passed
  - task: lint_typescript_tests
    status: na
  - task: lint_license_headers
    status: passed
---
@anandkaranubc

Copy link
Copy Markdown
Contributor

A side note, for single-precision benchmarks, authors would be casting the results:

float r = (float)stdlib_bench_random_uniform( 1.0, 100.0 );

There are 26 f32 benchmarks in math/base/special that currently use a float-typed random_uniform.

Alternatively, a stdlib_bench_random_uniform_f32 helper could be added (matching the existing stdlib_bench_rand_float32), but that could also be a follow-up.

@anandkaranubc

Copy link
Copy Markdown
Contributor

stdlib_bench_rand_float32 documents its range as [0,1), but when RAND_MAX is 2147483647, (float)RAND_MAX rounds up to 2147483648.0f, which equals (float)RAND_MAX + 1.0f, so the division can return exactly 1.0f. The existing hand-written f32 benchmarks have the same formula, so this isn't a regression, but worth noting since the doc says the upper bound is exclusive.

I think the simplest fix for this would be:

static inline float stdlib_bench_rand_float32( void ) {
	int r = rand();
	return (float)( (double)r / ( (double)RAND_MAX + 1.0 ) );
}

@anandkaranubc anandkaranubc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the comments above, the PR looks solid! Thanks @0PrashantYadav0 for working on it 🚀

@anandkaranubc anandkaranubc added the METR Pull request associated with the METR project. label Sep 27, 2026
@0PrashantYadav0

Copy link
Copy Markdown
Member Author

Thanks @anandkaranubc for review.

@0PrashantYadav0

Copy link
Copy Markdown
Member Author

@kgryte Will you please do a final review on this 🙏🏼

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Benchmarks Pull requests adding or improving benchmarks for measuring performance. C Issue involves or relates to C. Feature Issue or pull request for adding a new feature. METR Pull request associated with the METR project. Needs Review A pull request which needs code review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RFC]: add a C benchmark harness

5 participants