C#: Performance difference between var and explicit data type with example
Introduction
The var data type was introduced in C# 3.0, where var is used to declare implicitly typed local variable, meaning it tells the compiler to figure out the type of the variable at compilation time. Whereas, explicit data types such as int and double among many could be assigned to a variable to improve the readability of the code while not needing to initialize the variable in the declaration. That being said, var data type must be used when a variable is initialized with anonymous type and the properties of the object are accessed at a later point during run time.
However, a question remains whether there is any performance difference during runtime in using var data type instead of explicit data types such as int, double, etc. This article explores the performance difference in using var data type vs explicit data types if there is any.
Experiments and experimental setup
In order to check the performance difference of using var data type and explicit data types, we ran a set of experiments, which are mentioned below.
We wanted to assign a variable num (short for number)** **as var, int and double in 3 separate programs to test the execution time of the programs. The codes for these 3 separate programs are given below.
num assigned as var:
var watch = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
var num = 9999;
}
watch.Stop();
Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
Console.ReadKey();
num assigned as int:
var watch = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
int num = 9999;
}
watch.Stop();
Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
Console.ReadKey();
num assigned as double:
var watch = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
double num = 9999;
}
watch.Stop();
Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
Console.ReadKey();
All 3 programs were built using Visual Studio 2022 on console projects (utilizing .Net CLR 6.0) and then executed as .exe (executable) for 10 times in a row (sequential) to check for the execution time (runtime).
Results
After running the executables for the respective above code snippets the results are as follows.
num assigned as var:
Run 0 | Run 1 | Run 2 | Run 3 | Run 4 | Run 5 | Run 6 | Run 7 | Run 8 | Run 9 |
4 ms | 4 ms | 4 ms | 4 ms | 4 ms | 4 ms | 5 ms | 4 ms | 4 ms | 4 ms |
num assigned as int:
Run 0 | Run 1 | Run 2 | Run 3 | Run 4 | Run 5 | Run 6 | Run 7 | Run 8 | Run 9 |
4 ms | 5 ms | 4 ms | 4 ms | 4 ms | 4 ms | 5 ms | 4 ms | 4 ms | 4 ms |
num assigned as double:
Run 0 | Run 1 | Run 2 | Run 3 | Run 4 | Run 5 | Run 6 | Run 7 | Run 8 | Run 9 |
6 ms | 6 ms | 6 ms | 6 ms | 6 ms | 6 ms | 8 ms | 6 ms | 6 ms | 8 ms |
Immediate conclusion: From the above set of experimental results we could notice that there is barely any difference in performance in execution of num assignment as var or int, however, if the intended data type was supposed to be a double then the execution time in double assignment is different from var.
We also checked for the difference in JIT (Just In Time) assembler code for each of the code snippets as sourced from https://sharplab.io/ to check the differences in the assembler code produced as part of the compilation process from the different code snippets. The JIT assembler codes for the 3 programs as follows.
JIT assembler code for num assigned as var:
01.; Core CLR 6.0.622.26707 on x86
02.
03.C..ctor()
04. L0000: push ebp
05. L0001: mov ebp, esp
06. L0003: push eax
07. L0004: mov [ebp-4], ecx
08. L0007: cmp dword ptr [0x18c3c190], 0
09. L000e: je short L0015
10. L0010: call 0x6b214e50
11. L0015: mov ecx, [ebp-4]
12. L0018: call System.Object..ctor()
13. L001d: nop
14. L001e: nop
15. L001f: mov esp, ebp
16. L0021: pop ebp
17. L0022: ret
18.
19.C.M()
20. L0000: push ebp
21. L0001: mov ebp, esp
22. L0003: sub esp, 0x3c
23. L0006: vxorps xmm4, xmm4, xmm4
24. L000a: vmovdqu [ebp-0x3c], xmm4
25. L000f: vmovdqu [ebp-0x2c], xmm4
26. L0014: vmovdqu [ebp-0x1c], xmm4
27. L0019: xor eax, eax
28. L001b: mov [ebp-0xc], eax
29. L001e: mov [ebp-8], eax
30. L0021: mov [ebp-4], ecx
31. L0024: cmp dword ptr [0x18c3c190], 0
32. L002b: je short L0032
33. L002d: call 0x6b214e50
34. L0032: nop
35. L0033: call System.Diagnostics.Stopwatch.StartNew()
36. L0038: mov [ebp-0x18], eax
37. L003b: mov ecx, [ebp-0x18]
38. L003e: mov [ebp-8], ecx
39. L0041: xor ecx, ecx
40. L0043: mov [ebp-0xc], ecx
41. L0046: nop
42. L0047: jmp short L0059
43. L0049: nop
44. L004a: mov dword ptr [ebp-0x10], 0x270f
45. L0051: nop
46. L0052: mov ecx, [ebp-0xc]
47. L0055: inc ecx
48. L0056: mov [ebp-0xc], ecx
49. L0059: cmp dword ptr [ebp-0xc], 0x989680
50. L0060: setl cl
51. L0063: movzx ecx, cl
52. L0066: mov [ebp-0x14], ecx
53. L0069: cmp dword ptr [ebp-0x14], 0
54. L006d: jne short L0049
55. L006f: mov ecx, [ebp-8]
56. L0072: cmp [ecx], ecx
57. L0074: call System.Diagnostics.Stopwatch.Stop()
58. L0079: nop
59. L007a: mov ecx, [0x8799400]
60. L0080: mov [ebp-0x1c], ecx
61. L0083: mov ecx, [ebp-8]
62. L0086: cmp [ecx], ecx
63. L0088: call System.Diagnostics.Stopwatch.get_ElapsedMilliseconds()
64. L008d: mov [ebp-0x24], eax
65. L0090: mov [ebp-0x20], edx
66. L0093: mov ecx, 0x5ff2cfc
67. L0098: call 0x05d030c0
68. L009d: mov [ebp-0x28], eax
69. L00a0: mov edx, [ebp-0x28]
70. L00a3: add edx, 4
71. L00a6: mov [ebp-0x3c], edx
72. L00a9: mov edx, [ebp-0x3c]
73. L00ac: mov ecx, [ebp-0x24]
74. L00af: mov [edx], ecx
75. L00b1: mov edx, [ebp-0x20]
76. L00b4: mov ecx, [ebp-0x3c]
77. L00b7: mov [ecx+4], edx
78. L00ba: mov edx, [ebp-0x28]
79. L00bd: mov ecx, [ebp-0x1c]
80. L00c0: call System.String.Format(System.String, System.Object)
81. L00c5: mov [ebp-0x2c], eax
82. L00c8: mov ecx, [ebp-0x2c]
83. L00cb: call System.Console.WriteLine(System.String)
84. L00d0: nop
85. L00d1: lea ecx, [ebp-0x38]
86. L00d4: call System.Console.ReadKey()
87. L00d9: nop
88. L00da: nop
89. L00db: mov esp, ebp
90. L00dd: pop ebp
91. L00de: ret
JIT assembler code for num assigned as int:
01.; Core CLR 6.0.622.26707 on x86
02.
03.C..ctor()
04. L0000: push ebp
05. L0001: mov ebp, esp
06. L0003: push eax
07. L0004: mov [ebp-4], ecx
08. L0007: cmp dword ptr [0x18c3c190], 0
09. L000e: je short L0015
10. L0010: call 0x6b214e50
11. L0015: mov ecx, [ebp-4]
12. L0018: call System.Object..ctor()
13. L001d: nop
14. L001e: nop
15. L001f: mov esp, ebp
16. L0021: pop ebp
17. L0022: ret
18.
19.C.M()
20. L0000: push ebp
21. L0001: mov ebp, esp
22. L0003: sub esp, 0x3c
23. L0006: vxorps xmm4, xmm4, xmm4
24. L000a: vmovdqu [ebp-0x3c], xmm4
25. L000f: vmovdqu [ebp-0x2c], xmm4
26. L0014: vmovdqu [ebp-0x1c], xmm4
27. L0019: xor eax, eax
28. L001b: mov [ebp-0xc], eax
29. L001e: mov [ebp-8], eax
30. L0021: mov [ebp-4], ecx
31. L0024: cmp dword ptr [0x18c3c190], 0
32. L002b: je short L0032
33. L002d: call 0x6b214e50
34. L0032: nop
35. L0033: call System.Diagnostics.Stopwatch.StartNew()
36. L0038: mov [ebp-0x18], eax
37. L003b: mov ecx, [ebp-0x18]
38. L003e: mov [ebp-8], ecx
39. L0041: xor ecx, ecx
40. L0043: mov [ebp-0xc], ecx
41. L0046: nop
42. L0047: jmp short L0059
43. L0049: nop
44. L004a: mov dword ptr [ebp-0x10], 0x270f
45. L0051: nop
46. L0052: mov ecx, [ebp-0xc]
47. L0055: inc ecx
48. L0056: mov [ebp-0xc], ecx
49. L0059: cmp dword ptr [ebp-0xc], 0x989680
50. L0060: setl cl
51. L0063: movzx ecx, cl
52. L0066: mov [ebp-0x14], ecx
53. L0069: cmp dword ptr [ebp-0x14], 0
54. L006d: jne short L0049
55. L006f: mov ecx, [ebp-8]
56. L0072: cmp [ecx], ecx
57. L0074: call System.Diagnostics.Stopwatch.Stop()
58. L0079: nop
59. L007a: mov ecx, [0x8799400]
60. L0080: mov [ebp-0x1c], ecx
61. L0083: mov ecx, [ebp-8]
62. L0086: cmp [ecx], ecx
63. L0088: call System.Diagnostics.Stopwatch.get_ElapsedMilliseconds()
64. L008d: mov [ebp-0x24], eax
65. L0090: mov [ebp-0x20], edx
66. L0093: mov ecx, 0x5ff2cfc
67. L0098: call 0x05d030c0
68. L009d: mov [ebp-0x28], eax
69. L00a0: mov edx, [ebp-0x28]
70. L00a3: add edx, 4
71. L00a6: mov [ebp-0x3c], edx
72. L00a9: mov edx, [ebp-0x3c]
73. L00ac: mov ecx, [ebp-0x24]
74. L00af: mov [edx], ecx
75. L00b1: mov edx, [ebp-0x20]
76. L00b4: mov ecx, [ebp-0x3c]
77. L00b7: mov [ecx+4], edx
78. L00ba: mov edx, [ebp-0x28]
79. L00bd: mov ecx, [ebp-0x1c]
80. L00c0: call System.String.Format(System.String, System.Object)
81. L00c5: mov [ebp-0x2c], eax
82. L00c8: mov ecx, [ebp-0x2c]
83. L00cb: call System.Console.WriteLine(System.String)
84. L00d0: nop
85. L00d1: lea ecx, [ebp-0x38]
86. L00d4: call System.Console.ReadKey()
87. L00d9: nop
88. L00da: nop
89. L00db: mov esp, ebp
90. L00dd: pop ebp
91. L00de: ret
JIT assembler code for num assigned as double:
01.; Core CLR 6.0.622.26707 on x86
02.
03.C..ctor()
04. L0000: push ebp
05. L0001: mov ebp, esp
06. L0003: push eax
07. L0004: mov [ebp-4], ecx
08. L0007: cmp dword ptr [0x1afec190], 0
09. L000e: je short L0015
10. L0010: call 0x6b214e50
11. L0015: mov ecx, [ebp-4]
12. L0018: call System.Object..ctor()
13. L001d: nop
14. L001e: nop
15. L001f: mov esp, ebp
16. L0021: pop ebp
17. L0022: ret
18.
19.C.M()
20. L0000: push ebp
21. L0001: mov ebp, esp
22. L0003: sub esp, 0x40
23. L0006: vzeroupper
24. L0009: vxorps xmm4, xmm4, xmm4
25. L000d: vmovdqu [ebp-0x40], xmm4
26. L0012: vmovdqu [ebp-0x30], xmm4
27. L0017: vmovdqu [ebp-0x20], xmm4
28. L001c: xor eax, eax
29. L001e: mov [ebp-0x10], eax
30. L0021: mov [ebp-0xc], eax
31. L0024: mov [ebp-8], eax
32. L0027: mov [ebp-4], ecx
33. L002a: cmp dword ptr [0x1afec190], 0
34. L0031: je short L0038
35. L0033: call 0x6b214e50
36. L0038: nop
37. L0039: call System.Diagnostics.Stopwatch.StartNew()
38. L003e: mov [ebp-0x1c], eax
39. L0041: mov ecx, [ebp-0x1c]
40. L0044: mov [ebp-8], ecx
41. L0047: xor ecx, ecx
42. L0049: mov [ebp-0xc], ecx
43. L004c: nop
44. L004d: jmp short L0065
45. L004f: nop
46. L0050: vmovsd xmm0, [C.M()]
47. L0058: vmovsd [ebp-0x14], xmm0
48. L005d: nop
49. L005e: mov ecx, [ebp-0xc]
50. L0061: inc ecx
51. L0062: mov [ebp-0xc], ecx
52. L0065: cmp dword ptr [ebp-0xc], 0x989680
53. L006c: setl cl
54. L006f: movzx ecx, cl
55. L0072: mov [ebp-0x18], ecx
56. L0075: cmp dword ptr [ebp-0x18], 0
57. L0079: jne short L004f
58. L007b: mov ecx, [ebp-8]
59. L007e: cmp [ecx], ecx
60. L0080: call System.Diagnostics.Stopwatch.Stop()
61. L0085: nop
62. L0086: mov ecx, [0x8799400]
63. L008c: mov [ebp-0x20], ecx
64. L008f: mov ecx, [ebp-8]
65. L0092: cmp [ecx], ecx
66. L0094: call System.Diagnostics.Stopwatch.get_ElapsedMilliseconds()
67. L0099: mov [ebp-0x28], eax
68. L009c: mov [ebp-0x24], edx
69. L009f: mov ecx, 0x5ff2cfc
70. L00a4: call 0x05d030c0
71. L00a9: mov [ebp-0x2c], eax
72. L00ac: mov edx, [ebp-0x2c]
73. L00af: add edx, 4
74. L00b2: mov [ebp-0x40], edx
75. L00b5: mov edx, [ebp-0x40]
76. L00b8: mov ecx, [ebp-0x28]
77. L00bb: mov [edx], ecx
78. L00bd: mov edx, [ebp-0x24]
79. L00c0: mov ecx, [ebp-0x40]
80. L00c3: mov [ecx+4], edx
81. L00c6: mov edx, [ebp-0x2c]
82. L00c9: mov ecx, [ebp-0x20]
83. L00cc: call System.String.Format(System.String, System.Object)
84. L00d1: mov [ebp-0x30], eax
85. L00d4: mov ecx, [ebp-0x30]
86. L00d7: call System.Console.WriteLine(System.String)
87. L00dc: nop
88. L00dd: lea ecx, [ebp-0x3c]
89. L00e0: call System.Console.ReadKey()
90. L00e5: nop
91. L00e6: nop
92. L00e7: mov esp, ebp
93. L00e9: pop ebp
94. L00ea: ret
When you look at the JIT assembler code for num assigned as var and num assigned as int, you would notice that there is not difference in the JIT assembler code produced for the two. However, when you compare the JIT assembler code produced for num assigned as var and num assigned as double, you could notice that the code varies from line 22 to line 94 in the JIT assembler for the code snippet for num assigned as double.
Discussion
In the loop, inside each code snippet you could notice that we have used a loop of assignment for 10 million times. This was chosen by design such that the total execution time could be significant enough to measure. Usually assignments of variables happen in fraction of nano seconds depending on the computing system in which the program is executed. In embedded systems, assignments of variables (var, int, double, etc.) might take a bit longer than general purpose computers as the processing elements (CPUs) could be slower clocked.
Another significant observation from the JIT assembler codes of the 3 different types of assignment statements is that if the programmer doesn't intend for a variable to be assigned a particular type then it is better to assign explicit types as the assigned variable type might not be correct as intended via var data type. For example, a programmer might intend to assign a double of a number as 9999 (or something similar) but if the programmer forgets to initialize 9999.0 in the var assignment then the compiler might deduce that number assignment to be an int (integer data type) rather than a double data type, thus, changing the whole meaning of the program where the variable is used and also affecting the execution time as well.
Conclusion
From the above set of experiments, there is no (significant) difference in using var data type and explicit data types during runtime (execution), however, if the intention of the programmer to assign a different data type on the go (runtime) then it might be better to use explicit data types to be sure that the right intended data type is assigned and used throughout the program.
Acknowledgement
These sets of experiments and performance evaluation was intrigued by my brilliant Computer Science student at the University of Essex and ex-colleague, Filip Vleck.