Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a real-life example of detrimental code explosion caused by field initializations?

Tags:

c#

.net

In CLR via C#, Richter notes that initializing fields in a class declaration, like so

class C {
  int x = 3;
  int y = 4;

  public C() { ... }
  public C(int z) { ... }

  ...
}

results in inserting statements at the beginning of each constructor that set the fields to the provided values. As such, the line int x = 3; above will be responsible for two separate initializations -- one in the parameterless constructor and one in the constructor that takes an int argument.

Richter goes on to say:

This means that you should be aware of code explosion [...] If you have several initialized instance fields and a lot of overloaded constructor methods, you should consider defining the fields without the initialization, creating a single constructor that performs the common initialization, and having each constructor explicitly call the common initialization constructor. This approach will reduce the size of the generated code.

I'm having trouble envisioning a scenario in which this would become a noticeable issue, which makes me wonder if I'm missing something here. For instance, if we imagine that our class has ten constructors and a hundred fields and it takes, say, sixteen bytes of native machine code to perform an initialization then we're talking about a total of 16 kB of generated native code. Surely that's a negligible amount of memory on any computer from this century, right?

I imagine using generics could multiply that by a small factor, but still the impact on the working set seems quite small.

Question: Am I missing something here, and, if so, what?

While my question is mainly theoretical -- I want to test my own understanding -- it's also a bit practical, as initializing the fields where they're declared seems to produce substantially more readable code than using a centralized constructor like Richter suggests.

like image 508
Daniel McLaury Avatar asked Feb 01 '17 18:02

Daniel McLaury


2 Answers

What is a real-life example of detrimental code explosion caused by field initializations?

I'm not aware of any real-world example of that problem.

I'm having trouble envisioning a scenario in which this would become a noticeable issue

Me too.

which makes me wonder if I'm missing something here.

Not to my knowledge.

Surely that's a negligible amount of memory on any computer from this century, right?

Right. And also don't forget that you pay for play in a jitted world. You only get as many ctors generated as you call.

I imagine using generics could multiply that by a small factor

That's true in general for generic types that are constructed with value types.

but still the impact on the working set seems quite small.

Again, working set is pay for play. Code that only gets called a few times gets paged out eventually, and lots of constructors are only called a few times in a program.

Am I missing something here, and, if so, what?

Not to my knowledge.

initializing the fields where they're declared seems to produce substantially more readable code than using a centralized constructor like Richter suggests.

I would optimize for correctness and elegance first, and only work around this issue if an empirical performance test clearly showed that it was a real, user-impacting problem.

like image 199
Eric Lippert Avatar answered Oct 19 '22 13:10

Eric Lippert


Firstly I'd like to point out that parts of this answer are taken from college back in '99-01 (which dealt with procedural code rather than OOP) and bits I've picked up over time not neccesarily all to do with c# so I'm not sure of exact numbers in relation to c# thus any number starting with ~ may not be accurate (in the case of 16b for constructors I would assume it's actually 12b + 1b per param though it might not be the case for c#), however for the sake of demonstration purposes they shouldn't be important.

Usually when creating an object you will know what information is needed and be able to create a blank template or give some values to produce what you want possibly building it up by chaining constructors but what if when creating your instances you don't know all the values or can't chain your constructors? The example below shows a class that will create an instance of an object of a set design based on which values you give it.

class ObjOfSetDesign
{
 int A, B, C, D, E, F;
 int X = 90;
 int Y = 45;
 int Z = 75;

public ObjOfSetDesign(int a)
{
 A = a;
 D = a / 5;
 B = (A + D) * 2;
 C = B * 6;
 E = A * 4;
 F = C / 2;
}

public ObjOfSetDesign(int b, int c)
{
 B = b;
 C = c;
 D = b / 12;
 A = D * 5;
 E = A * 4;
 F = c / 2;
}

public ObjOfSetDesign (int d, int e, int f)
{
 D = d;
 E = e;
 F = f;
 A = d * 5;
 C = f * 2;
 B = c / 6;
}

Obviously this isn't the best example, you could calculate A and use that constructor each time but if your class has 100 fields or some of the variables use XYZ to calculate their value then you might not have that luxury. However, none of the constructors have anything in common except that the values can be used to calculate the others, in the case of the second constructor you can't even use b and c to call :this(a) either.

Couple this with the fact that not all of those fields may just be ints, you may have lists or be passing an instance of a custom class into the constructor to create your object and I hope this helps you see how a class could have 100 fields with 10 independant constructors, and each one would have XYZ injected at the start of the constructor.

As for how this could be significant you come to 16kb in your example, this would cover the code to create a template but AFAIK would also have to hold the data itself. If half of your fields are initialised as ints that's an extra 100b though if you had a list in there you would have ~22b to create the list, 1b for each index plus the data contained within. If your list contains lists the same would apply for each list held too which brings me to this next class to consider.

class BiggerObj
{
 ObjOfSetDesign Obj1 = new ObjOfSetDesign(5);
 ObjOfSetDesign Obj2 = new ObjOfSetDesign(10);
 ObjOfSetDesign Obj3 = new ObjOfSetDesign(15);

 ObjOfSetDesign Obj4;
 ObjOfSetDesign Obj5;

 public BiggerObj(int a4, int a5)
 {
  Obj4 = new ObjOfSetDesign(a4);
  Obj5 = new ObjOfSetDesign(a5);
 }

 public BiggerObj(int b4, int c4, int b5, int c5)
 {
  Obj4 = new ObjOfSetDesign(b4, c4);
  Obj5 = new ObjOfSetDesign(b5, c5);
 }
 publ BiggerObj(int d4, int e4, int f4, int d5, int e5, int f5)
 {
  Obj4 = new ObjOfSetDesign(d4, e4, f4);
  Obj5 = new ObjOfSetDesign(d5, e5, f5);
 }
}

In this example our ObjOfSetDesign is now being injected into the beginning of our BiggerObj constructor along with code to create it including our 3 constructors and XYZ now being injected 9 times. Here's how I calculate that to add up.

In ObjOfSetDesign you have:

  • 9 fields = 9b
  • 3 initialised ints = 6b
  • 3 constructors (~16b x 3) = 48b + 18b injected = 66b

Totalling at 81b to create an object holding 18b of data

In BiggerObj you have:

  • 5 fields = 5b
  • 3 initialised ObjOfSetDesign = (75b x 3) 225b
  • 3 constructors (~16b x 3) = 48b + 675b injected = 723b

Totalling at 953b to create an object holding 90b

And this doesn't even include the cost of calling the functions inside each constructor. If in both cases we'd called a common constructor to initialise the preset fields, ObjOfSetDesign would be 73b and BiggerObj would come to 69b, plus the code size of our constructors (I didn't inlcude this as it wouldn't change apart from there being an extra constructor which would balance out against the original method of initialising on declaration) and still hold the same amount of data.

On a larger scale such as your 100 field class, especially if nested like above, this could end up resulting in an object that's inserting >5mb of code to be created when it only actually needs to be at most 50kb.

Hope this helps and if any of this doesn't apply to c# please let me know so I can correct it.

like image 2
Rowie Avatar answered Oct 19 '22 14:10

Rowie