cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Discussions

Solve problems, and share tips and tricks with other JMP users.
Choose Language Hide Translation Bar
tsl
tsl
Level III

VConcat many matrices together

Concatenating matrices in a loop slows down with loop iterations.

For example this pseudocode:

OutputM = [];
for( i=1, i<=N, i++,
  // do some calculations, generate a matrix of results, call it,  ThisM
  OutputM  |/ = ThisM
);

If I do the above, then as N increases, the VConcat line gets slower and slower with every iteration.

Note that each iteration produces a matrix with same number of columns, but variable number of rows.

It's much faster to do this :

OutputList = {}; // will be a list of matrices
for( i=1, i<=N, i++,
  // do some calculations, generate a matrix of results, call it,  ThisM
  insertinto(OutputList, ThisM)
);

Now we want to concat together the list of matrices we've generated.

The question is how ?

//This doesn't work:
VConcat(OutputMList);
// Neither does this :
VConat(Eval List(OutputMList);
// Nor this:
VConcat(Eval list(Eval(OutputMList);
// I know I can concat multiple matrices, for example this works:
VConat(OutputMList[1],OutputMList[2], OutputMList[3]);
// But the desire is to generalize this

  

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
Craige_Hales
Super User

Re: VConcat many matrices together

If you are concatenating 50 or more, you might not want to use a loop to concatenate them one at a time. If you already have a list of mats to concat, you can turn the list into a vconcat function like this:

	Substitute Into( OutputList, {}, Expr( vconcat ) ); // convert {a,b,c} -> vconcat(a,b,c)
	result2 = Eval( OutputList ); // evaluate the expression

The eval can be 10X faster than the loop:

Craige_Hales_0-1746026430357.png

 

 

JSL to make the graph:

dt = New Table( "timecat", New Column( "nmats" ), New Column( "timeloop" ), New Column( "timeeval" ) );
dt << Graph Builder( Size( 534, 456 ), Show Control Panel( 0 ),
	Variables( X( :nmats ), Y( :timeloop ), Y( :timeeval, Position( 1 ) ) ),
	Elements( Points( X, Y( 1 ), Y( 2 ), Legend( 3 ) ) ),
	SendToReport( Dispatch( {}, "timeloop", ScaleBox,
		{Scale( "Log" ), Format( "Engineering SI", 9, 0 ), Min( 10 ), Max( 1e7 ), Inc( 1 ), Minor Ticks( 1 ),
		Add Ref Line( 1e5, "Solid", "Black", "1/10 second", 1 ), Add Ref Line( 1e6, "Solid", "Black", "one second", 1 ),
		Label Row( Show Minor Labels( 0 ) )} ),
		Dispatch( {}, "graph title", TextEditBox, {Set Text( "smaller is better" )} ),
		Dispatch( {}, "X title", TextEditBox, {Set Text( "number of vconcats" )} ),
		Dispatch( {}, "Y title", TextEditBox, {Set Text( "microseconds" )} )
	)
);

For( N = 1, N < 550, N = Ceiling( n * 1.2 ), // build a bunch of test sets of bigger and bigger sizes
	OutputList = {}; // will be a list of matrices
	For( i = 1, i <= N + 1, i++, 
  // do some calculations, generate a matrix of results, call it,  ThisM
		ThisM = J( i, 30, i ); // rows, cols, initial value. 
		Insert Into( OutputList, ThisM );
	);
// loop is slower ... blue in graph
	start = HP Time();
	result1 = OutputList[1];
	For( i = 2, i <= N Items( OutputList ), i += 1,
		result1 |/= OutputList[i]; // this is the vconcat into operator
	);
	t1 = HP Time() - start;
// eval is faster ... red in graph
	start = HP Time();
	Substitute Into( OutputList, {}, Expr( vconcat ) ); // convert {a,b,c} -> vconcat(a,b,c)
	result2 = Eval( OutputList ); // evaluate the expression
	t2 = HP Time() - start;
// capture the timings
	dt << addrows( 1 );
	dt:nmats[N Rows( dt )] = N;
	dt:timeloop[N Rows( dt )] = t1;
	dt:timeeval[N Rows( dt )] = t2;
	If( !All( result1 == result2 ),
		Throw( "bug?" ) // they should be identical
	);
	Wait( .1 ); // watch the graph grow
);

 

 

Craige

View solution in original post

6 REPLIES 6

Re: VConcat many matrices together

I don't have time to check this idea, but it might be faster to concatenate lists and then convert the final list to matrix.

jthi
Super User

Re: VConcat many matrices together

This might contain some tips regarding matrices, Scripting Guide > Efficient Scripts > Use Matrices 

-Jarmo
txnelson
Super User

Re: VConcat many matrices together

This seems to work fast for me

OutputList = {}; // will be a list of matrices
n=50;
for( i=1, i<=N, i++,
  // do some calculations, generate a matrix of results, call it,  ThisM
  ThisM = [1,2,3];
  insertinto(OutputList, ThisM)
);
OutputM = [];
for each({l},OutputList,
OutputM  = OutputM |/  matrix(l);
);
Jim
Craige_Hales
Super User

Re: VConcat many matrices together

If you are concatenating 50 or more, you might not want to use a loop to concatenate them one at a time. If you already have a list of mats to concat, you can turn the list into a vconcat function like this:

	Substitute Into( OutputList, {}, Expr( vconcat ) ); // convert {a,b,c} -> vconcat(a,b,c)
	result2 = Eval( OutputList ); // evaluate the expression

The eval can be 10X faster than the loop:

Craige_Hales_0-1746026430357.png

 

 

JSL to make the graph:

dt = New Table( "timecat", New Column( "nmats" ), New Column( "timeloop" ), New Column( "timeeval" ) );
dt << Graph Builder( Size( 534, 456 ), Show Control Panel( 0 ),
	Variables( X( :nmats ), Y( :timeloop ), Y( :timeeval, Position( 1 ) ) ),
	Elements( Points( X, Y( 1 ), Y( 2 ), Legend( 3 ) ) ),
	SendToReport( Dispatch( {}, "timeloop", ScaleBox,
		{Scale( "Log" ), Format( "Engineering SI", 9, 0 ), Min( 10 ), Max( 1e7 ), Inc( 1 ), Minor Ticks( 1 ),
		Add Ref Line( 1e5, "Solid", "Black", "1/10 second", 1 ), Add Ref Line( 1e6, "Solid", "Black", "one second", 1 ),
		Label Row( Show Minor Labels( 0 ) )} ),
		Dispatch( {}, "graph title", TextEditBox, {Set Text( "smaller is better" )} ),
		Dispatch( {}, "X title", TextEditBox, {Set Text( "number of vconcats" )} ),
		Dispatch( {}, "Y title", TextEditBox, {Set Text( "microseconds" )} )
	)
);

For( N = 1, N < 550, N = Ceiling( n * 1.2 ), // build a bunch of test sets of bigger and bigger sizes
	OutputList = {}; // will be a list of matrices
	For( i = 1, i <= N + 1, i++, 
  // do some calculations, generate a matrix of results, call it,  ThisM
		ThisM = J( i, 30, i ); // rows, cols, initial value. 
		Insert Into( OutputList, ThisM );
	);
// loop is slower ... blue in graph
	start = HP Time();
	result1 = OutputList[1];
	For( i = 2, i <= N Items( OutputList ), i += 1,
		result1 |/= OutputList[i]; // this is the vconcat into operator
	);
	t1 = HP Time() - start;
// eval is faster ... red in graph
	start = HP Time();
	Substitute Into( OutputList, {}, Expr( vconcat ) ); // convert {a,b,c} -> vconcat(a,b,c)
	result2 = Eval( OutputList ); // evaluate the expression
	t2 = HP Time() - start;
// capture the timings
	dt << addrows( 1 );
	dt:nmats[N Rows( dt )] = N;
	dt:timeloop[N Rows( dt )] = t1;
	dt:timeeval[N Rows( dt )] = t2;
	If( !All( result1 == result2 ),
		Throw( "bug?" ) // they should be identical
	);
	Wait( .1 ); // watch the graph grow
);

 

 

Craige
tsl
tsl
Level III

Re: VConcat many matrices together

Thanks ! That is the "magic sauce" I needed.

Agree that loop iteration will slow down for larger numbers, that's a nice demonstration of it.

The 'Substitute Into()' syntax is what I'd been missing

Craige_Hales
Super User

Re: VConcat many matrices together

I reworked the graph to see the O(N) behavior and add a jsl loop that also achieves the O(N) behavior. The red, green, and blue curves now measure time per concatenated row. The red and green curves stabilize around .3µs / row but the blue curve is getting slower and slower per row copied for bigger and bigger problems because the blue loop recopies all of the previously copied data (and reallocates the matrix) for each mat added to the result. The last blue dot is 180µs * 3.8M rows, or 11 minutes. The red and green dots are ~200X faster.

Craige_Hales_0-1746216387055.png

dt = New Table( "timecat", New Column( "nmats" ), New Column( "loop" ), New Column( "eval" ), New Column( "copy" ) );
dt << Graph Builder( Size( 1000, 500 ), Show Control Panel( 0 ), Legend Position( "Inside Left" ),
	Variables( X( :nmats ), Y( :loop ), Y( :eval, Position( 1 ) ), Y( :copy, Position( 1 ) ) ),
	Elements( Points( X, Y( 1 ), Y( 2 ), Y( 3 ), Legend( 3 ) ) ),
	SendToReport( Dispatch( {}, "loop", ScaleBox,
			{Scale( "Log" ), Format( "Best", 6 ), Min( 0.1 ), Max( 100 ), Inc( 1 ), Minor Ticks( 1 ), Add Ref Line( 0.2, "Solid", "Black", ".2", 1 ),
			Add Ref Line( 0.4, "Solid", "Black", ".4", 1 ), Label Row( Show Minor Labels( 0 ) )} ),
		Dispatch( {}, "graph title", TextEditBox, {Set Text( "smaller is better" )} ),
		Dispatch( {}, "X title", TextEditBox, {Set Text( "number of concats" )} ),
		Dispatch( {}, "Y title", TextEditBox, {Set Text( "microseconds / row" )} ) ) );
		
For( N = 1, N < 3000, N = Ceiling( n * 1.2 ), // build a bunch of test sets of bigger and bigger sizes
	result1 = result2 = result3 = .; // clear previous run memory, if any
	OutputList = {}; // will be a list of matrices
	For( i = 1, i <= N + 1, i++, 
  // do some calculations, generate a matrix of results, call it,  ThisM
		ThisM = J( i, 30, i ); // rows, cols, initial value. 30 makes this demo run in a reasonable time.
		Insert Into( OutputList, ThisM );
	);
// copy... fastest, green in graph, O(N) behavior. Use the red eval below because it is simpler?
	start = HP Time();
	// find total rows and pre-allocate
	nr = 0;
	nc = N Cols( OutputList[1] );
	For( i = 1, i <= N Items( OutputList ), i += 1,
		nr += N Rows( OutputList[i] );
		If( nc != N Cols( OutputList[i] ),
			Throw( "bad cols" )
		);
	);
	result3 = J( nr, nc, . ); // allocate the result, exactly one time
	nr = 0;
	For( i = 1, i <= N Items( OutputList ), i += 1,
		result3[nr + 1 :: nr + N Rows( OutputList[i] ), 0] = OutputList[i]; // copy pieces into the pre-allocated result
		nr += N Rows( OutputList[i] ); // position the next piece after this one
	);
	t3 = HP Time() - start;
// loop is slower ... blue in graph. This exhibits terrible O(N^2) behavior--reallocates the result bigger and bigger
	start = HP Time();
	result1 = OutputList[1];
	For( i = 2, i <= N Items( OutputList ), i += 1,
		result1 |/= OutputList[i]; // this is the vconcat into operator. copies all the previous data into a bigger mat, each time.
	);
	t1 = HP Time() - start;
// eval is faster ... red in graph ... last, because OutputList is modified. Easier than the green copy and same O(N) result.
	start = HP Time();
	Substitute Into( OutputList, {}, Expr( vconcat ) ); // convert {a,b,c} -> vconcat(a,b,c)
	result2 = Eval( OutputList ); // evaluate the expression
	t2 = HP Time() - start;

// capture the timings
	dt << addrows( 1 );
	dt:nmats[N Rows( dt )] = N;
	dt:loop[N Rows( dt )] = t1 / N Rows( result1 );
	dt:eval[N Rows( dt )] = t2 / N Rows( result2 );
	dt:copy[N Rows( dt )] = t3 / N Rows( result3 );
	If( !All( result1 == result2 ) | !All( result1 == result3 ) | !(N Rows( result1 ) == N Rows( result2 ) == N Rows( result3 )),
		Throw( "bug?" ) // they should be identical
	);
	Wait( .1 ); // watch the graph grow
);

 

 

 

Craige

Recommended Articles