Skip to content

Refactor Remez exchange into structured Julia and reduce allocations - #667

Draft
thimotedupuch wants to merge 1 commit into
JuliaDSP:masterfrom
thimotedupuch:refactor/remez-structured-julia
Draft

thimotedupuch wants to merge 1 commit into
JuliaDSP:masterfrom
thimotedupuch:refactor/remez-structured-julia

Conversation

@thimotedupuch

@thimotedupuch thimotedupuch commented Sep 5, 2026 •

Copy link
Copy Markdown

Disclaimer : The following was written by AI agent (specifically GPT6-Astra)
I noticed a few months ago the legacy remez implementation and I thought a proper Julia rewrite would be interesting.

Thank you very much,
Thimoté D.



This draft proposes a structured Julia implementation of the Remez exchange step, with the aim of making the algorithm easier to follow while preserving both public remez APIs and improving runtime and allocations.

The exchange search uses loops and small helpers in place of labels and jumps. It retains the original search order, endpoint margin, tie handling, and stopping rules, including the warning and returned approximation when maxiter is reached. Grid construction fills its arrays directly, coefficient recovery reuses interpolation workspace, and the barycentric evaluator groups independent divisions while retaining accumulation order with fused operations. The existing attribution and license notice are retained; no package dependencies are added.

Validation performed locally on Julia 1.12.7:

  • The full test suite passes with --check-bounds=yes: 12,079 passing assertions and 7 existing broken tests, including 95 passing Remez assertions.
  • A separate differential script compares 600 seeded random designs against commit 7c798756cca39251da14bb5d38146feb2cdd1717. Coefficients match bit for bit in these tests, as do warning messages and exception types/messages.
  • Added package tests cover API equivalence, all four linear-phase symmetry types, functional responses and weights, grid endpoints, coefficients returned at the iteration limit, and an independent check of weighted equiripple behavior.

Across 18 benchmark cases covering both APIs, local measurements show approximately 9–31% less execution time and 24–27% fewer allocated bytes. These are measurements on one znver3 machine using Julia 1.12.7 and one Julia thread, not a claim about every platform. The scripts warm up both implementations and alternate their measurement order; compilation time and process resident memory are not measured. Commands, methodology, and the complete results are in benchmark/README.md.

I am opening this as a draft because Julia 1.10 and the other supported platforms still need validation. In particular, the fused operations deserve review across architectures. The exact comparisons above describe the tested environment and do not establish bitwise equivalence on all systems.

@codecov

codecov Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.31973% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.65%. Comparing base (7c79875) to head (0759d66).

Files with missing lines Patch % Lines
src/Filters/remez_fir.jl 99.31% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #667      +/-   ##
==========================================
+ Coverage   97.98%   98.65%   +0.66%     
==========================================
  Files          19       20       +1     
  Lines        3332     3335       +3     
==========================================
+ Hits         3265     3290      +25     
+ Misses         67       45      -22     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@wheeheee

wheeheee commented Sep 7, 2026

Copy link
Copy Markdown
Member

Hm, had always thought that chunk of code was a bit unsightly, but still, wary of changing it because I don't fully understand Remez, and it's pesky to verify the behaviour of the gotos are fully replicated when one changes the code. What does this do to code size (does LLVM IR look nicer? shorter?)? Are there any numerical gotchas?

@thimotedupuch

Copy link
Copy Markdown
Author

Thanks.
About the numerical behavior, I believe we can be very confident that the refactor is harmless, at least on my laptop's CPU architecture. The differential test ran 600 seeded random designs against the old revision and got bit-for-bit identical coefficients, warnings, and exceptions.

I also see that it passes all platforms on the CI.


I am not a compiler engineer myself and I don't know much about LLVM IR.

So I asked the agent about it, that answered :

"For a concrete 31-tap, two-band design on Julia 1.12.6, the legacy implementation compiles the whole algorithm into one very large method: about 6,456 lines / 451 kB of textual LLVM IR (8,388 lines / 292 kB of textual assembly). The refactor’s top-level algorithm method is about 208 lines / 15 kB of LLVM IR (270 lines / 7.6 kB assembly)."

If I understand correctly, the new algorithm generates many small compiled helpers instead of a large one. (I don't know if it's trivial to get the sum of sizes of all helpers)

I don't really have the technical knowledge to give you better information.
I also don't think there are many programmers (alive, or at least not retired) that know the Remez exchange algorithm very well. It's possible that every open source implementation is based on the original one made in fortran.

@wheeheee

wheeheee commented Sep 9, 2026

Copy link
Copy Markdown
Member

Still, I think it is extremely non-obvious that this rewrite is equivalent to the previous, and I wouldn't like to silently corrupt downstream consumers. Tests are good but not enough, there could always be an edge case that your rewrite handles incorrectly that they simply don't hit. Could you show that the state machines are equivalent?

@thimotedupuch

Copy link
Copy Markdown
Author

Yes that is true. I don't see a trivial or non convoluted way to prove that the rewrite is precisely equivalent to the goto state machine.

Do you know why julia added goto and label to the language, knowing this would result in very unreadable code ?

I understand the importance of having correct algorithms for downstream users. You can do whatever you want with this PR. This can be a reference of what an implementation of the algorithm would look like without these macros.

Thanks, have a nice day.

@wheeheee

Copy link
Copy Markdown
Member

If this PR was squashed from a series of PRs that gradually transformed the original code, one might be able to verify the final output by going through them.

Do you know why julia added goto and label to the language, knowing this would result in very unreadable code ?

sometimes you just need gotos to write code. Plus if you look at the IR of a function with a loop / conditionals from code_lowered, you see gotos anyway since that's how the machines work.

IDK, since you're using GPT6-Astra, you might as well pull the jackpot machine's lever and maybe it can generate two images of the old and new state machines (or CFGs à la Godbolt's); easier to see that way.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants