-
Notifications
You must be signed in to change notification settings - Fork 11
extend rustfft to singlestore DB #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "fft" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen.git", rev = "60e3c5b41e616fee239304d92128e117dd9be0a7" } | ||
rustfft = "6.1.0" | ||
num-complex = "0.4.3" | ||
serde_json = "1.0.97" | ||
serde = { version = "1.0.164", features = ["derive"] } | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
|
||
[[bin]] | ||
name = "test" | ||
path = "test/main.rs" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
MODULE := fft | ||
|
||
.PHONY: debug | ||
debug: $(eval TGT:=debug) | ||
debug: wasm | ||
|
||
.PHONY: release | ||
release: $(eval TGT:=release) | ||
release: RELFLAGS = --release | ||
release: wasm | ||
|
||
.PHONY: wasm | ||
wasm: | ||
cargo wasi build --lib $(RELFLAGS) | ||
|
||
.PHONY: test | ||
test: debug | ||
writ-docker --verbose \ | ||
-e '[{"re":1.0,"im":2.0}]' \ | ||
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-forward \ | ||
1 '[{"re":1.0,"im":2.0}]' | ||
@echo PASS | ||
writ-docker --verbose \ | ||
-e '[{"re":1.0,"im":2.0}]' \ | ||
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-inverse \ | ||
1 '[{"re":1.0,"im":2.0}]' | ||
@echo PASS | ||
writ-docker --verbose \ | ||
-e '[{"re":1.0,"im":2.0}]' \ | ||
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-scalar-process-forward \ | ||
1 '[{"re":1.0,"im":2.0}]' | ||
@echo PASS | ||
writ-docker --verbose \ | ||
-e '[{"re":1.0,"im":2.0}]' \ | ||
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-scalar-process-inverse \ | ||
1 '[{"re":1.0,"im":2.0}]' | ||
@echo PASS | ||
writ-docker --verbose \ | ||
-e '[{"re":2.5,"im":4.5},{"re":-0.5,"im":-0.5}]' \ | ||
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-forward \ | ||
2 '[{"re":1.0,"im":2.0},{"re":1.5,"im":2.5}]' | ||
@echo PASS | ||
writ-docker --verbose \ | ||
-e '[{"re":2.5,"im":4.5},{"re":-0.5,"im":-0.5}]' \ | ||
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-inverse \ | ||
2 '[{"re":1.0,"im":2.0},{"re":1.5,"im":2.5}]' | ||
@echo PASS | ||
.PHONY: clean | ||
clean: | ||
@cargo clean | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Fast Fourier Transform in SingleStoreDB | ||
|
||
## Introduction | ||
|
||
[Fast Fourier transform](https://en.wikipedia.org/wiki/Fast_Fourier_transform) is an algorithm that computes the discrete Fourier transform (DFT) of a sequence, or its inverse (IDFT). | ||
Fourier analysis converts a signal from its original domain (often time or space) to a representation in the frequency domain and vice versa. The DFT is obtained by decomposing a sequence of values into components of different frequencies. | ||
Fast Fourier transforms are widely used for applications in engineering, music, science, and mathematics. The FFT is used in digital recording, sampling, additive synthesis and pitch correction software. | ||
|
||
This library uses [rustfft](https://docs.rs/rustfft/latest/rustfft/) for algorithm implementation. This is a high-performance, SIMD-accelerated FFT library, compute FFTs of any size, in O(nlogn) time. Support for hardware acceleration (Avx, Neon, Sse) from rustfft is not ported to this module. | ||
|
||
## Contents | ||
This library provides the following database objects. | ||
|
||
### `st_planner_forward(len: u8, buffer: [Complex<f64>])` | ||
This is a TVF that will create a a new FFT algorthim instance for computing forward FFT of size `len`. Divides the `buffer` into chunks of size `len`, and computes FFT forward on each chunk. | ||
This method will panic (on Rust side) if: | ||
``` | ||
buffer.len() % len > 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using database operators, this would be: LENGTH(buffer) MOD len > 0 |
||
buffer.len() < len | ||
``` | ||
|
||
### `st_planner_inverse(len: u8, buffer: [Complex<f64>])` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comments as above in favor of using database syntax. |
||
This is a TVF that will create a a new FFT algorthim instance for computing inverse FFT of size `len`. Divides the `buffer` into chunks of size `len`, and computes FFT inverse on each chunk. | ||
This method will panic (on Rust side) if: | ||
``` | ||
buffer.len() % len > 0 | ||
buffer.len() < len | ||
``` | ||
|
||
## Building | ||
The Wasm module can be built using the following commands. The build requires Rust with the WASI extension. | ||
```bash | ||
# Install the WASI cargo extension. | ||
cargo install cargo-wasi | ||
|
||
# Compile the Wasm module. | ||
cargo wasi build --release | ||
``` | ||
The binary will be placed in `target/wasm32-wasi/release/fft.wasm`. | ||
|
||
## Deployment to SingleStoreDB | ||
|
||
To install these functions using the MySQL client, use the following commands. This command assumes you have built the Wasm module and your current directory is the root of this Git repo. Replace `$DBUSER`, `$DBHOST`, `$DBPORT`, and `$DBNAME` with, respectively, your database username, hostname, port, and the name of the database where you want to deploy the functions. | ||
```bash | ||
cat <<EOF | mysql -u $DBUSER -h $DBHOST -P $DBPORT -D $DBNAME -p | ||
CREATE FUNCTION st_process_forward RETURNS TABLE AS WASM FROM LOCAL INFILE "target/wasm32-wasi/release/fft.wasm" WITH WIT FROM LOCAL INFILE "fft.wit"; | ||
CREATE FUNCTION st_process_inverse RETURNS TABLE AS WASM FROM LOCAL INFILE "target/wasm32-wasi/release/fft.wasm" WITH WIT FROM LOCAL INFILE "fft.wit"; | ||
``` | ||
|
||
Alternatively, you can install these functions using [pushwasm](https://github.com/singlestore-labs/pushwasm) with the following command lines. As above, be sure to substitute the environment variables with values of your own. | ||
```bash | ||
pushwasm tvf --force --prompt --name st_process_forward \ | ||
--wasm target/wasm32-wasi/release/fft.wasm \ | ||
--wit fft.wit \ | ||
--abi canonical \ | ||
--conn "mysql://$DBUSER@$DBHOST:$DBPORT/$DBNAME" | ||
pushwasm tvf --force --prompt --name st_process_inverse \ | ||
--wasm target/wasm32-wasi/release/fft.wasm \ | ||
--wit fft.wit \ | ||
--abi canonical \ | ||
--conn "mysql://$DBUSER@$DBHOST:$DBPORT/$DBNAME" | ||
``` | ||
|
||
## Usage | ||
The following is a simple example that creates two tables with a columns of strings. The first table's column is used to generate a Bloom Filter, which we store in a User Defined Variable. We then run the Bloom Filter on the strings in the second table. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you forgot to delete this paragraph :-) |
||
The following is a simple example that performs forward FFT on a vector `buffer` of two complex numbers `{"re": 1.0, "im": 2.5}` and `{"re": 2.0, "im": 2.5}`. This will divides the vector `buffer` into chunks of size `1` and computes a FFT on each chunk. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. divides -> divide |
||
```sql | ||
SELECT * FROM (st_process_forward(1, [ROW(1.0, 2.5), ROW(2.0, 2.5)])); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The outer parens aren't needed here. |
||
``` | ||
|
||
This should produce the following output: | ||
```console | ||
+----+-----+ | ||
| re | im | | ||
+----+-----+ | ||
| 1 | 2.5 | | ||
| 2 | 2.5 | | ||
+----+-----+ | ||
2 rows in set (0.004 sec) | ||
``` | ||
|
||
## Additional Information | ||
|
||
To learn about the process of developing a Wasm UDF or TVF in more detail, please have a look at our [tutorial](https://singlestore-labs.github.io/singlestore-wasm-toolkit/html/Tutorial-Overview.html). | ||
|
||
The SingleStoreDB Wasm UDF/TVF documentation is [here](https://docs.singlestore.com/managed-service/en/reference/code-engine---powered-by-wasm.html). | ||
|
||
## Resources | ||
|
||
* [Fast Fourier Transform](https://en.wikipedia.org/wiki/Fast_Fourier_transform) | ||
* [Rust FFT library](https://docs.rs/rustfft/latest/rustfft/) | ||
* [Documentation](https://docs.singlestore.com) | ||
* [Twitter](https://twitter.com/SingleStoreDevs) | ||
* [SingleStore forums](https://www.singlestore.com/forum) | ||
* [SingleStoreDB extension template for C++](https://github.com/singlestore-labs/singlestoredb-extension-cpp-template) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
record stcomplex64 { | ||
re: float64, | ||
im: float64 | ||
} | ||
|
||
st-process-forward: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64> | ||
st-process-inverse: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64> | ||
st-scalar-process-forward: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64> | ||
st-scalar-process-inverse: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use rustfft::{FftPlanner, FftPlannerScalar, num_complex::Complex64}; | ||
use fft::{Stcomplex64}; | ||
|
||
wit_bindgen_rust::export!("fft.wit"); | ||
|
||
struct Fft; | ||
|
||
fn from_num_complex(list_num_complex: Vec<Complex64>) -> Vec<Stcomplex64> { | ||
list_num_complex.iter().map(|x| Stcomplex64 { | ||
re: x.re, | ||
im: x.im | ||
}).collect::<Vec<Stcomplex64>>() | ||
} | ||
|
||
fn from_st_complex(list_st_complex: Vec<Stcomplex64>) -> Vec<Complex64> { | ||
list_st_complex.iter().map(|x| Complex64::new(x.re, x.im)).collect::<Vec<Complex64>>() | ||
} | ||
|
||
impl crate::fft::Fft for Fft { | ||
fn st_process_forward(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> { | ||
let mut planner = FftPlanner::new(); | ||
let fft = planner.plan_fft_forward(usize::from(l)); | ||
let mut buffer = from_st_complex(buf); | ||
fft.process(&mut buffer); | ||
from_num_complex(buffer) | ||
} | ||
fn st_process_inverse(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> { | ||
let mut planner = FftPlanner::new(); | ||
let fft = planner.plan_fft_inverse(usize::from(l)); | ||
let mut buffer = from_st_complex(buf); | ||
fft.process(&mut buffer); | ||
from_num_complex(buffer) | ||
} | ||
fn st_scalar_process_forward(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> { | ||
let mut planner = FftPlannerScalar::new(); | ||
let fft = planner.plan_fft_forward(usize::from(l)); | ||
let mut buffer = from_st_complex(buf); | ||
fft.process(&mut buffer); | ||
from_num_complex(buffer) | ||
} | ||
fn st_scalar_process_inverse(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> { | ||
let mut planner = FftPlannerScalar::new(); | ||
let fft = planner.plan_fft_inverse(usize::from(l)); | ||
let mut buffer = from_st_complex(buf); | ||
fft.process(&mut buffer); | ||
from_num_complex(buffer) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Generate json testing input / output for make test | ||
use rustfft::{FftPlanner}; | ||
use num_complex::{Complex64}; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct Complex64Def { | ||
pub re: f64, | ||
pub im: f64, | ||
} | ||
|
||
impl From<Complex64> for Complex64Def { | ||
fn from(def: Complex64) -> Complex64Def { | ||
Complex64Def { re : def.re, im : def.im } | ||
} | ||
} | ||
|
||
fn from_num_complex(list_num_complex: Vec<Complex64>) -> Vec<Complex64Def> { | ||
list_num_complex.iter().map(|x| Complex64Def { | ||
re: x.re, | ||
im: x.im | ||
}).collect::<Vec<Complex64Def>>() | ||
} | ||
|
||
fn print_json_debug<'a, T: Serialize + Deserialize<'a>>(type_with_serialize: T) { | ||
let json_buffer = serde_json::to_string(&type_with_serialize).unwrap(); | ||
println!("{}", json_buffer); | ||
} | ||
|
||
fn test_forward(l: u8, mut buffer: Vec<Complex64>) { | ||
let mut planner = FftPlanner::new(); | ||
let fft = planner.plan_fft_forward(usize::from(l)); | ||
fft.process(&mut buffer); | ||
print_json_debug(from_num_complex(buffer)); | ||
} | ||
|
||
fn test_inverse(l: u8, mut buffer: Vec<Complex64>) { | ||
let mut planner = FftPlanner::new(); | ||
let fft = planner.plan_fft_inverse(usize::from(l)); | ||
fft.process(&mut buffer); | ||
print_json_debug(from_num_complex(buffer)); | ||
} | ||
|
||
fn main() { | ||
//test_forward(1, vec![Complex64{ re: 1.0, im: 2.0 }; 1]); | ||
//test_inverse(1, vec![Complex64{ re: 1.0, im: 2.0 }; 1]); | ||
let vtor_input = vec![Complex64{ re: 1.0, im: 2.0 }, Complex64{ re: 1.5, im: 2.5 }]; | ||
let json_input = serde_json::to_string(&from_num_complex(vtor_input.clone())).unwrap(); | ||
println!("{}", json_input); | ||
test_forward(1, vtor_input.clone()); | ||
test_forward(2, vtor_input.clone()); | ||
test_inverse(2, vtor_input.clone()); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the database types here. So, for this case it would be:
This is a bit long for the heading line, so maybe just put
st_planner_forward
in the heading and then put the UDF signature in the description.