Click here to Skip to main content
Click here to Skip to main content

Coding Tip for Performance Improvement .Net

By , 16 May 2010
 
I always find this thing while doing code reviews and that is a performance issue. The above point I haven’t found anywhere as a performance issue, so I thought to share it.
 
Scenario: There is a function in which one need to extract 10-20 values from an object for example a column value from Dataset. Mostly it was written as:
 
String colValue = dataSet.Tables[0].Rows[0][0]
colValue = dataSet.Tables[0].Rows[0][1]
.
colValue = dataSet.Tables[0].Rows[0][20]
 
So what is wrong with the above code?
Let me ask you a question before I answer this – How does a CLR executes your c# code? Ahhhh… common you will say – the compiler converts it to Intermediate language and then CLR will execute it with JIT. Great you know it all, but do you check IL generated for you code?. (I’m not an exception here ;))
 
Try it yourself – write the above code in an library and open it (dll) with ILDASM and navigate the function/method and double click it to see the IL code generated for that function.
 
All right I’ll suggest to write above code like this
 
DataRow dr = DataSet.Tables[0].Rows[0];
String colValue = dr[0]
colValue = dr[1]
.
.
colValue = dr[20]
 
Check the IL code generated for the following code. I’ve attached the c# code and its IL version:
 
public class ILExample
    {
        public void FillTableValuesA()
        {
            DataSet dataSet = new DataSet();
            dataSet.Tables.Add(new DataTable());
            String colValue = dataSet.Tables[0].Rows[0][1].ToString(); //There are 11 lines of IL for this single line
            colValue = dataSet.Tables[0].Rows[0][1].ToString();   //hence for 3 calls there are 33 lines
            colValue = dataSet.Tables[0].Rows[0][2].ToString();
        }
        public void FillTableValuesB()
        {
            DataSet dataSet = new DataSet();
            dataSet.Tables.Add(new DataTable());
            DataRow dr = dataSet.Tables[0].Rows[0]; //Here are few additional lines but worthy
            String colValue = dr[1].ToString();//There are 4 lines of IL for this single line
            colValue = dr[1].ToString();  //hence for 3 calls there are 12 lines
            colValue = dr[2].ToString();
        }
    }
The IL code for the above functions, I've added proper comments(green) to elaborate it:
.method public hidebysig instance void  FillTableValuesA() cil managed
{
  // Code size       130 (0x82)
  .maxstack  2
  .locals init ([0] class [System.Data]System.Data.DataSet dataSet,
           [1] string colValue)
  IL_0000:  nop
  IL_0001:  newobj     instance void [System.Data]System.Data.DataSet::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance class [System.Data]System.Data.DataTableCollection [System.Data]System.Data.DataSet::get_Tables()
  IL_000d:  newobj     instance void [System.Data]System.Data.DataTable::.ctor()
  IL_0012:  callvirt   instance void [System.Data]System.Data.DataTableCollection::Add(class [System.Data]System.Data.DataTable)
  IL_0017:  nop
//This code is for String colValue = dataSet.Tables[0].Rows[0][1].ToString();
//First it loads DataSet Object
  IL_0018:  ldloc.0
// Call getTables() function to get .Tables collection
  IL_0019:  callvirt   instance class [System.Data]System.Data.DataTableCollection [System.Data]System.Data.DataSet::get_Tables()
  IL_001e:  ldc.i4.0
// Call get_Item() function to get 0th Table from Tables collection
  IL_001f:  callvirt   instance class [System.Data]System.Data.DataTable [System.Data]System.Data.DataTableCollection::get_Item(int32)
// Call get_Rows() function to get .Rows collection from 0th Table
  IL_0024:  callvirt   instance class [System.Data]System.Data.DataRowCollection [System.Data]System.Data.DataTable::get_Rows()
  IL_0029:  ldc.i4.0
// Call get_Item() function to get 0th Row from .Rows collection
  IL_002a:  callvirt   instance class [System.Data]System.Data.DataRow [System.Data]System.Data.DataRowCollection::get_Item(int32)
  IL_002f:  ldc.i4.1
// Call get_Item() function to get 1st column from 0th Row
  IL_0030:  callvirt   instance object [System.Data]System.Data.DataRow::get_Item(int32)
// Convert the column’s value to string
  IL_0035:  callvirt   instance string [mscorlib]System.Object::ToString()
// Store the current value on Stack (i.e. string value of the column) to 1st local variable 
//(i.e. colValue; check .locals init function at the beginning)
  IL_003a:  stloc.1
//Code ends here

//This code is for colValue = dataSet.Tables[0].Rows[0][1].ToString();
  IL_003b:  ldloc.0
  IL_003c:  callvirt   instance class [System.Data]System.Data.DataTableCollection [System.Data]System.Data.DataSet::get_Tables()
  IL_0041:  ldc.i4.0
  IL_0042:  callvirt   instance class [System.Data]System.Data.DataTable [System.Data]System.Data.DataTableCollection::get_Item(int32)
  IL_0047:  callvirt   instance class [System.Data]System.Data.DataRowCollection [System.Data]System.Data.DataTable::get_Rows()
  IL_004c:  ldc.i4.0
  IL_004d:  callvirt   instance class [System.Data]System.Data.DataRow [System.Data]System.Data.DataRowCollection::get_Item(int32)
  IL_0052:  ldc.i4.1
  IL_0053:  callvirt   instance object [System.Data]System.Data.DataRow::get_Item(int32)
  IL_0058:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_005d:  stloc.1
//Code ends here

//This code is for colValue = dataSet.Tables[0].Rows[0][2].ToString();
  IL_005e:  ldloc.0
  IL_005f:  callvirt   instance class [System.Data]System.Data.DataTableCollection [System.Data]System.Data.DataSet::get_Tables()
  IL_0064:  ldc.i4.0
  IL_0065:  callvirt   instance class [System.Data]System.Data.DataTable [System.Data]System.Data.DataTableCollection::get_Item(int32)
  IL_006a:  callvirt   instance class [System.Data]System.Data.DataRowCollection [System.Data]System.Data.DataTable::get_Rows()
  IL_006f:  ldc.i4.0
  IL_0070:  callvirt   instance class [System.Data]System.Data.DataRow [System.Data]System.Data.DataRowCollection::get_Item(int32)
  IL_0075:  ldc.i4.2
  IL_0076:  callvirt   instance object [System.Data]System.Data.DataRow::get_Item(int32)
  IL_007b:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0080:  stloc.1
//Code ends here

  IL_0081:  ret
} // end of method ILExample::FillTableValuesA
//Function ends here
 
//Function FillTableValuesB starts here
.method public hidebysig instance void  FillTableValuesB() cil managed
{
  // Code size       88 (0x58)
  .maxstack  2
  .locals init ([0] class [System.Data]System.Data.DataSet dataSet,
           [1] class [System.Data]System.Data.DataRow dr,
           [2] string colValue)
  IL_0000:  nop
  IL_0001:  newobj     instance void [System.Data]System.Data.DataSet::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance class [System.Data]System.Data.DataTableCollection [System.Data]System.Data.DataSet::get_Tables()
  IL_000d:  newobj     instance void [System.Data]System.Data.DataTable::.ctor()
  IL_0012:  callvirt   instance void [System.Data]System.Data.DataTableCollection::Add(class [System.Data]System.Data.DataTable)
  IL_0017:  nop
  IL_0018:  ldloc.0
  IL_0019:  callvirt   instance class [System.Data]System.Data.DataTableCollection [System.Data]System.Data.DataSet::get_Tables()
  IL_001e:  ldc.i4.0
  IL_001f:  callvirt   instance class [System.Data]System.Data.DataTable [System.Data]System.Data.DataTableCollection::get_Item(int32)
  IL_0024:  callvirt   instance class [System.Data]System.Data.DataRowCollection [System.Data]System.Data.DataTable::get_Rows()
  IL_0029:  ldc.i4.0
  IL_002a:  callvirt   instance class [System.Data]System.Data.DataRow [System.Data]System.Data.DataRowCollection::get_Item(int32)
  IL_002f:  stloc.1
//This code is for String colValue = dr[1].ToString();
// Load first local variable that is dr
  IL_0030:  ldloc.1
  IL_0031:  ldc.i4.1
// Get first column
  IL_0032:  callvirt   instance object [System.Data]System.Data.DataRow::get_Item(int32)
// Convert the column’s value to string
  IL_0037:  callvirt   instance string [mscorlib]System.Object::ToString()
// Store the current value on Stack (i.e. string value of the column) to 2nd local variable 
//(i.e. colValue; check .locals init function at the beginning)
  IL_003c:  stloc.2
//Code ends here

//This code is for colValue = dr[1].ToString();
  IL_003d:  ldloc.1
  IL_003e:  ldc.i4.1
  IL_003f:  callvirt   instance object [System.Data]System.Data.DataRow::get_Item(int32)
  IL_0044:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0049:  stloc.2
//Code ends here

//This code is for colValue = dr[2].ToString();
  IL_004a:  ldloc.1
  IL_004b:  ldc.i4.2
  IL_004c:  callvirt   instance object [System.Data]System.Data.DataRow::get_Item(int32)
  IL_0051:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0056:  stloc.2
//Code ends here

  IL_0057:  ret
} // end of method ILExample::FillTableValuesB
 
The collections make above code more expensive because to get one item it has to search whole collection(with indexes its easy though). And the second function makes the code more legible.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Hemant__Sharma
Technical Lead L & T Infotech
India India
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionCan you add a "real life" example with performance counters?memberOshtri Deka21 May '12 - 21:49 
Your point is very valid, but in order to make it firmer (and clearer), can you improve your tip with adding example(s) with large collection(s) and several performance counters?
Just to be sure how big are the gains, because sometimes some of us just feel lazy to write code "proper" way or are in the hurry (especially when we know that nobody shall check our code).
4 from me.
GeneralI am not sure about the performance yet, because you have in...memberzenwalker198512 Nov '11 - 16:30 
I am not sure about the performance yet, because you have included any information regarding the same.
 
Any ways need not always be a dll for disassembling! Wink | ;)
QuestionUnclearmemberPIEBALDconsult8 Dec '11 - 10:58 
Your point may very well be valid, but it's unclear what your point is.
AnswerRe: UnclearmemberOshtri Deka21 May '12 - 21:44 
I agree.
GeneralInteresting TipmemberJeremy Hutchinson20 May '10 - 10:09 
Did you do any testing on this to see what kind of difference these two options would make on performance? While it's safe to say performing 3 steps to get the value is going to be faster than taking 11 steps, I'd be interested to see how many row and/or columns we'd have to be looking at before it cross into a perceivable difference in performance.
 
That being said I think it's good practice to assign the collection you're working with to a variable and work with that variable, but for me this is mostly a style issue.
GeneralRe: Interesting TipmemberHemant-Sharma-8127 May '10 - 1:08 
Hi Jeremy,
 
Certainly for fewer statements this won't create a problem and with today's faster processors there are less chances in the current scenario.
 
That means for first method (i.e. .cs code) if single statements is taking 100 nanoseconds (ns)then 3 statement will take 0.0003 milliseconds (ms), now for second method if we say one statment takes 10 nanoseconds then 3 statement will take 0.00003 milliseconds so until unless there are 1000.. of statement we wont see any difference even in milliseconds.
 
But do you really think we need any testing to check the performance for the above scenario when we are saying that 3 steps(i.e. .IL code) can do 11 steps' work? Yes I did testing for such a scenario but object was not same. In current scenario the performance will go down on increase of method invocation or users to your application. For huge applications this is really a matter.
 
Do you really think its an style issue? The Dataset example I've used because its very simple and lots of developer use this. Now imagine you have an XML Dom object and you need to extract several nth child's value. In such scenario every time we execute SelectSingleNode function it will parse the whole XML document to search the node, hence for n number of child n times parsing will happen
 
Again suppose I've written 15 statements like:
someVariable = Dataset.Tables[0].Rows[0][colIndex];
 
and according to new requirement you need to change it to 2nd table and 2nd row. Should I update 15 statements? Why to wait for such a scenario to occur.
Hemant Sharma

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 17 May 2010
Article Copyright 2010 by Hemant__Sharma
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid