Benchmark trigonometry against precision, and stop discarding the allocations - #850
Merged
Conversation
…ocations Both halves of what #167 asked for, after Happypig375 said to add the benchmarks and to profile memory as well. **Trigonometry against precision.** CommonFunctionsInterVersion had no trigonometric case at any precision. EvalTrig and EvalTrigPrecise evaluate sin(1) + cos(1) + tan(1) at the default hundred digits and at five hundred. Measured while writing them: 3.8 ms, 10.2 ms at two hundred, 25.4 ms at five hundred and 146 ms at a thousand, so the issue's premise holds and now has a row that will show it moving. Both build their nodes on every call rather than evaluating a shared instance. That is not a stylistic choice: an Entity caches its own Evaled, so the obvious form of this benchmark measures a dictionary lookup. A hundred thousand evaluations of one instance take a millisecond in total; a thousand evaluations of fresh nodes take a second. Which is also what EvalEasy has been measuring since that cache landed, and why it reads two nanoseconds. Left as it is so the column history stays comparable, with the trap written down in both the benchmark and the document. **Allocations.** The class has carried [MemoryDiagnoser] all along, so the figure was collected on every run and then dropped, because Program.cs asked for Mean, Error and StdDev and nothing else. Now it asks for Allocated too. The column header is exactly "Allocated" -- verified by running BenchmarkDotNet and printing the headers, since TableToString skips a header it cannot find without complaining. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rafael-SOWNet
added a commit
that referenced
this pull request
Aug 9, 2026
The half of #167 the benchmark itself could not supply. #850 added EvalTrig and EvalTrigPrecise and stopped the allocation figure being discarded, and the Kernel Benchmark has now run on master with both, so the numbers come from a GitHub runner rather than a contributor's machine. Trigonometry at five hundred digits is 43.3 ms against 1.3 ms at the default hundred. Allocation gets a table of its own rather than doubling the width of the main one. EvalEasy allocating nothing is the proof of the caching note above it rather than a further argument for it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both halves of #167, after @Happypig375 said "add as many benchmarks as needed, maybe profile memory use as well".
Trigonometry against precision
CommonFunctionsInterVersionhad no trigonometric case at all, at any precision — which is what the issue asked for.EvalTrigandEvalTrigPreciseevaluatesin(1) + cos(1) + tan(1)at the default hundred digits and at five hundred.Measured on one machine while writing them, so read the shape rather than the numbers:
The issue's premise — "arbitrary precision of trig functions cost a lot in performance" — holds, and now has a row that will show it moving.
Both build their nodes on every call, and that is not cosmetic
An
Entitycaches its ownEvaled, so the obvious form of this benchmark — one shared instance, evaluated repeatedly — measures a dictionary lookup instead of any arithmetic:Building the nodes costs microseconds against milliseconds of arithmetic, so including it does not move the figure; evaluating a cached instance would move it to zero.
Which is what
EvalEasyhas been measuringThat is the explanation for a number already in the table.
EvalEasyevaluates one static instance, and its row falls to 2 ns in the latest column:That is not the arithmetic getting faster — it is the row silently changing meaning when the evaluation cache landed. Left exactly as it is, because rewriting it would break the comparison the file exists to provide, but the trap is now written down in the benchmark and in the document.
Allocations were being collected and thrown away
The class has carried
[MemoryDiagnoser]all along.Program.csasked for"Mean", "Error", "StdDev"and nothing else, so the figure was measured on every run and then dropped on the floor. It now asks for"Allocated"as well — which is whatTransformationLayeralready does, with a comment saying the regressions it exists to catch are invisible in the timings.Verified rather than assumed, because
TableToStringfilters columns by header and silently skips one it cannot find — so a wrong string here would have been a no-op that looked like a fix. Ran BenchmarkDotNet with[MemoryDiagnoser]and printed the headers:What this does not do
It does not add a column to
version_performance_control.md. Columns there are whole-suite runs produced by the Kernel Benchmark workflow, and this PR's rows will appear in the next one — the document says so rather than leaving a reader to wonder why two rows are blank.🤖 Generated with Claude Code