Go deep into the security and memory allocation of static readonly in C # [original]

Posted by rubbertoad on Sun, 28 Nov 2021 13:33:40 +0100

In depth security and memory allocation of static readonly [original]

Problem origin: in order to develop frame synchronization, when writing the Vector3 class of fixed points, you want to add some static read-only fields to this class like Vector3 of Unity, such as Vector3.up in Unity, etc.

After reading the Unity source code, I found that in addition to the implementation of Unity, there are several different design methods (listed below). This has aroused my curiosity: what is the difference between these methods and which is better? Is there any way to improve performance and reduce unwanted replication under the condition of ensuring security?

So I made a comparison (regardless of JIT optimization) by testing and checking the IL code (the IL code will not be posted):

background

A simplified example is introduced as the background of comparison:

public struct Point
{
    public int x;
    public int y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    } 
    public void Method() { y += 1; }
    
    //This property copies the value of x when called, and then returns the copied value
    public int Change => ++x;
    
    
    //1. How unity is defined
    private static readonly Point unity =new Point(1, 1);
    public static Point Unity => unity;
    
    //2. readonly is removed from the definition of unity
    private static Point staticPro =new Point(1, 1);
    public static Point StaticPro => staticPro;
    
    //3. Use the automatic attribute of static
    public static Point StaticAutoPro => new Point(1, 1);
    
    //4. Remove the attribute and use the static field directly
    public static Point StaticField =new Point(1, 1);
    
    //5. Use the static+readonly field
    public static readonly Point StaticReadonlyField = new Point(1, 1);
}
  • The property get can be regarded as a method. A new value has been copied in get, and then return it.

1. How unity is defined

definition:

//1. How unity is defined
private static readonly Point unity = new Point(1, 1);
public static Point Unity => unity;
//When Unity is called, a copy of the value of the structure will be copied and then passed out

Use resolution (IL):

//This sentence will copy a new structure in get and store it in stack a1. (copied 1 time)
var a1 = Point.Unity;

//This sentence will copy a new structure in get and store it in stack memory, and then copy the value of x in the structure and store it in stack b1 (copied twice)
var b1 = Point.Unity.x;

//This sentence will copy a new structure in get and store it in the stack memory. Then, through the Change attribute of the structure in the stack, copy the value of x and store it in stack c1 (copied twice)
var c1 = Point.Unity.Change;

//This sentence will copy a new structure in get, store it in stack memory, and then call its function. (copied 1 time)
Point.Unity.Method();

Therefore, in the case of Unity, the security is best guaranteed: it is protected by attributes outside the class. As long as it is called, it will be copied out for operation anyway and will not be tampered with. But of course, this will lose performance (although it seems to me that there is almost no, because it is rarely used), but there may be a way to balance security and performance.

In the class, it is readonly, which also has a certain protective effect. In addition to not copying the read field, a copy of the read attribute / method will also be copied (see the implicit copy of the read-only type in the in-depth analysis C# for details).

2. The definition of unity removes readonly, which is an attribute of static

definition:

//2. readonly is removed from the definition of unity
private static Point staticPro = new Point(1, 1);
public static Point StaticPro => staticPro;
//When Unity is called, a copy of the value of the structure will be copied and then passed out

Use resolution (IL):

//In fact, when used outside the class, it is the same as the first one. After all, readonly is removed.
//Only when it is used in a class can it make a difference: modifying it in a class or calling attribute methods may cause its own modification.

Therefore, the security is not as good as the first, and there is no performance improvement when used outside the class. According to my code requirements, it is not as good as the first.

3. Use the automatic attribute of static

definition:

//3. Use the automatic attribute of static
public static Point StaticAutoPro => new Point(1, 1);
//When Unity is called, a new value of the structure will be created and then passed out

Use resolution (IL):

//Create a structure in get and store it in stack a3. (create 1 time)
var a3 = Point.StaticAutoPro;

//Create a structure in get and store it in the stack, and then copy the value of x in the structure and store it in stack b3 (create once and copy once)
var b3 = Point.StaticAutoPro.x;

//Create a structure in get and store it in the stack. Then copy the value of x and store it in stack c3 through the Change attribute of the structure in the stack (create once and copy once)
var c3 = Point.StaticAutoPro.Change;

//Create a structure in get, store it in the stack, and then call its function. (copied 1 time)
Point.StaticAutoPro.Method();

The difference between this and the second is that a new value will be created and passed out, whether outside or inside the class, rather than copying the value.

In fact, this kind of security can be guaranteed, because a new value is created every time (the performance should not be different). The only small difference is that if it is read in the class, it is also necessary to create a new value. This overhead is actually unnecessary, but it will hardly happen when it is read in the class.

4. Remove the attribute and use the static field directly

definition:

//4. Remove the attribute protection and directly use the static field
public static Point StaticField =new Point(1, 1);

Use resolution (IL):

//Copy a structure from the field and store it on the stack a4. (copy once)
var a4 = Point.StaticField;

//The address of the original field will be loaded into the stack memory, and then the value of x in the original field will be copied and stored in the stack b4 (once)
var b4 = Point.StaticField.x;

//The address of the original field will be loaded into the stack memory, and then the value of x will be copied and stored in the stack c4 through the Change attribute of the original field (copy once)
var c4 = Point.StaticField.Change;

//The original field address will be loaded into the stack memory, and then directly called (0 copy)
Point.StaticField.Method();

This kind of performance will be much better, but there is almost no security. You can access and modify it inside and outside the class. Even if I don't modify it in my code, I can't design it like this.

5. Use the static+readonly field

definition:

//5. Use the static+readonly field
public static readonly Point StaticReadonlyField = new Point(1, 1);

Use resolution (IL):

//A new structure is copied and stored on the stack a5. (copied 1 time)
var a5 = Point.StaticReadonlyField;

//The address of the original field will be loaded into the stack memory, and then the value of x in the original field will be copied and stored in the stack b5 (once)
var b5 = Point.StaticReadonlyField.x;

//A new structure will be copied and stored in the stack memory, and then the value of x will be copied and stored in the stack c5 through the Change attribute of the structure in the stack (copied twice)
var c5 = Point.StaticReadonlyField.Change;

//A new structure will be copied and stored in the stack memory, and then its function will be called. (copied 1 time)
Point.StaticReadonlyField.Method();

Although there is no property protection, it has readonly protection: when calling properties / methods outside or inside a class, they will be copied once, but they will not be copied when reading variables. In this way, the performance has been improved and the security has been guaranteed.

In fact, compared with the first (Unity), the only difference is that this one will improve the performance when reading variables. It does not copy, but it cannot modify variables. My requirements (Vector3.up) will not be modified, so I chose this method in the end.

Author: Yu

Topics: C# Unity