Glitch in dynamic plugin

Hi.

I’ve (badly) written a dynamic plugin in vvvv. It processes a fairly high spreadcount of values and outputs a much lower count of values.

For some reason it’s responsible for very regular (about every half second) dropouts, ie massive reductions in frame rate.

How do I tell which part of the code is responsible for this?

I don’t know where to begin troubleshooting

You’re probably allocating too much memory each frame causing the garbage collection to kick in. Posting the code would helpthough.

Hi Elias

This is almost certainly really badly coded :)

I have a large spectral spread in which I’m using the differential between slices to look for ‘peaks’ and then finding the ‘width’ of the falloff around each peak. Not sure if it’ll make much sense!

- region usings
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Collections.Generic;

using VVVV.PluginInterfaces.V1;
using VVVV.PluginInterfaces.V2;
using VVVV.Utils.VColor;
using VVVV.Utils.VMath;

using VVVV.Core.Logging;
- endregion usings

namespace VVVV.Nodes
{
	#region PluginInfo
	[PluginInfo(Name = "led_val_to_frame_data", Category = "Value", Help = "Basic template with one value in/out", Tags = "")](PluginInfo(Name = "led_val_to_frame_data", Category = "Value", Help = "Basic template with one value in/out", Tags = ""))
	#endregion PluginInfo
	public class Valueled_val_to_frame_dataNode : IPluginEvaluate
	{
		#region fields & pins
		[Input("LED spread in", DefaultValue = 1.0)](Input("LED spread in", DefaultValue = 1.0))
		public ISpread<double> FInput;
		
		[Input("width to frame number multiplier", DefaultValue = 1.0)](Input("width to frame number multiplier", DefaultValue = 1.0))
		public ISpread<double> widthFrameMult;
		
		[Input("num of leds per arc", DefaultValue = 1, IsSingle = true)](Input("num of leds per arc", DefaultValue = 1, IsSingle = true))
		public ISpread<int> ledCount;
		
		[Input("peak minimum threshold", DefaultValue = 0, IsSingle = true)](Input("peak minimum threshold", DefaultValue = 0, IsSingle = true))
		public ISpread<double> peakMin;
		
		[Input("find highest peaks", DefaultValue = 0, IsSingle = true)](Input("find highest peaks", DefaultValue = 0, IsSingle = true))
		public ISpread<bool> sortPeaks;

		[Output("Peaks Heights")](Output("Peaks Heights"))
		public ISpread<double> justPeaks;
		
		[Output("Width on side L")](Output("Width on side L"))
		public ISpread<double> justWidthsOutL;

		[Output("Width on side R")](Output("Width on side R"))
		public ISpread<double> justWidthsOutR;
		
		[Output("Peak Centre")](Output("Peak Centre"))
		public ISpread<double> peakCentre;
		
		
		//[Output("Dip Height")](Output("Dip Height"))
		//public ISpread<double> dipHeight;
		
		[Output("Peaks count per Arc")](Output("Peaks count per Arc"))
		public ISpread<int> peakCount;

		[Import()](Import())
		public ILogger FLogger;
		#endregion fields & pins

		//called when data for any output pin is requested
		
		public int mapWidth(double width, double factor)
		{
			return (int)Math.Max(Math.Min(width * factor, 1023),0);
		}
		
		// function to initialise array with values
		static void InitIntArray(int[]() arr)
	    {
			for (int i = 0; i < arr.Length; i++)
			{
			    arr[i](i) = -1;
			}
	    }	
		
		static void InitDoubleArray(double[]() arr)
	    {
			for (int i = 0; i < arr.Length; i++)
			{
			    arr[i](i) = -1;
			}
	    }	
		
		
		// oject to hold accumulated output data.  Reset each arc iter
		class Peak
	    {
	        public Peak(double height, double centre, int widthL, int widthR)
	        {
	            Height = height;
	            Centre = centre;
	        	WidthL = widthL;
	            WidthR = widthR;
	        }
	        public double Height { get; set; }
	        public double Centre { get; set; }
	    	public double WidthL { get; set; }
	        public double WidthR { get; set; }
	    }
		
		public void Evaluate(int SpreadMax)
		{
			int diffCount = ledCount[0](0)-1;
			int arcCount = SpreadMax/ledCount[0](0);
			
			int outputSliceCount = SpreadMax - arcCount;
			
			justPeaks.SliceCount = arcCount * 3;		//
			justWidthsOutL.SliceCount = arcCount * 3;		//
			justWidthsOutR.SliceCount = arcCount * 3;		//
			peakCentre.SliceCount = arcCount * 3;		//
			peakCount.SliceCount = arcCount;
			
			
			//double[]() diffArray = new double[outputSliceCount](outputSliceCount);
			//double[]() dipHeight = new double[SpreadMax](SpreadMax);
			
			double[]() diffArray = new double[outputSliceCount](outputSliceCount);
			double[]() dipHeight = new double[SpreadMax+arcCount](SpreadMax+arcCount);
			

			List<Peak> peaks = new List<Peak>();

			
			bool foundPeak = false;
			
			
			for (int i = 0; i<arcCount; i++) //loop through arcs
			{
				int j=0; // index of current led
				
				peaks.Add(new Peak(0, 0, 0, 0));  // add 3 peaks so we don't need to check count before sorting later
				peaks.Add(new Peak(0, 0, 0, 0));
				peaks.Add(new Peak(0, 0, 0, 0));
				
				for (j=0; j<diffCount; j++) //loop through leds in arc
				{
					
					int IndexIn = i*ledCount[0](0) + j;
					int IndexOut = i*diffCount + j;
					dipHeight[IndexOut](IndexOut) = 0;

					
					

					
					diffArray[IndexOut](IndexOut) = FInput[IndexIn+1](IndexIn+1) - FInput[IndexIn](IndexIn);
					
					
					// id the first in arc
					if(j == 0)
					{
						if(diffArray[IndexOut](IndexOut) < 0)
						{
							foundPeak = true;
							peaks.Add(new Peak(FInput[IndexIn](IndexIn), j*0.00523560, 0, 0)); //0.00523560 is to normalise peak centre slice index (1/192)
						}
					}
					
					//is this the last in an arc
					else if(j == diffCount-1)
					{
						if(diffArray[| (diffArray[IndexOut](IndexOut](| (diffArray[IndexOut](IndexOut) > 0 ) < 0 && diffArray[IndexOut-1](IndexOut-1) >0) )
						{
							foundPeak = true;
							peaks.Add(new Peak(FInput[IndexIn](IndexIn), j*0.00523560, 0, 0));
						}
					}
					
					//then it's the rest
					else
					{
						if(diffArray[IndexOut-1](IndexOut-1) >= 0 && diffArray[IndexOut](IndexOut) < 0) //diff[i-1](i-1) is being taken from the last frame so might cause issues
						{
							foundPeak = true;
							peaks.Add(new Peak(FInput[IndexIn](IndexIn), j*0.00523560, 0, 0));	
						}
						
						
						// find dips (troughs)
						dipHeight[i](i) = 0;
						if(diffArray[IndexOut-1](IndexOut-1) <= 0 && diffArray[IndexOut](IndexOut) > 0) //diff[i-1](i-1) is being taken from the last frame so might cause issues
						{
							dipHeight[IndexOut](IndexOut) = FInput[IndexIn](IndexIn);
						}
					}
					
					

					
					//find widths
					if(foundPeak)
					{
						//double currentVal = peakHeight[IndexOut](IndexOut);
						double currentHeight = peaks.Last().Height;
						double changingHeight = currentHeight;
						double previousHeight = currentHeight;
						int k = 0;
						
						// find index of half decay point on RIGHT side
						while( k<192 )
						{
							if(dipHeight[IndexOut+k](IndexOut+k) > 0.01 || IndexOut+k == diffCount-1)
							{
								double temp = changingHeight/(currentHeight/2);
								temp = Math.Pow(temp, 1.75);
								temp *=k;
								k = (int)temp;
								
								break;
							}
							previousHeight = changingHeight;
							changingHeight = FInput[IndexIn+k+1](IndexIn+k+1);
							if(changingHeight < (currentHeight/2))
							{
								break;
							}
							k++;
						}
									
						double subSampleShift = (previousHeight-(currentHeight/2)) / (previousHeight-changingHeight);
						peaks.Last().WidthR = mapWidth(k+subSampleShift, widthFrameMult[i](i));
						
						changingHeight = currentHeight;
						
						k = 0;
						
						// find index of half decay point on LEFT side
						while( k<192 )
						{
							if(dipHeight[IndexOut+k](IndexOut+k) > 0.01 || IndexOut+k == 0)
							{
								double temp = changingHeight/(currentHeight/2);
								temp = Math.Pow(temp, 1.75);
								temp *= k;
								k = (int)temp;
								
								break;
							}
							previousHeight = changingHeight;
							changingHeight = FInput[IndexIn+k-1](IndexIn+k-1);
							if( changingHeight < (currentHeight/2) )
							{
								break;
							}
							k--;
						}
						subSampleShift = (previousHeight-(currentHeight/2)) / (previousHeight-changingHeight);
						peaks.Last().WidthL = mapWidth(-(k-subSampleShift), widthFrameMult[i](i));
					}
					

					foundPeak = false;
				}
				
				
				
				int listSize = peaks.Count;
				peakCount[i](i) = listSize - 3; //take off the 3 dummies we added
				
				if(sortPeaks[0](0))
				{
					peaks = peaks.OrderBy(x => x.Height).ToList(); //sort by height to find biggest peaks
				}
				
				
				for (int l=1; l<4; l++)
				{
					int offset = 3 * i;
					justPeaks[l+offset](l+offset) = peaks[listSize-l](listSize-l).Height;
					peakCentre[l+offset](l+offset) = peaks[listSize-l](listSize-l).Centre;
					justWidthsOutL[l+offset](l+offset) = peaks[listSize-l](listSize-l).WidthL;
					justWidthsOutR[l+offset](l+offset) = peaks[listSize-l](listSize-l).WidthR;
				}
				
				peaks.Clear(); // clear list (so list calcs only happen per arc)
			}
			
			
		}
		
	}
	
}

a few thoughts to reduce memory allocations:

  • put the peaks list into your class and clear it every frame. this way the internal array used by the list can be reused. see List<T>.Capacity Property (System.Collections.Generic) | Microsoft Learn for details.
  • in each iteration you access only the current and the last element of the diffArray - i think you can remove it entirely and replace it with too local variables, one holding the current diff, the other one the previous.
  • try to get rid of the dipHeight array or at least find a way that you can reuse it each frame - for example by using an object pool. i’m not sure how large your arrays are, but objects exceeding a certain size get allocated on the large object heap, which is very bad if it happens every frame because doing a garbage collection on the large object heap always triggers a gen 2 collection, which is very expensive. if you want to know more about these things just google the terms i’ve used here.

regarding the object pool:
if you don’t find a way to get rid of the dipHeight array, i post a little example here how to do it with an object pool:

// This gives you access to the MemoryPool and StreamUtils classes
using VVVV.Utils.Streams;
...
// In your evaluate method
// Calculate a size which is a power of two
// E.g.: dipCount = 3456 -> powerOfTwoSize = 4096
var dipCount = SpreadMax + arcCount;
var powerOfTwoSize = StreamUtils.NextHigher(dipCount );
// Fetch a free array of that size from the pool
var dipArray = MemoryPool<double>.GetArray(powerOfTwoSize);
try
{
  // Here goes your usual code. Just rememeber to use dipCount, not dipArray.Length
}
finally
{
  // Ensures that the array gets put back in the pool - otherwise you'd quickly run out of memory if the code in the try block crashes for some reason
  MemoryPool<double>.PutArray(dipArray);
}

Hi Elias

Thanks for looking through that mess :)

Yes I’m using a high spreadcount for things like the diffArray(around 30000) though not for the outputs (that’s around 450)

‘in each iteration you access only the current and the last element of the diffArray - i think you can remove it entirely and replace it with too local variables, one holding the current diff, the other one the previous.’

Do you mean recalculate these two on every iteration? Will that use more cpu but save memory?

I’ll have a look at memory pools

Is there a better way to make this plugin traditionally ‘spreadable’, and would it result in any performance benefits? I’ve seen somewhere about using <ISpread<ISpread> or something.

and thanks again…

replacing with two variables i meant something like this:

var previous = 0d;
for (...)
{
    var current = FInput[IndexIn+1](IndexIn+1) - FInput[IndexIn](IndexIn);
    ...
    // At the end of the loop
    previous = current;
}