[WIP] Implements reusable sample buffers across all components. This

produces a significant reduction in memory usage, especially for tuners
like the airspy and hackrf that produce 10/20 MSPS sample rates.  This
is observed to be around 66% reduction in peak memory usage and provides
a more stable memory utilization across time reducing stress on the
garbage collector.

Use of these reusable memory buffers will be pushed further down into
the processing chain modules to ensure we keep new object creation
to the lowest levels possible.
This commit is contained in:
Denny
2018-02-03 10:05:16 -05:00
parent fcd3c2fd46
commit f2f2c92afe
56 changed files with 1630 additions and 1255 deletions

View File

@@ -25,7 +25,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public class Broadcaster<T> implements Listener<T>
{
private List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
protected List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
/**
* Implements the Listener<T> interface to receive an element and broadcast that element to all registered

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.sample;
public class Buffer
{
private float[] mSamples;
protected float[] mSamples;
/**
* Buffer containing float sample data

View File

@@ -36,9 +36,9 @@ public class OverflowableTransferQueue<E>
private IOverflowListener mOverflowListener;
private Source mSourceOverflowListener;
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
private AtomicInteger mCounter = new AtomicInteger();
private AtomicBoolean mOverflow = new AtomicBoolean();
protected LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
protected AtomicInteger mCounter = new AtomicInteger();
protected AtomicBoolean mOverflow = new AtomicBoolean();
private int mMaximumSize;
private int mResetThreshold;
@@ -73,6 +73,21 @@ public class OverflowableTransferQueue<E>
setOverflow(true);
}
}
else
{
overflow(e);
}
}
/**
* Invoked when the buffer is in an overflow state. The element argument is thrown away. Override this method
* in subclasses to perform any necessary cleanup action(s).
*
* @param e element that is being thrown away due to an overflow condition
*/
protected void overflow(E e)
{
//No-op. Override in subclass to perform any cleanup actions during overflow
}
/**

View File

@@ -0,0 +1,58 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.sample.adapter;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* Converts 16-bit little endian byte data into an array of complex floats
*/
public class ComplexShortAdapter implements IComplexSampleAdapter
{
private ShortToFloatMap mMap = new ShortToFloatMap();
private FloatBuffer mFloatBuffer;
private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN;
@Override
public void convertAndLoad(byte[] samples, ReusableComplexBuffer reusableComplexBuffer)
{
int sampleCount = samples.length / 2;
if(mFloatBuffer == null || mFloatBuffer.capacity() != sampleCount)
{
mFloatBuffer = FloatBuffer.allocate(sampleCount);
}
mFloatBuffer.rewind();
/* Wrap byte array in a byte buffer so we can process them as shorts */
ByteBuffer buffer = ByteBuffer.wrap(samples);
/* Set endian to correct byte ordering */
buffer.order(mByteOrder);
while(buffer.hasRemaining())
{
mFloatBuffer.put(mMap.get(buffer.getShort()));
}
reusableComplexBuffer.reloadFrom(mFloatBuffer, System.currentTimeMillis());
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.sample.adapter;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
public interface IComplexSampleAdapter
{
/**
* Converts the sample byte array into complex float samples, loading the converted float samples into the buffer.
* @param samples
* @param reusableComplexBuffer
*/
void convertAndLoad(byte[] samples, ReusableComplexBuffer reusableComplexBuffer);
}

View File

@@ -19,9 +19,6 @@ import io.github.dsheirer.sample.Buffer;
public class ComplexBuffer extends Buffer
{
//Default timestamp - we use a static variable here so that there is only ever 1 instance in memory
private static final long DEFAULT_TIMESTAMP = 0;
/**
* Wrapper around float array containing interleaved I/Q samples
*/
@@ -29,24 +26,4 @@ public class ComplexBuffer extends Buffer
{
super(samples);
}
/**
* Reference timestamp for the first sample in this buffer. This functionality is intended to be implemented by a
* subclass implementation. The default value is 0.
*
* @return reference timestamp or a value of 0 if hasTimestamp() indicates false.
*/
public long getTimestamp()
{
return DEFAULT_TIMESTAMP;
}
/**
* Indicates if this buffer has a timestamp that references the first sample in the buffer.
*/
public boolean hasTimestamp()
{
return false;
}
}

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.sample.complex.reusable;
import io.github.dsheirer.sample.OverflowableTransferQueue;
public class OverflowableReusableBufferTransferQueue extends OverflowableTransferQueue<ReusableComplexBuffer>
{
/**
* Concurrent transfer queue that couples a higher-throughput linked transfer queue with an atomic integer for
* monitoring queue size. When the queue size exceeds maximum size (overflow), all inbound elements are ignored
* until the queue size is reduced to or below the reset threshold.
*
* This implementation includes special handling for reusable complex buffers.
*
* @param maximumSize of the queue. Overflow state will occur once queue size exceeds this value.
* @param resetThreshold for resetting overflow state to normal, once queue size is at or below this value.
*/
public OverflowableReusableBufferTransferQueue(int maximumSize, int resetThreshold)
{
super(maximumSize, resetThreshold);
}
/**
* Overrides the overflow method to decrement the user count on any buffers that are being discarded when the queue
* is in an overflow state.
*
* @param reusableComplexBuffer that will be discarded
*/
@Override
protected void overflow(ReusableComplexBuffer reusableComplexBuffer)
{
reusableComplexBuffer.decrementUserCount();
}
/**
* Overrides the buffer clear method to decrement the user count on each buffer that is being cleared from the queue.
*/
@Override
public void clear()
{
synchronized(mQueue)
{
ReusableComplexBuffer buffer = mQueue.poll();
while(buffer != null)
{
buffer.decrementUserCount();
buffer = mQueue.poll();
}
mCounter.set(0);
mOverflow.set(false);
}
}
}