02/23/2015

Parallel Aggregation using AccoreConsole

In a comment to this blog post, a developer had enquired about the possibility of using AccoreConsole to gather information from multiple drawings without having to launch AccoreConsole for each drawing. AccoreConsole can only work on the drawing passed to it using the "/i" startup switch. This prevents it from working on other drawings. But to considerably speed up the processing, parallel aggregation can be used to launch multiple instances of AccoreConsole. This leverages the multi-core capabilities of the system to perform and easily aggregate the results.

Here are two versions of a code that gathers the entity type of all the entities from drawings. In my Oct-core system, the serial version completed its processing for 5 drawings in about 8 seconds, while the parallel version completed it in 1.9 seconds. The results may vary at your end, but this should provide an idea of the performance enhancement that can be expected.

// Serial version

Dictionary<String, String> entityBreakUp

= new Dictionary<String, String>();

DirectoryInfo di =

new DirectoryInfo(@"D:\Temp\TestDrawings" );

FileInfo[] fiCollection = di.GetFiles("*.dwg" );

DateTime startTime = DateTime.Now;

foreach (FileInfo fi in fiCollection)

{

String consoleOutput = String.Empty;

String entityNames = String.Empty;

using (Process coreprocess = new Process())

{

coreprocess.StartInfo.UseShellExecute

= false ;

coreprocess.StartInfo.CreateNoWindow

= true ;

coreprocess.StartInfo.RedirectStandardOutput

= true ;

coreprocess.StartInfo.FileName =

@"C:\Program Files\Autodesk\

AutoCAD 2015\accoreconsole.exe" ;

coreprocess.StartInfo.Arguments =

string .Format("/i \"{0}\" /s \"{1}\" /l en-US" ,

fi.FullName,

@"C:\Temp\RunCustomNETCmd.scr" );

coreprocess.Start();

// Max wait for 5 seconds

coreprocess.WaitForExit(5000);

StreamReader outputStream

= coreprocess.StandardOutput;

consoleOutput = outputStream.ReadToEnd();

String cleaned =

consoleOutput.Replace("\0" , string .Empty);

int first = cleaned.IndexOf("BreakupBegin" )

+ "BreakupBegin" .Length;

int last = cleaned.IndexOf("BreakupEnd" );

if (first != -1 && last != -1)

entityNames =

cleaned.Substring(first, last - first);

outputStream.Close();

}

entityBreakUp.Add(fi.FullName, entityNames);

}

Console.WriteLine(string .Format(

"*** Serial processing : {0:0.0} seconds ***" ,

DateTime.Now.Subtract(startTime).TotalSeconds));

foreach (KeyValuePair<String, String> kvp in entityBreakUp)

{

Console.WriteLine(String.Format("{0} - {1}" ,

kvp.Key.ToString(), kvp.Value.ToString()));

}

// Parallel version

Dictionary<String, String> entityBreakUp

= new Dictionary<String, String>();

DirectoryInfo di =

new DirectoryInfo(@"D:\Temp\TestDrawings" );

FileInfo[] fiCollection = di.GetFiles("*.dwg" );

entityBreakUp.Clear();

startTime = DateTime.Now;

entityBreakUp = GetEntityBreakUp(fiCollection);

Console.WriteLine(string .Format(

"*** Parallel processing : {0:0.0} seconds ***" ,

DateTime.Now.Subtract(startTime).TotalSeconds));

foreach (KeyValuePair<String, String> kvp

in entityBreakUp)

{

Console.WriteLine(String.Format("{0} - {1}" ,

kvp.Key.ToString(), kvp.Value.ToString()));

}

// Launches multiple instance of AccoreConsole

static Dictionary<String, String>

GetEntityBreakUp(FileInfo[] fiCollection)

{

object lockObject = newobject ();

Dictionary<String, String> entBreakup

= new Dictionary<String, String>();

Parallel.ForEach(

// The values to be aggregated

fiCollection,

// The local initial partial result

() => new Dictionary<String, String>(),

// The loop body

(x, loopState, partialResult) =>

{

// Lauch AccoreConsole and find the

// entity breakup

FileInfo fi = x as FileInfo;

String consoleOutput = String.Empty;

String entityBreakup = String.Empty;

using (Process coreprocess = new Process())

{

coreprocess.StartInfo.UseShellExecute

= false ;

coreprocess.StartInfo.CreateNoWindow

= true ;

coreprocess.StartInfo.RedirectStandardOutput

= true ;

coreprocess.StartInfo.FileName =

@"C:\Program Files\

Autodesk\AutoCAD 2015\accoreconsole.exe" ;

coreprocess.StartInfo.Arguments =

string .Format("/i \"{0}\" /s \"{1}\" /l en-US" ,

fi.FullName,

@"C:\Temp\RunCustomNETCmd.scr" );

coreprocess.Start();

// Max wait for 5 seconds

coreprocess.WaitForExit(5000);

StreamReader outputStream

= coreprocess.StandardOutput;

consoleOutput = outputStream.ReadToEnd();

String cleaned =

consoleOutput.Replace("\0" , string .Empty);

int first = cleaned.IndexOf("BreakupBegin" )

+ "BreakupBegin" .Length;

int last = cleaned.IndexOf("BreakupEnd" );

if (first != -1 && last != -1)

entityBreakup =

cleaned.Substring(first, last - first);

outputStream.Close();

}

Dictionary<String, String> partialDict

= partialResult as Dictionary<String, String>;

partialDict.Add(x.FullName, entityBreakup);

return partialDict;

},

// The final step of each local context

(partialEntBreakup) =>

{

// Enforce serial access to single, shared result

lock (lockObject)

{

Dictionary<String, String> partialDict

= partialEntBreakup as Dictionary<String, String>;

foreach (KeyValuePair<String, String> kvp

in partialDict)

{

entBreakup.Add(kvp.Key, kvp.Value);

}

}

});

return entBreakup;

}

Here is the code from the custom .Net plugin for "EntBreakup" command which lists the entities in a drawing.

Comments

In a comment to this blog post, a developer had enquired about the possibility of using AccoreConsole to gather information from multiple drawings without having to launch AccoreConsole for each drawing. AccoreConsole can only work on the drawing passed to it using the "/i" startup switch. This prevents it from working on other drawings. But to considerably speed up the processing, parallel aggregation can be used to launch multiple instances of AccoreConsole. This leverages the multi-core capabilities of the system to perform and easily aggregate the results.

Here are two versions of a code that gathers the entity type of all the entities from drawings. In my Oct-core system, the serial version completed its processing for 5 drawings in about 8 seconds, while the parallel version completed it in 1.9 seconds. The results may vary at your end, but this should provide an idea of the performance enhancement that can be expected.

// Serial version

Dictionary<String, String> entityBreakUp

= new Dictionary<String, String>();

DirectoryInfo di =

new DirectoryInfo(@"D:\Temp\TestDrawings" );

FileInfo[] fiCollection = di.GetFiles("*.dwg" );

DateTime startTime = DateTime.Now;

foreach (FileInfo fi in fiCollection)

{

String consoleOutput = String.Empty;

String entityNames = String.Empty;

using (Process coreprocess = new Process())

{

coreprocess.StartInfo.UseShellExecute

= false ;

coreprocess.StartInfo.CreateNoWindow

= true ;

coreprocess.StartInfo.RedirectStandardOutput

= true ;

coreprocess.StartInfo.FileName =

@"C:\Program Files\Autodesk\

AutoCAD 2015\accoreconsole.exe" ;

coreprocess.StartInfo.Arguments =

string .Format("/i \"{0}\" /s \"{1}\" /l en-US" ,

fi.FullName,

@"C:\Temp\RunCustomNETCmd.scr" );

coreprocess.Start();

// Max wait for 5 seconds

coreprocess.WaitForExit(5000);

StreamReader outputStream

= coreprocess.StandardOutput;

consoleOutput = outputStream.ReadToEnd();

String cleaned =

consoleOutput.Replace("\0" , string .Empty);

int first = cleaned.IndexOf("BreakupBegin" )

+ "BreakupBegin" .Length;

int last = cleaned.IndexOf("BreakupEnd" );

if (first != -1 && last != -1)

entityNames =

cleaned.Substring(first, last - first);

outputStream.Close();

}

entityBreakUp.Add(fi.FullName, entityNames);

}

Console.WriteLine(string .Format(

"*** Serial processing : {0:0.0} seconds ***" ,

DateTime.Now.Subtract(startTime).TotalSeconds));

foreach (KeyValuePair<String, String> kvp in entityBreakUp)

{

Console.WriteLine(String.Format("{0} - {1}" ,

kvp.Key.ToString(), kvp.Value.ToString()));

}

// Parallel version

Dictionary<String, String> entityBreakUp

= new Dictionary<String, String>();

DirectoryInfo di =

new DirectoryInfo(@"D:\Temp\TestDrawings" );

FileInfo[] fiCollection = di.GetFiles("*.dwg" );

entityBreakUp.Clear();

startTime = DateTime.Now;

entityBreakUp = GetEntityBreakUp(fiCollection);

Console.WriteLine(string .Format(

"*** Parallel processing : {0:0.0} seconds ***" ,

DateTime.Now.Subtract(startTime).TotalSeconds));

foreach (KeyValuePair<String, String> kvp

in entityBreakUp)

{

Console.WriteLine(String.Format("{0} - {1}" ,

kvp.Key.ToString(), kvp.Value.ToString()));

}

// Launches multiple instance of AccoreConsole

static Dictionary<String, String>

GetEntityBreakUp(FileInfo[] fiCollection)

{

object lockObject = newobject ();

Dictionary<String, String> entBreakup

= new Dictionary<String, String>();

Parallel.ForEach(

// The values to be aggregated

fiCollection,

// The local initial partial result

() => new Dictionary<String, String>(),

// The loop body

(x, loopState, partialResult) =>

{

// Lauch AccoreConsole and find the

// entity breakup

FileInfo fi = x as FileInfo;

String consoleOutput = String.Empty;

String entityBreakup = String.Empty;

using (Process coreprocess = new Process())

{

coreprocess.StartInfo.UseShellExecute

= false ;

coreprocess.StartInfo.CreateNoWindow

= true ;

coreprocess.StartInfo.RedirectStandardOutput

= true ;

coreprocess.StartInfo.FileName =

@"C:\Program Files\

Autodesk\AutoCAD 2015\accoreconsole.exe" ;

coreprocess.StartInfo.Arguments =

string .Format("/i \"{0}\" /s \"{1}\" /l en-US" ,

fi.FullName,

@"C:\Temp\RunCustomNETCmd.scr" );

coreprocess.Start();

// Max wait for 5 seconds

coreprocess.WaitForExit(5000);

StreamReader outputStream

= coreprocess.StandardOutput;

consoleOutput = outputStream.ReadToEnd();

String cleaned =

consoleOutput.Replace("\0" , string .Empty);

int first = cleaned.IndexOf("BreakupBegin" )

+ "BreakupBegin" .Length;

int last = cleaned.IndexOf("BreakupEnd" );

if (first != -1 && last != -1)

entityBreakup =

cleaned.Substring(first, last - first);

outputStream.Close();

}

Dictionary<String, String> partialDict

= partialResult as Dictionary<String, String>;

partialDict.Add(x.FullName, entityBreakup);

return partialDict;

},

// The final step of each local context

(partialEntBreakup) =>

{

// Enforce serial access to single, shared result

lock (lockObject)

{

Dictionary<String, String> partialDict

= partialEntBreakup as Dictionary<String, String>;

foreach (KeyValuePair<String, String> kvp

in partialDict)

{

entBreakup.Add(kvp.Key, kvp.Value);

}

}

});

return entBreakup;

}

Here is the code from the custom .Net plugin for "EntBreakup" command which lists the entities in a drawing.