|
35 | 35 | ;; - **Apache Commons Math** - Mature library with general mathematical transforms |
36 | 36 | ;; - **JDSP** - Digital signal processing library (uses Apache Commons Math internally) |
37 | 37 | ;; - **JTransforms** - First multithreaded, pure-Java FFT library |
38 | | -;; - **fastmath** - Clojure math library (wraps JTransforms with idiomatic API) |
| 38 | +;; - **Fastmath** - Clojure math library (wraps JTransforms with idiomatic API) |
39 | 39 | ;; |
40 | 40 | ;; We'll compute the FFT of the same test signal using each library, measure performance, and discuss their trade-offs. |
41 | 41 |
|
|
71 | 71 | ;; - Supports **1D, 2D, and 3D** transforms (FFT, [DCT](https://en.wikipedia.org/wiki/Discrete_cosine_transform), [DST](https://en.wikipedia.org/wiki/Discrete_sine_transform), [DHT](https://en.wikipedia.org/wiki/Discrete_Hartley_transform)) |
72 | 72 | ;; - [In-place mutations](https://en.wikipedia.org/wiki/In-place_algorithm) (efficient but not functional) |
73 | 73 | ;; - Mixed-radix support: works with arbitrary sizes (not just power-of-2) |
74 | | -;; - Used internally by fastmath and dtype-next |
| 74 | +;; - Used internally by Fastmath and dtype-next |
75 | 75 |
|
76 | | -;; ### fastmath |
| 76 | +;; ### Fastmath |
77 | 77 | ;; |
78 | 78 | ;; [fastmath](https://github.com/generateme/fastmath) (version 3.x) by [Tomasz Sulej](https://github.com/genmeblog) is a Clojure library for fast primitive-based mathematics. Its [`fastmath.transform`](https://generateme.github.io/fastmath/fastmath.transform.html) namespace wraps JTransforms with an idiomatic Clojure API. |
79 | 79 | ;; |
|
247 | 247 | "FFT Spectrum (JTransforms)" |
248 | 248 | "purple") |
249 | 249 |
|
250 | | -;; ## FFT Implementation #4: fastmath |
| 250 | +;; ## FFT Implementation #4: Fastmath |
251 | 251 |
|
252 | | -;; fastmath provides the most Clojure-idiomatic API. Create a transformer, then use `forward-1d`. |
| 252 | +;; Fastmath provides the most Clojure-idiomatic API. Create a transformer, then use `forward-1d`. |
253 | 253 |
|
254 | 254 | (defn fft-fastmath |
255 | 255 | "Compute FFT using fastmath." |
|
260 | 260 | (def fastmath-result |
261 | 261 | (time (fft-fastmath signal))) |
262 | 262 |
|
263 | | -;; Extract magnitudes (fastmath uses JTransforms format internally): |
| 263 | +;; Extract magnitudes (Fastmath uses JTransforms format internally): |
264 | 264 |
|
265 | 265 | (defn fastmath-magnitude |
266 | 266 | "Compute magnitudes from fastmath FFT output." |
|
278 | 278 | ; Visualize |
279 | 279 | (plot-fft-spectrum |
280 | 280 | fastmath-magnitudes |
281 | | - "FFT Spectrum (fastmath)" |
| 281 | + "FFT Spectrum (Fastmath)" |
282 | 282 | "crimson") |
283 | 283 |
|
284 | 284 | ;; ## Performance Comparison |
|
302 | 302 | [lib-name fft-fn] [["Apache Commons Math" fft-apache-commons] |
303 | 303 | ["JDSP" fft-jdsp] |
304 | 304 | ["JTransforms" fft-jtransforms] |
305 | | - ["fastmath" fft-fastmath]]] |
| 305 | + ["Fastmath" fft-fastmath]]] |
306 | 306 | (let [sig (generate-test-signal size) |
307 | 307 | result (benchmark-library fft-fn sig)] |
308 | 308 | (assoc result |
|
388 | 388 | :=y :mean-ms |
389 | 389 | :=color :size |
390 | 390 | :=color-type :nominal |
391 | | - :=title "FFT Performance vs Thread Count (fastmath/JTransforms)" |
| 391 | + :=title "FFT Performance vs Thread Count (Fastmath/JTransforms)" |
392 | 392 | :=x-title "Number of Threads" |
393 | 393 | :=y-title "Mean Time per FFT (ms)" |
394 | 394 | :=width 800 |
|
433 | 433 | ;; |
434 | 434 | ;; If you need good performance, focus on: |
435 | 435 | ;; - Using power-of-2 signal sizes (triggers fast SPLIT_RADIX plan) |
436 | | -;; - Choosing an appropriate library (JTransforms/fastmath are typically fastest for 1D, though differences aren't huge) |
| 436 | +;; - Choosing an appropriate library (JTransforms/Fastmath are typically fastest for 1D, though differences aren't huge) |
437 | 437 | ;; - Optimizing your overall algorithm to minimize FFT calls |
438 | 438 |
|
439 | 439 | ;; **Note:** 2D and 3D FFTs may benefit more from parallelization since they have more work to distribute. We only tested 1D here. |
|
471 | 471 | ;; |
472 | 472 | ;; Apache Commons Math **strictly requires** signal length to be a [power of 2](https://en.wikipedia.org/wiki/Power_of_two) (128, 256, 512, 1024, etc.). This is because it only implements the classic [Cooley-Tukey algorithm](https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm). |
473 | 473 |
|
474 | | -;; ### JTransforms/fastmath: Flexible (with caveats) |
| 474 | +;; ### JTransforms/Fastmath: Flexible (with caveats) |
475 | 475 | ;; |
476 | 476 | ;; Based on our source code investigation (see the threading section above), JTransforms uses **three execution plans**: |
477 | 477 | ;; |
|
484 | 484 | ;; - ⚠️ Performance may be slower for non-power-of-2 sizes |
485 | 485 | ;; - ⚠️ MIXED_RADIX is primarily sequential; BLUESTEIN supports limited parallelization |
486 | 486 | ;; |
487 | | -;; **Recommendation:** Use power-of-2 sizes when possible for best performance (triggers SPLIT_RADIX). If your data doesn't fit, zero-pad to the next power of 2, or use JTransforms/fastmath with the understanding that a different plan will be selected. |
| 487 | +;; **Recommendation:** Use power-of-2 sizes when possible for best performance (triggers SPLIT_RADIX). If your data doesn't fit, zero-pad to the next power of 2, or use JTransforms/Fastmath with the understanding that a different plan will be selected. |
488 | 488 |
|
489 | 489 | ;; ## Related Functionality |
490 | 490 |
|
|
512 | 512 | ;; - [DHT](https://en.wikipedia.org/wiki/Discrete_Hartley_transform) (Discrete Hartley Transform) |
513 | 513 | ;; - [Multithreaded](https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)) variants for all transforms |
514 | 514 |
|
515 | | -;; ### fastmath |
| 515 | +;; ### Fastmath |
516 | 516 | ;; - [Protocol-based](https://clojure.org/reference/protocols) transform system |
517 | 517 | ;; - All JTransforms transforms (FFT, DCT, DST, DHT) |
518 | 518 | ;; - [Wavelet transforms](https://en.wikipedia.org/wiki/Wavelet) (Haar, Daubechies, Coiflet, Symlet) |
|
524 | 524 |
|
525 | 525 | ;; After comparing these libraries, here are my recommendations: |
526 | 526 |
|
527 | | -;; **For most Clojure projects: fastmath 3** |
| 527 | +;; **For most Clojure projects: Fastmath 3** |
528 | 528 | ;; |
529 | | -;; fastmath provides a good balance of performance and developer experience: |
| 529 | +;; Fastmath provides a good balance of performance and developer experience: |
530 | 530 | ;; - Idiomatic Clojure API (functional, immutable) |
531 | 531 | ;; - Leverages JTransforms' performance |
532 | 532 | ;; - Rich ecosystem (transforms, signal processing, statistics) |
|
546 | 546 | ;; If you want to use JTransforms directly and don't mind mutation: |
547 | 547 | ;; - Use `DoubleFFT_1D` directly |
548 | 548 | ;; - In-place mutations avoid allocations |
549 | | -;; - May be slightly faster than fastmath wrapper in some cases |
| 549 | +;; - May be slightly faster than Fastmath wrapper in some cases |
550 | 550 | ;; - Good for real-time audio processing or when you need fine-grained control |
551 | 551 |
|
552 | 552 | ;; **For broader DSP needs: JDSP** |
|
555 | 555 | ;; - Convenient, batteries-included library |
556 | 556 | ;; - Simple API |
557 | 557 | ;; - Good documentation |
558 | | -;; - Note: FFT performance is somewhat slower than JTransforms/fastmath |
| 558 | +;; - Note: FFT performance is somewhat slower than JTransforms/Fastmath |
559 | 559 |
|
560 | 560 | ;; **Apache Commons Math: Consider alternatives** |
561 | 561 | ;; |
562 | 562 | ;; While Commons Math is excellent for general mathematics, for FFT specifically you might prefer other options: |
563 | | -;; - Somewhat slower than JTransforms/fastmath |
| 563 | +;; - Somewhat slower than JTransforms/Fastmath |
564 | 564 | ;; - Returns boxed `Complex[]` objects (allocation overhead) |
565 | 565 | ;; - Still a reasonable choice if you're already using Commons Math for other features |
566 | 566 |
|
567 | 567 | ;; ## Summary |
568 | 568 |
|
569 | | -;; The Clojure ecosystem offers several good FFT options through Java interop. For typical signal processing tasks, **fastmath 3** is a solid choice: JTransforms' performance wrapped in a Clojure-friendly API. For direct control, use **JTransforms directly**. And if you need comprehensive DSP utilities beyond FFT, **JDSP** is worth exploring. |
| 569 | +;; The Clojure ecosystem offers several good FFT options through Java interop. For typical signal processing tasks, **Fastmath 3** is a solid choice: JTransforms' performance wrapped in a Clojure-friendly API. For direct control, use **JTransforms directly**. And if you need comprehensive DSP utilities beyond FFT, **JDSP** is worth exploring. |
0 commit comments