Is there any run-time overhead to readonly?

2019-04-18 18:12发布

For some reason, I've always assumed that readonly fields have overhead associated with them, which I thought of as the CLR keeping track of whether or not a readonly field has been initialized or not. The overhead here would be some extra memory usage to keep track of the state and a check when assigning a value.

Perhaps I assumed this because I didn't know a readonly field could only be initialized inside a constructor or within the field declaration itself and without a run-time check, you wouldn't be able to guarantee it's not being assigned to multiple times in various methods. But now I know this, it could easily be statically checked by the C# compiler, right? So is that the case?

Another reason is that I've read that the usage of readonly has a 'slight' performance impact, but they never went into this claim and I can't find information on this subject, hence my question. I don't know what other performance impact there might be aside from run-time checks.

A third reason is that I saw that readonly is preserved in the compiled IL as initonly, so what is the reason for this information to be in the IL if readonly is nothing more than a guarantee by the C# compiler that the field is never assigned to outside of a constructor or declaration?

On the other hand, I've found out you can set the value of a readonly int through reflection without the CLR throwing an exception, which shouldn't be possible if readonly was a run-time check.

So my guess is: the 'readonlyness' is only a compile time feature, can anyone confirm/deny this? And if it is, what is the reason for this information to be included in the IL?

4条回答
来,给爷笑一个
2楼-- · 2019-04-18 18:38

If you're using a standard, instance variable, readonly will perform nearly identically to a normal variable. The IL added becomes a compile time check, but is pretty much ignored at runtime.

If you're using a static readonly member, things are a bit different...

Since the static readonly member is set during the static constructor, the JIT "knows" that a value exists. There is no extra memory - readonly just prevents other methods from setting this, but that's a compile time check.

SInce the JIT knows this member can never change, it gets "hard-coded" at runtime, so the final effect is just like having a const value. The difference is that it will take longer during the JIT time itself, since the JIT compiler needs to do extra work to hard-wire the readonly's value into place. (This is going to be very fast, though.)

Expert C++/CLI by Marcus Hegee has a reasonably good explanation of this.

查看更多
We Are One
3楼-- · 2019-04-18 18:43

Even if readonly only had effect at compile time, it would still be necessary to store the data in the assembly (i.e. the IL). The CLR is a Common Language Runtime - classes written in one language can both be used and extended by other languages.

Since every compiler for the CLR isn't going to know how to read and compile every other language, in order to preserve the semantics of readonly fields, that data needs to be stored in the assembly so that compilers for other languages will respect it.

Of course, the fact that the field is marked readonly means the JIT can do other things, like optimization (e.g. inline uses of the value), etc. Irrespective of the fact that you used reflection to change the field's value, creating IL which modifies an initonly field outside of the corresponding constructor (instance or static, depending on field type), will result in an unverifiable assembly.

查看更多
ゆ 、 Hurt°
4楼-- · 2019-04-18 18:54

One important point not yet mentioned by any other answers is that when a readonly field is accessed, or when any property is accessed, the request is satisfied using a copy of the data. If the data in question is a value type with more than 4-8 bytes of data, the cost of this extra copying can sometimes be significant. Note that while there is a big jump in cost when structs grow from 16 bytes to 17, structs can be quite a bit larger and still be faster than classes in many applications, if they're not copied too often. For example, if one is supposed to have a type which represents the vertices of a triangle in three-dimensional space. A straightforward implementation would be a struct containing a struct with three float for each point; probably 36 bytes total. If the points, and the coordinates within each point, are mutable public fields, one can access someTriangle.P1.X quickly and easily, without having to copy any data except the Y coordinate of vertex 1. On the other hand, if P1 was a property or a readonly field, the compiler would have to copy P1 to a temporary structure, and then read X from that.

查看更多
虎瘦雄心在
5楼-- · 2019-04-18 18:56

You have to look at it from the same point of view as the access modifiers. The access modifiers exist in IL, but are they really a run-time check? (1) I can't directly assign private fields at compile-time, (2) I can assign them using reflection. So far it seems no run-time check, like readonly.

But let's examine access modifiers. Do the following:

  1. Create Assembly A.dll with public class C
  2. Create an Assembly B.exe that references A.dll. B.exe uses class C.
  3. Build the two assemblies. Running B.exe works just fine.
  4. Rebuild A.dll but set class C to internal. Replace A.dll in B.exe's directory.

Now, running B.exe throws a runtime exception.

Access modifiers exist in IL as well, right? So what's their purpose? The purpose is that other assemblies that reference a .Net assembly need to know what they are allowed to access and what they are not allowed to access, both compile-time AND run-time.

Readonly seems to have a similar purpose in IL. It tells other assemblies whether they can write to a field on a particular type. However, readonly does not seem to have that same run-time check that access modifiers exhibit in my sample above. It seems that readonly is a compile-time check and does not occur in run-time. Take a look at a sample of performance here: Read-only performance vs const.

Again, this doesn't mean the IL is useless. The IL makes sure that a compile-time error occurs in the first place. Remember, when you build you don't build against code, but assemblies.

查看更多
登录 后发表回答