I have a method that returns list of services on remote machine. I'm getting the ManagementObjectCollection using ManagementObjectSearcher.Get() and WIN32 query. Then in foreach loop I'm creating instance of my Service class and add it to result List. While initializing new Service I'm getting ManagementObject properties using GetPropertyValue(string). The problem i'm facing is that this process is very slow. I think that GetPropertyValue is slow(I'm using it 7 times per loop). Is there faster way of getting properties from ManagementObject class?
var query = new ObjectQuery("Select Name, DisplayName, ProcessId, Description, State, StartMode, StartName From Win32_Service");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection allServices = searcher.Get();
foreach (ManagementObject p in allServices)
{Service newService = new Service{ Name = p.GetPropertyValue("Name"),etc...} result.Add(newService);}
I have been fighting with this, trying to understand what is so slow.
I wrote a test program, and used Stopwatch to time practically everything.
I have a wql query that returns 3 devices from Win32_PnPSignedDriver.
I used three different methods to retrieve the results from the query.
ManagementObjectCollection queryResults;
ManagementObjectSearcher searcher = new ManagementObjectSearcher();
var myWql = "SELECT * FROM Win32_PnPSignedDriver WHERE ..."
searcher.Scope = new ManagementScope(#"root\CIMV2");
searcher.Query = new WqlObjectQuery(wmiQry);
queryResults = searcher.Get();
searcher.Get() is plenty fast.
I tested three methods of retrieving data from ManagementObjectCollection queryResults.
foreach (ManagementBaseObject device in queryResults) { }
IEnumerator enumerator = queryResults.GetEnumerator(); while (enumerator.MoveNext()) { }
queryResults.CopyTo(deviceArray, 0); foreach (var btDevice in deviceArray)
First round testing:
Method 1: Was very slow -- over 3000 ms to execute an empty loop.
Method 2: Was very fast. 1 ms
Method 3: Also very fast. 0 ms.
Then I saw the fault in my testing. The first loop counted the object in the collection, and then this was remembered by the framework. If I executed Method 1 several times, only the initial loop was slow, but then repeating the foreach was 0 to 1 ms.
I restructured my test to re-execute the query before each fetch of the data.
Method 1: Was slow every time.
Method 2: Also slow every time.
Method 3: My Stopwatch timing reported 0 to 1 ms, but I noticed the execution time was much longer. ???
Looking deeply into what I coded, I saw that I did not time the following line:
ManagementObject[] deviceArray = new ManagementObject[queryResults.Count];
Which is actually two commands:
var count = queryResults.Count;
ManagementObject[] deviceArray = new ManagementObject[count];
I timed each separately, and saw that the queryResults.Count took almost all the time. Often > 3000 ms.
I then hard-coded the size of the array to avoid the call: queryResults.Count
However, when I executed
queryResults.CopyTo(deviceArray, 0);
The CopyTo method still needed to know how many items are in the ManagementObjectCollection, and now the CopyTo took > 3000 ms where it had been 0 or 1 ms before.
So, it would appear that ManagementObjectCollection. get_Count is the bottleneck, and I do not know anyway to retrieve the results without causing the Count getter to be executed.
Related
I am trying to help with a slow legacy application that gets certain data through the ManagementObjectCollection class. This function takes around 40 seconds, however it took a little over a minute when getting the data directly from the ManagementObjectCollection. We pared down the query and selected the data into a list which helped execution time, however it is still very slow considering how many elements are supposed to be here. I have the following code:
string baseStr;
ManagementObjectCollection moReturn;
ManagementObjectSearcher moSearch;
ManagementObjectSearcher moSearch32;
List<string> programs = new List<string>();
List<ProgramDescription> moList;
moSearch32 = new ManagementObjectSearcher("Select Name, Version, InstallDate from Win32_Product");
moReturn = moSearch32.Get();
moList = moReturn.Cast<ManagementObject>().AsEnumerable()
.Select(x => new ProgramDescription
{
Name = x["Name"],
Version = x["Version"],
InstallDate = x["installDate"]
}).ToList();
foreach (ProgramDescription currentMo in moList)
{
if (currentMo.Name == null)
continue;
else
{
baseStr = currentMo.Name.ToString() + ", " + currentMo.Version?.ToString()
+ ", " + currentMo.InstallDate?.ToString();
}
}
As you can see, I am attempting to convert this to a list of another object and iterate through that. However, I have noticed that even when doing this, the foreach loop takes an eternity despite the moList object only having ~290 items. The management object search and converting it to a list seem to happen instantly, but this iteration is still taking forever. Why is this? If this is just a quirk in relation to accessing these management objects, is there a more efficient way of getting this specific query?
After running my query in Google Cloud Platform, the results are of type BigQueryResults. To create an object from this results, I run a foreach loop across the rows of this results and keep adding them to the object as shown below:
var data = new List<DefectData>();
foreach (BigQueryRow row in results)
{
DefectData defectData = new DefectData();
defectData.DefectId = long.Parse(SqlUtility.GetValue(row, "DefectId"));
defectData.TrueSize = float.Parse(SqlUtility.GetValue(row, "TrueSize"));
defectData.WaferId = SqlUtility.GetValue(row, "WaferId");
defectData.DieX = int.Parse(SqlUtility.GetValue(row, "DieX"));
data.Add(defectData);
}
The results may have millions of records. The foreach loop takes too much time to create the object. I tried to use Parallel.foreach as well. That too doesn't help. How do I make this process of creating object to run faster?
So what i'm doind for the moment is getting information from win32 classes and putting that information on my view using a viewmodel -> mvvm. So at this point i aquire information from my disks and put them on my view. Since for example my pc and server have pultiple drive I use a for each loop and add theinformation in a list -> I have tried IList<> and ICollection<>. my current code:
//disk
int index = 0;
ObjectQuery objectQuery3 = new ObjectQuery("select * from Win32_LogicalDisk");
ManagementObjectSearcher searcher3 = new
ManagementObjectSearcher(objectQuery3);
ManagementObjectCollection disks = searcher3.Get();
foreach (ManagementObject value in disks)
{
disk.FreeSpace = (Convert.ToUInt64(value.GetPropertyValue("FreeSpace"))/1024/1024/1024/1024);
disk.Size = (Convert.ToUInt64(value.GetPropertyValue("Size")) /1024/1024/1024/1024);
disk.Name = Convert.ToString(value.GetPropertyValue("Name"));
disk.VolumeName = Convert.ToString(value.GetPropertyValue("VolumeName"));
storage.Insert(index, disk);
index++;
}
My problem here when i put a breakpoint at the list is -> he gets the information from my first disks put's it in 'disk' model aka class. next he add's the model in the IList. next iteration he get information put's it into the model but after that he overwrites current ilist index 0 and then add itself on index 1. next iteration the same he overwrites index 0 overwrites index 1 and adds itself on index 2. leaving me exactly the same information every time from my last disk. Any ideas on how to fix this?
besides insert method I have tried add method too.
I'm looking to get performance information for the disks on my local computer using WMI. According to the documentation for Win32_PerfFormattedData_PerfDisk_LogicalDisk (http://msdn.microsoft.com/en-us/library/aa394261(v=vs.85).aspx), it looks like I should just be able to query the information and get the values.
However, when I run the code I get "0" for each value for the properties.
var selectQuery = new SelectQuery("Select * from Win32_PerfFormattedData_PerfDisk_LogicalDisk");
var searcher = new ManagementObjectSearcher(selectQuery);
foreach (ManagementObject disk in searcher.Get())
{
foreach (PropertyData property in disk.Properties)
{
var propertyValue = property.Value; // 0 value.
}
}
I have also queried Win32_PerfRawData_PerfDisk_LogicalDisk and I do get actual raw values. Of course, I would rather have the formatted data instead of the raw data. The documentation for converting the raw data to the formatted data seems to be lacking when it comes to using WMI.
Any idea why I am getting 0 for each value?
Here is a working example just tested this
You can use the WMI Performance Class Counters. An example of this would be polling the PerfDisk_LogicalDisk
ManagementObjectSearcher mos = new ManagementObjectSearcher("select * from Win32_PerfFormattedData_PerfDisk_LogicalDisk");
foreach (ManagementObject service in mos.Get())
{
foreach (PropertyData propData in service.Properties)
{
Console.WriteLine("{0} {1}", propData.Name, propData.Value);
}
}
Update 2011-05-20 12:49AM: The foreach is still 25% faster than the parallel solution for my application. And don't use the collection count for max parallelism, use somthing closer to the number of cores on your machine.
=
I have an IO bound task that I would like to run in parallel. I want to apply the same operation to every file in a folder. Internally, the operation results in a Dispatcher.Invoke that adds the computed file info to a collection on the UI thread. So, in a sense, the work result is a side effect of the method call, not a value returned directly from the method call.
This is the core loop that I want to run in parallel
foreach (ShellObject sf in sfcoll)
ProcessShellObject(sf, curExeName);
The context for this loop is here:
var curExeName = Path.GetFileName(Assembly.GetEntryAssembly().Location);
using (ShellFileSystemFolder sfcoll = ShellFileSystemFolder.FromFolderPath(_rootPath))
{
//This works, but is not parallel.
foreach (ShellObject sf in sfcoll)
ProcessShellObject(sf, curExeName);
//This doesn't work.
//My attempt at PLINQ. This code never calls method ProcessShellObject.
var query = from sf in sfcoll.AsParallel().WithDegreeOfParallelism(sfcoll.Count())
let p = ProcessShellObject(sf, curExeName)
select p;
}
private String ProcessShellObject(ShellObject sf, string curExeName)
{
String unusedReturnValueName = sf.ParsingName
try
{
DesktopItem di = new DesktopItem(sf);
//Up date DesktopItem stuff
di.PropertyChanged += new PropertyChangedEventHandler(DesktopItem_PropertyChanged);
ControlWindowHelper.MainWindow.Dispatcher.Invoke(
(Action)(() => _desktopItemCollection.Add(di)));
}
catch (Exception ex)
{
}
return unusedReturnValueName ;
}
Thanks for any help!
+tom
EDIT: Regarding the update to your question. I hadn't spotted that the task was IO-bound - and presumably all the files are from a single (traditional?) disk. Yes, that would go slower - because you're introducing contention in a non-parallelizable resource, forcing the disk to seek all over the place.
IO-bound tasks can still be parallelized effectively sometimes - but it depends on whether the resource itself is parallelizable. For example, an SSD (which has much smaller seek times) may completely change the characteristics you're seeing - or if you're fetching over the network from several individually-slow servers, you could be IO-bound but not on a single channel.
You've created a query, but never used it. The simplest way of forcing everything to be used with the query would be to use Count() or ToList(), or something similar. However, a better approach would be to use Parallel.ForEach:
var options = new ParallelOptions { MaxDegreeOfParallelism = sfcoll.Count() };
Parallel.ForEach(sfcoll, options, sf => ProcessShellObject(sf, curExeName));
I'm not sure that setting the max degree of parallelism like that is the right approach though. It may work, but I'm not sure. A different way of approaching this would be to start all the operations as tasks, specifying TaskCreationOptions.LongRunning.
Your query object created via LINQ is an IEnumerable. It gets evaluated only if you enumerate it (eg. via foreach loop):
var query = from sf in sfcoll.AsParallel().WithDegreeOfParallelism(sfcoll.Count())
let p = ProcessShellObject(sf, curExeName)
select p;
foreach(var q in query)
{
// ....
}
// or:
var results = query.ToArray(); // also enumerates query
Should you add a line in the end
var results = query.ToList();