Skip to content
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

Feature/fix memory leak #1

Merged
3 commits merged into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Copyright (c) 2011 Matt Jibson <[email protected]>
Copyright (c) 2023 NectGmbH <[email protected]>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ go-dsp is a digital signal processing package for the [Go programming language](

## Packages

* **[dsputils](http://godoc.org/github.com/mjibson/go-dsp/dsputils)** - utilities and data structures for DSP
* **[fft](http://godoc.org/github.com/mjibson/go-dsp/fft)** - fast Fourier transform
* **[spectral](http://godoc.org/github.com/mjibson/go-dsp/spectral)** - power spectral density functions (e.g., Pwelch)
* **[wav](http://godoc.org/github.com/mjibson/go-dsp/wav)** - wav file reader functions
* **[window](http://godoc.org/github.com/mjibson/go-dsp/window)** - window functions (e.g., Hamming, Hann, Bartlett)
* **[dsputils](http://godoc.org/github.com/NectGmbH/go-dsp/dsputils)** - utilities and data structures for DSP
* **[fft](http://godoc.org/github.com/NectGmbH/go-dsp/fft)** - fast Fourier transform
* **[spectral](http://godoc.org/github.com/NectGmbH/go-dsp/spectral)** - power spectral density functions (e.g., Pwelch)
* **[wav](http://godoc.org/github.com/NectGmbH/go-dsp/wav)** - wav file reader functions
* **[window](http://godoc.org/github.com/NectGmbH/go-dsp/window)** - window functions (e.g., Hamming, Hann, Bartlett)

## Installation and Usage

```$ go get github.com/mjibson/go-dsp/fft```
```$ go get github.com/NectGmbH/go-dsp/fft```

```
package main

import (
"fmt"
"github.com/mjibson/go-dsp/fft"

"github.com/NectGmbH/go-dsp/fft"
)

func main() {
Expand Down
61 changes: 28 additions & 33 deletions fft/bluestein.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,45 @@ package fft

import (
"math"
"sync"

"github.com/mjibson/go-dsp/dsputils"
)
"github.com/NectGmbH/go-dsp/dsputils"

var (
bluesteinLock sync.RWMutex
bluesteinFactors = map[int][]complex128{}
bluesteinInvFactors = map[int][]complex128{}
"github.com/Code-Hex/go-generics-cache/policy/lfu"
)

func getBluesteinFactors(input_len int) ([]complex128, []complex128) {
bluesteinLock.RLock()
type factors struct {
normal []complex128
inverse []complex128
}

var bluesteinFactorCache = lfu.NewCache[int, factors](lfu.WithCapacity(64))

if hasBluesteinFactors(input_len) {
defer bluesteinLock.RUnlock()
return bluesteinFactors[input_len], bluesteinInvFactors[input_len]
func getBluesteinFactors(input_len int) ([]complex128, []complex128) {
factor, exists := bluesteinFactorCache.Get(input_len)
if exists {
return factor.normal, factor.inverse
}

bluesteinLock.RUnlock()
bluesteinLock.Lock()
defer bluesteinLock.Unlock()

if !hasBluesteinFactors(input_len) {
bluesteinFactors[input_len] = make([]complex128, input_len)
bluesteinInvFactors[input_len] = make([]complex128, input_len)

var sin, cos float64
for i := 0; i < input_len; i++ {
if i == 0 {
sin, cos = 0, 1
} else {
sin, cos = math.Sincos(math.Pi / float64(input_len) * float64(i*i))
}
bluesteinFactors[input_len][i] = complex(cos, sin)
bluesteinInvFactors[input_len][i] = complex(cos, -sin)
normal := make([]complex128, input_len)
inverse := make([]complex128, input_len)

var sin, cos float64
for i := 0; i < input_len; i++ {
if i == 0 {
sin, cos = 0, 1
} else {
sin, cos = math.Sincos(math.Pi / float64(input_len) * float64(i*i))
}
normal[i] = complex(cos, sin)
inverse[i] = complex(cos, -sin)
}

return bluesteinFactors[input_len], bluesteinInvFactors[input_len]
}
bluesteinFactorCache.Set(input_len, factors{
normal: normal,
inverse: inverse,
})

func hasBluesteinFactors(idx int) bool {
return bluesteinFactors[idx] != nil
return normal, inverse
}

// bluesteinFFT returns the FFT calculated using the Bluestein algorithm.
Expand Down
9 changes: 4 additions & 5 deletions fft/fft.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package fft

import (
"github.com/mjibson/go-dsp/dsputils"
"github.com/NectGmbH/go-dsp/dsputils"
)

// FFTReal returns the forward FFT of the real-valued slice.
Expand Down Expand Up @@ -86,9 +86,7 @@ func FFT(x []complex128) []complex128 {
return bluesteinFFT(x)
}

var (
worker_pool_size = 0
)
var worker_pool_size = 0

// SetWorkerPoolSize sets the number of workers during FFT computation on multicore systems.
// If n is 0 (the default), then GOMAXPROCS workers will be created.
Expand Down Expand Up @@ -192,7 +190,8 @@ func computeFFTN(m *dsputils.Matrix, fftFunc func([]complex128) []complex128) *d
}

// decrDim decrements an element of x by 1, skipping all -1s, and wrapping up to d.
// If a value is 0, it will be reset to its correspending value in d, and will carry one from the next non -1 value to the right.
// If a value is 0, it will be reset to its correspending value in d, and will carry one from the
// next non -1 value to the right.
// Returns true if decremented, else false.
func decrDim(x, d []int) bool {
for n, v := range x {
Expand Down
89 changes: 71 additions & 18 deletions fft/fft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"runtime"
"testing"

"github.com/mjibson/go-dsp/dsputils"
"github.com/NectGmbH/go-dsp/dsputils"
)

const (
Expand Down Expand Up @@ -59,7 +59,8 @@ var fftTests = []fftTest{
complex(1, 0),
complex(1, 0),
complex(1, 0),
complex(1, 0)},
complex(1, 0),
},
},

// shifted impulse response
Expand All @@ -81,7 +82,8 @@ var fftTests = []fftTest{
complex(-1, 0),
complex(-sqrt2_2, sqrt2_2),
complex(0, 1),
complex(sqrt2_2, sqrt2_2)},
complex(sqrt2_2, sqrt2_2),
},
},

// other
Expand All @@ -91,15 +93,17 @@ var fftTests = []fftTest{
complex(10, 0),
complex(-2, 2),
complex(-2, 0),
complex(-2, -2)},
complex(-2, -2),
},
},
{
[]float64{1, 3, 5, 7},
[]complex128{
complex(16, 0),
complex(-4, 4),
complex(-4, 0),
complex(-4, -4)},
complex(-4, -4),
},
},
{
[]float64{1, 2, 3, 4, 5, 6, 7, 8},
Expand All @@ -111,7 +115,8 @@ var fftTests = []fftTest{
complex(-4, 0),
complex(-4, -1.65685425),
complex(-4, -4),
complex(-4, -9.65685425)},
complex(-4, -9.65685425),
},
},

// non power of 2 lengths
Expand All @@ -122,21 +127,24 @@ var fftTests = []fftTest{
complex(1, 0),
complex(1, 0),
complex(1, 0),
complex(1, 0)},
complex(1, 0),
},
},
{
[]float64{1, 2, 3},
[]complex128{
complex(6, 0),
complex(-1.5, 0.8660254),
complex(-1.5, -0.8660254)},
complex(-1.5, -0.8660254),
},
},
{
[]float64{1, 1, 1},
[]complex128{
complex(3, 0),
complex(0, 0),
complex(0, 0)},
complex(0, 0),
},
},
}

Expand All @@ -150,14 +158,34 @@ var fft2Tests = []fft2Test{
[][]float64{{1, 2, 3}, {3, 4, 5}},
[][]complex128{
{complex(18, 0), complex(-3, 1.73205081), complex(-3, -1.73205081)},
{complex(-6, 0), complex(0, 0), complex(0, 0)}},
{complex(-6, 0), complex(0, 0), complex(0, 0)},
},
},
{
[][]float64{{0.1, 0.2, 0.3, 0.4, 0.5}, {1, 2, 3, 4, 5}, {3, 2, 1, 0, -1}},
[][]complex128{
{complex(21.5, 0), complex(-0.25, 0.34409548), complex(-0.25, 0.08122992), complex(-0.25, -0.08122992), complex(-0.25, -0.34409548)},
{complex(-8.5, -8.66025404), complex(5.70990854, 4.6742225), complex(1.15694356, 4.41135694), complex(-1.65694356, 4.24889709), complex(-6.20990854, 3.98603154)},
{complex(-8.5, 8.66025404), complex(-6.20990854, -3.98603154), complex(-1.65694356, -4.24889709), complex(1.15694356, -4.41135694), complex(5.70990854, -4.6742225)}},
{
complex(21.5, 0),
complex(-0.25, 0.34409548),
complex(-0.25, 0.08122992),
complex(-0.25, -0.08122992),
complex(-0.25, -0.34409548),
},
{
complex(-8.5, -8.66025404),
complex(5.70990854, 4.6742225),
complex(1.15694356, 4.41135694),
complex(-1.65694356, 4.24889709),
complex(-6.20990854, 3.98603154),
},
{
complex(-8.5, 8.66025404),
complex(-6.20990854, -3.98603154),
complex(-1.65694356, -4.24889709),
complex(1.15694356, -4.41135694),
complex(5.70990854, -4.6742225),
},
},
},
}

Expand All @@ -176,7 +204,8 @@ var fftnTests = []fftnTest{
complex(-42, 0), complex(-10.5, 6.06217783), complex(-10.5, -6.06217783),

complex(-48, 0), complex(-4.5, -11.25833025), complex(-4.5, 11.25833025),
complex(22, 0), complex(8.5, -6.06217783), complex(8.5, 6.06217783)},
complex(22, 0), complex(8.5, -6.06217783), complex(8.5, 6.06217783),
},
},
}

Expand All @@ -203,7 +232,14 @@ func TestFFT(t *testing.T) {

vi := IFFT(ft.out)
if !dsputils.PrettyCloseC(vi, dsputils.ToComplex(ft.in)) {
t.Error("IFFT error\ninput:", ft.out, "\noutput:", vi, "\nexpected:", dsputils.ToComplex(ft.in))
t.Error(
"IFFT error\ninput:",
ft.out,
"\noutput:",
vi,
"\nexpected:",
dsputils.ToComplex(ft.in),
)
}
}
}
Expand All @@ -217,7 +253,14 @@ func TestFFT2(t *testing.T) {

vi := IFFT2(ft.out)
if !dsputils.PrettyClose2(vi, dsputils.ToComplex2(ft.in)) {
t.Error("IFFT2 error\ninput:", ft.out, "\noutput:", vi, "\nexpected:", dsputils.ToComplex2(ft.in))
t.Error(
"IFFT2 error\ninput:",
ft.out,
"\noutput:",
vi,
"\nexpected:",
dsputils.ToComplex2(ft.in),
)
}
}
}
Expand All @@ -243,7 +286,16 @@ func TestReverseBits(t *testing.T) {
v := reverseBits(rt.in, rt.sz)

if v != rt.out {
t.Error("reverse bits error\ninput:", rt.in, "\nsize:", rt.sz, "\noutput:", v, "\nexpected:", rt.out)
t.Error(
"reverse bits error\ninput:",
rt.in,
"\nsize:",
rt.sz,
"\noutput:",
v,
"\nexpected:",
rt.out,
)
}
}
}
Expand Down Expand Up @@ -279,7 +331,8 @@ func BenchmarkFFT(b *testing.B) {
}
}

// This example is adapted from Richard Lyon's "Understanding Digital Signal Processing," section 3.1.1.
// This example is adapted from Richard Lyon's "Understanding Digital Signal Processing," section
// 3.1.1.
func ExampleFFTReal() {
numSamples := 8

Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/NectGmbH/go-dsp

go 1.18

require github.com/Code-Hex/go-generics-cache v1.2.1
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/Code-Hex/go-generics-cache v1.2.1 h1:jKof8Hk8mr28lcEAo9g90oj7H3vb8y2DdccoGpL4lyE=
github.com/Code-Hex/go-generics-cache v1.2.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
6 changes: 3 additions & 3 deletions spectral/pwelch.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import (
"math"
"math/cmplx"

"github.com/mjibson/go-dsp/dsputils"
"github.com/mjibson/go-dsp/fft"
"github.com/mjibson/go-dsp/window"
"github.com/NectGmbH/go-dsp/dsputils"
"github.com/NectGmbH/go-dsp/fft"
"github.com/NectGmbH/go-dsp/window"
)

type PwelchOptions struct {
Expand Down
Loading