Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove unnecessary ref modifier on ParseResult<> #96

Merged
merged 2 commits into from
Jul 11, 2024

Conversation

atifaziz
Copy link
Contributor

This PR removes the ref modifier on ParseResult<> that seems to be unused and unnecessarily could post restrictions on usage. Its removal does not seem to have any bearing on the compiled code:

--- .\before.il
+++ .\after.il
@@ -1678,17 +1678,6 @@
 .class public sequential ansi sealed beforefieldinit Parlot.ParseResult`1<T>
 	extends [netstandard]System.ValueType
 {
-	.custom instance void [netstandard]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
-		01 00 00 00
-	)
-	.custom instance void [netstandard]System.ObsoleteAttribute::.ctor(string, bool) = (
-		01 00 52 54 79 70 65 73 20 77 69 74 68 20 65 6d
-		62 65 64 64 65 64 20 72 65 66 65 72 65 6e 63 65
-		73 20 61 72 65 20 6e 6f 74 20 73 75 70 70 6f 72
-		74 65 64 20 69 6e 20 74 68 69 73 20 76 65 72 73
-		69 6f 6e 20 6f 66 20 79 6f 75 72 20 63 6f 6d 70
-		69 6c 65 72 2e 01 00 00
-	)
 	// Fields
 	.field public int32 Start
 	.field public int32 End
@@ -58420,8 +58409,8 @@
 
 
 	// Fields
-	.field assembly static initonly valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=256' '21244F82B210125632917591768F6BF22EB6861F80C6C25A25BD26DFB580EA7B' at I_00035F48
-	.data cil I_00035F48 = bytearray (
+	.field assembly static initonly valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=256' '21244F82B210125632917591768F6BF22EB6861F80C6C25A25BD26DFB580EA7B' at I_00035E98
+	.data cil I_00035E98 = bytearray (
 		ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
 		ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
 		ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
@@ -58439,8 +58428,8 @@
 		ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
 		ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
 	)
-	.field assembly static initonly valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=65535' '6413BECF20AD55EDE9309EA4BBE5B6097EE9EF531DF5A5096BD52D6DA032745C' at I_00036048
-	.data cil I_00036048 = bytearray (
+	.field assembly static initonly valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=65535' '6413BECF20AD55EDE9309EA4BBE5B6097EE9EF531DF5A5096BD52D6DA032745C' at I_00035F98
+	.data cil I_00035F98 = bytearray (
 		00 00 00 00 00 00 00 00 00 0c 08 08 00 08 00 00
 		00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 		0c 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00

The above delta was produced as follows (commit f1586c8 below was the head of main at time of opening this PR):

dotnet new tool-manifest
dotnet tool install ilspycmd --version 8.2.0.7535
git checkout f1586c898402f7056152a8012803db470ceee849
dotnet build -c Release src/Parlot
dotnet ilspycmd -il src/Parlot/bin/Release/netstandard2.1\Parlot.dll > before.il
git checkout ParseResult-x-ref
dotnet build -c Release src/Parlot
dotnet ilspycmd -il src/Parlot/bin/Release/netstandard2.1\Parlot.dll > after.il
diff -u before.il after.il

@atifaziz
Copy link
Contributor Author

atifaziz commented May 6, 2024

Can I help with any hesitation to merge this? Seems rather straightforward unless I'm missing some JIT intrinsics/tricks that this is relying on and which are not obvious from the lack of IL changes.

@lahma
Copy link
Collaborator

lahma commented May 7, 2024

I think the general rule is that if the struct is more than int pointer size it might make a difference. On x64 this struct is borderline so if no fields are anticipated to be added, might not need the strict requirement of being passed as ref.

Not sure what the current restrictions are though?

@sebastienros
Copy link
Owner

I ran the benchmarks and the changes are small, but much more on a single Parser call (SkipWhiteSpace1/10):

Before

| Method                         | Mean         | Error          | StdDev       | Gen0    | Gen1   | Allocated |
|------------------------------- |-------------:|---------------:|-------------:|--------:|-------:|----------:|
| CreateCompiledSmallParser      | 38,663.27 ns | 111,132.125 ns | 6,091.527 ns |  3.4180 | 3.1738 |   33289 B |
| CreateCompiledExpressionParser |  6,117.24 ns |   1,458.822 ns |    79.963 ns |  0.5188 | 0.4883 |    5024 B |
|                                |              |                |              |         |        |           |
| CursorMatchHello               |     20.78 ns |       8.627 ns |     0.473 ns |  0.0136 |      - |     128 B |
| CursorMatchGoodbye             |     20.18 ns |       1.549 ns |     0.085 ns |  0.0136 |      - |     128 B |
| CursorMatchNone                |     18.66 ns |      22.188 ns |     1.216 ns |  0.0136 |      - |     128 B |
|                                |              |                |              |         |        |           |
| DecodeStringWithoutEscapes     |     11.09 ns |       0.640 ns |     0.035 ns |       - |      - |         - |
| DecodeStringWithEscapes        |     38.97 ns |       2.381 ns |     0.131 ns |  0.0127 | 0.0001 |     120 B |
|                                |              |                |              |         |        |           |
| ExpressionRawBig               |    839.66 ns |     140.574 ns |     7.705 ns |  0.1268 |      - |    1200 B |
| ExpressionCompiledBig          |  1,502.99 ns |     446.331 ns |    24.465 ns |  0.3052 |      - |    2888 B |
| ExpressionFluentBig            |  1,901.86 ns |     818.213 ns |    44.849 ns |  0.3052 |      - |    2888 B |
|                                |              |                |              |         |        |           |
| ExpressionRawSmall             |    164.20 ns |      44.671 ns |     2.449 ns |  0.0322 |      - |     304 B |
| ExpressionCompiledSmall        |    290.48 ns |     124.043 ns |     6.799 ns |  0.0696 |      - |     656 B |
| ExpressionFluentSmall          |    364.68 ns |     115.644 ns |     6.339 ns |  0.0696 |      - |     656 B |
|                                |              |                |              |         |        |           |
| BigJson                        | 51,068.12 ns |  23,695.190 ns | 1,298.813 ns |  9.9487 | 1.3428 |   93992 B |
| BigJsonCompiled                | 47,148.08 ns |   2,966.961 ns |   162.629 ns |  9.9487 | 1.7700 |   93992 B |
|                                |              |                |              |         |        |           |
| DeepJson                       | 34,704.36 ns |   7,603.367 ns |   416.766 ns | 10.6812 | 1.1597 |  100688 B |
| DeepJsonCompiled               | 28,255.66 ns |   3,254.650 ns |   178.398 ns | 10.6812 | 2.0142 |  100688 B |
|                                |              |                |              |         |        |           |
| LongJson                       | 45,188.37 ns |   7,572.066 ns |   415.050 ns | 13.0615 | 2.6245 |  123224 B |
| LongJsonCompiled               | 39,278.41 ns |  10,857.236 ns |   595.122 ns | 13.0615 | 2.6245 |  123224 B |
|                                |              |                |              |         |        |           |
| WideJson                       | 21,991.07 ns |   5,252.902 ns |   287.929 ns |  4.3945 | 0.3967 |   41536 B |
| WideJsonCompiled               | 20,914.31 ns |   6,822.441 ns |   373.961 ns |  4.3945 | 0.4883 |   41536 B |
|                                |              |                |              |         |        |           |
| Lookup                         |     22.37 ns |       4.576 ns |     0.251 ns |  0.0136 |      - |     128 B |
|                                |              |                |              |         |        |           |
| SkipWhiteSpace_1               |     15.85 ns |       2.030 ns |     0.111 ns |  0.0136 |      - |     128 B |
| SkipWhiteSpace_10              |     21.37 ns |       7.068 ns |     0.387 ns |  0.0136 |      - |     128 B |

After

| Method                         | Mean         | Error         | StdDev       | Gen0    | Gen1   | Allocated |
|------------------------------- |-------------:|--------------:|-------------:|--------:|-------:|----------:|
| CreateCompiledSmallParser      | 33,481.15 ns | 34,989.242 ns | 1,917.878 ns |  3.4180 | 3.1738 |   33289 B |
| CreateCompiledExpressionParser |  6,276.06 ns |  6,440.387 ns |   353.019 ns |  0.5188 | 0.4883 |    5024 B |
|                                |              |               |              |         |        |           |
| CursorMatchHello               |     20.23 ns |      0.479 ns |     0.026 ns |  0.0136 |      - |     128 B |
| CursorMatchGoodbye             |     23.51 ns |     46.454 ns |     2.546 ns |  0.0136 |      - |     128 B |
| CursorMatchNone                |     17.33 ns |      1.315 ns |     0.072 ns |  0.0136 |      - |     128 B |
|                                |              |               |              |         |        |           |
| DecodeStringWithoutEscapes     |     11.14 ns |      1.654 ns |     0.091 ns |       - |      - |         - |
| DecodeStringWithEscapes        |     40.39 ns |     31.777 ns |     1.742 ns |  0.0127 | 0.0001 |     120 B |
|                                |              |               |              |         |        |           |
| ExpressionRawBig               |    830.66 ns |     66.513 ns |     3.646 ns |  0.1268 |      - |    1200 B |
| ExpressionCompiledBig          |  1,548.18 ns |    447.288 ns |    24.517 ns |  0.3052 |      - |    2888 B |
| ExpressionFluentBig            |  2,021.56 ns |    424.065 ns |    23.244 ns |  0.3052 |      - |    2888 B |
|                                |              |               |              |         |        |           |
| ExpressionRawSmall             |    168.86 ns |     25.437 ns |     1.394 ns |  0.0322 |      - |     304 B |
| ExpressionCompiledSmall        |    295.55 ns |     62.882 ns |     3.447 ns |  0.0696 |      - |     656 B |
| ExpressionFluentSmall          |    360.47 ns |     69.248 ns |     3.796 ns |  0.0696 |      - |     656 B |
|                                |              |               |              |         |        |           |
| BigJson                        | 51,121.76 ns |  5,311.321 ns |   291.131 ns |  9.9487 | 1.3428 |   93992 B |
| BigJsonCompiled                | 48,300.99 ns |  9,526.195 ns |   522.163 ns |  9.9487 | 1.7700 |   93992 B |
|                                |              |               |              |         |        |           |
| DeepJson                       | 33,876.91 ns |    806.898 ns |    44.229 ns | 10.6812 | 1.1597 |  100688 B |
| DeepJsonCompiled               | 28,366.68 ns |  1,299.791 ns |    71.246 ns | 10.6812 | 2.0142 |  100688 B |
|                                |              |               |              |         |        |           |
| LongJson                       | 43,190.38 ns |  4,418.623 ns |   242.200 ns | 13.0615 | 2.6245 |  123224 B |
| LongJsonCompiled               | 40,101.58 ns | 13,241.494 ns |   725.811 ns | 13.0615 | 2.6245 |  123224 B |
|                                |              |               |              |         |        |           |
| WideJson                       | 22,854.24 ns | 10,767.236 ns |   590.189 ns |  4.3945 | 0.3967 |   41536 B |
| WideJsonCompiled               | 22,378.38 ns |  5,584.930 ns |   306.129 ns |  4.3945 | 0.4883 |   41536 B |
|                                |              |               |              |         |        |           |
| Lookup                         |     29.76 ns |     32.111 ns |     1.760 ns |  0.0136 |      - |     128 B |
|                                |              |               |              |         |        |           |
| SkipWhiteSpace_1               |     24.28 ns |     39.807 ns |     2.182 ns |  0.0136 |      - |     128 B |
| SkipWhiteSpace_10              |     27.76 ns |     39.446 ns |     2.162 ns |  0.0136 |      - |     128 B |

@atifaziz
Copy link
Contributor Author

atifaziz commented Jun 4, 2024

I think the general rule is that if the struct is more than int pointer size it might make a difference. On x64 this struct is borderline so if no fields are anticipated to be added, might not need the strict requirement of being passed as ref.

Not sure what the current restrictions are though?

@lahma I'm not sure I follow your reasoning here. ParseResult<> is generally passed by reference via Parser<>.Parse:

public abstract bool Parse(ParseContext context, ref ParseResult<T> result);

Making ParseResult<> a ref struct should therefore have no impact, but will add restrictions for non-obvious reasons.

For example, for the following:

static class C
{
    public static bool M(ref ParseResult<string> r)
    {
        r.Set(12, 34, "Hello");
        return true;
    }

    public static bool M(ref ParseResult<int> r)
    {
        r.Set(12, 34, 56);
        return true;
    }
}

public /* ref */ struct ParseResult<T>
{
    public void Set(int start, int end, T value)
    {
        Start = start;
        End = end;
        Value = value;
    }

    public int Start;
    public int End;
    public T Value;
}

The compiled native code will be identical whether the ParseResult<> is defined as a regular value struct or a ref struct:

; Core CLR 8.0.424.16909 on x86

C.M(ParseResult`1<System.String> ByRef)
    L0000: mov dword ptr [ecx+4], 0xc
    L0007: mov dword ptr [ecx+8], 0x22
    L000e: mov eax, [0x8f83360]
    L0013: mov edx, ecx
    L0015: call 0x72138004
    L001a: mov eax, 1
    L001f: ret

C.M(ParseResult`1<Int32> ByRef)
    L0000: mov dword ptr [ecx], 0xc
    L0006: mov dword ptr [ecx+4], 0x22
    L000d: mov dword ptr [ecx+8], 0x38
    L0014: mov eax, 1
    L0019: ret

ParseResult`1.Set(...)
    ; Open generics cannot be JIT-compiled.
    ; However you can use attribute SharpLab.Runtime.JitGeneric to specify argument types.
    ; Example: [JitGeneric(typeof(int)), JitGeneric(typeof(string))] void M<T>() { ... }.

And along the same lines for x64:

C.M (ParseResult`1&)
L0000	mov	dword ptr [rcx+8], 0xc
L0007	mov	dword ptr [rcx+0xc], 0x22
L000e	mov	rdx, 0x198318258f0
L0018	mov	rdx, [rdx]
L001b	call	0x00007fffb9acbf40
L0020	mov	eax, 1
L0025	ret	
C.M (ParseResult`1&)
L0000	mov	dword ptr [rcx], 0xc
L0006	mov	dword ptr [rcx+4], 0x22
L000d	mov	dword ptr [rcx+8], 0x38
L0014	mov	eax, 1
L0019	ret	

but much more on a single Parser call (SkipWhiteSpace1/10):

@sebastienros I'm seeing somewhat different results. Before this PR:

BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3593/23H2/2023Update/SunValley3)
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.301
  [Host]   : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
  ShortRun : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  
Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
BigJson_ParlotCompiled 137.38 μs 258.04 μs 14.144 μs 1.00 0.00 22.4609 0.2441 91.79 KB 1.00
BigJson_Parlot 132.97 μs 36.73 μs 2.013 μs 0.97 0.10 22.4609 0.2441 91.79 KB 1.00
BigJson_Pidgin 280.94 μs 297.52 μs 16.308 μs 2.07 0.35 21.9727 0.9766 91.7 KB 1.00
BigJson_Newtonsoft 252.79 μs 133.28 μs 7.306 μs 1.85 0.20 48.0957 11.9629 203.1 KB 2.21
BigJson_Sprache 2,872.92 μs 2,696.65 μs 147.812 μs 21.03 1.88 1289.0625 70.3125 5271.8 KB 57.43
BigJson_Superpower 1,922.72 μs 3,379.93 μs 185.265 μs 14.04 1.16 218.7500 23.4375 905.93 KB 9.87
DeepJson_ParlotCompiled 84.69 μs 87.62 μs 4.803 μs 1.00 0.00 23.9258 - 98.33 KB 1.00
DeepJson_Parlot 103.49 μs 108.17 μs 5.929 μs 1.23 0.13 24.0479 - 98.33 KB 1.00
DeepJson_Pidgin 398.62 μs 362.65 μs 19.878 μs 4.71 0.17 41.0156 4.3945 168.79 KB 1.72
DeepJson_Newtonsoft 144.46 μs 164.00 μs 8.989 μs 1.71 0.19 40.5273 11.4746 179.13 KB 1.82
DeepJson_Sprache 3,551.62 μs 5,504.82 μs 301.738 μs 41.99 3.84 593.7500 335.9375 2914.45 KB 29.64
LongJson_ParlotCompiled 118.97 μs 172.73 μs 9.468 μs 1.00 0.00 29.2969 0.7324 120.34 KB 1.00
LongJson_Parlot 132.39 μs 83.19 μs 4.560 μs 1.12 0.07 29.2969 - 120.34 KB 1.00
LongJson_Pidgin 298.04 μs 166.34 μs 9.118 μs 2.52 0.24 29.2969 - 120.25 KB 1.00
LongJson_Newtonsoft 199.30 μs 672.60 μs 36.868 μs 1.67 0.17 49.0723 14.4043 202.68 KB 1.68
LongJson_Sprache 2,652.07 μs 6,044.98 μs 331.346 μs 22.40 3.59 1042.9688 3.9063 4261.31 KB 35.41
LongJson_Superpower 2,009.64 μs 3,095.04 μs 169.650 μs 16.89 0.11 177.7344 - 726.79 KB 6.04
WideJson_ParlotCompiled 63.11 μs 133.60 μs 7.323 μs 1.00 0.00 9.8877 - 40.56 KB 1.00
WideJson_Parlot 66.19 μs 172.27 μs 9.443 μs 1.05 0.05 9.8877 - 40.56 KB 1.00
WideJson_Pidgin 179.04 μs 575.69 μs 31.556 μs 2.89 0.75 9.7656 0.4883 40.48 KB 1.00
WideJson_Newtonsoft 186.56 μs 342.25 μs 18.760 μs 2.96 0.08 25.8789 5.6152 106.72 KB 2.63
WideJson_Sprache 1,430.97 μs 2,026.56 μs 111.082 μs 22.97 4.01 675.7813 50.7813 2766.87 KB 68.21
WideJson_Superpower 956.22 μs 1,752.99 μs 96.087 μs 15.17 0.62 110.3516 - 451.81 KB 11.14

and after this PR:

Method Mean Error StdDev Gen0 Gen1 Allocated
CreateCompiledSmallParser 105,980.10 ns 285,600.51 ns 15,654.728 ns 7.8125 7.3242 33292 B
CreateCompiledExpressionParser 17,728.11 ns 21,612.47 ns 1,184.653 ns 1.1597 1.0986 5024 B
CursorMatchHello 57.83 ns 125.31 ns 6.868 ns 0.0305 - 128 B
CursorMatchGoodbye 53.72 ns 47.19 ns 2.587 ns 0.0305 - 128 B
CursorMatchNone 47.39 ns 168.94 ns 9.260 ns 0.0306 - 128 B
DecodeStringWithoutEscapes 19.62 ns 16.15 ns 0.885 ns - - -
DecodeStringWithEscapes 108.12 ns 100.67 ns 5.518 ns 0.0286 - 120 B
ExpressionRawBig 1,859.38 ns 452.60 ns 24.809 ns 0.2861 - 1200 B
ExpressionCompiledBig 4,094.02 ns 11,130.93 ns 610.124 ns 0.6866 - 2888 B
ExpressionFluentBig 5,307.45 ns 4,758.85 ns 260.849 ns 0.6866 - 2888 B
ExpressionRawSmall 558.45 ns 3,416.79 ns 187.286 ns 0.0725 - 304 B
ExpressionCompiledSmall 899.93 ns 330.45 ns 18.113 ns 0.1564 - 656 B
ExpressionFluentSmall 986.68 ns 1,411.71 ns 77.381 ns 0.1564 - 656 B
BigJson 129,484.24 ns 105,243.80 ns 5,768.768 ns 22.4609 0.2441 93992 B
BigJsonCompiled 131,801.62 ns 103,239.53 ns 5,658.907 ns 22.4609 0.2441 93992 B
DeepJson 90,113.05 ns 63,905.59 ns 3,502.881 ns 23.9258 - 100688 B
DeepJsonCompiled 85,303.90 ns 177,979.92 ns 9,755.680 ns 24.0479 - 100688 B
LongJson 127,503.47 ns 178,490.58 ns 9,783.671 ns 29.2969 - 123224 B
LongJsonCompiled 112,048.53 ns 71,945.07 ns 3,943.552 ns 29.4189 0.3662 123224 B
WideJson 68,570.62 ns 305,037.47 ns 16,720.133 ns 9.8877 - 41536 B
WideJsonCompiled 61,277.53 ns 112,641.94 ns 6,174.285 ns 9.8877 - 41536 B
Lookup 54.36 ns 95.52 ns 5.236 ns 0.0305 - 128 B
SkipWhiteSpace_1 40.29 ns 28.90 ns 1.584 ns 0.0306 - 128 B
SkipWhiteSpace_10 54.82 ns 90.76 ns 4.975 ns 0.0305 - 128 B

SkipWhiteSpace_1 is 40.29 ns compared to 40.36 ns before and SkipWhiteSpace_10 is 54.82 ns compared to 56.25 ns before. The table below is the delta between the after and before this PR (where negative numbers mean better after this PR):

Method Mean Gen0 Gen1 Allocated
CreateCompiledSmallParser 2,640.78 ns 0 0 0
CreateCompiledExpressionParser 209.23 ns 0 0 0
CursorMatchHello 5.94 ns -1E-04 0 0
CursorMatchGoodbye -10.87 ns -1E-04 0 0
CursorMatchNone -6.32 ns 1E-04 0 0
DecodeStringWithoutEscapes -7.71 ns 0 0 0
DecodeStringWithEscapes -8.89 ns 0 0 0
ExpressionRawBig -314.77 ns 0 0 0
ExpressionCompiledBig 136.90 ns 0 0 0
ExpressionFluentBig 310.61 ns 0 0 0
ExpressionRawSmall 152.10 ns 0 0 0
ExpressionCompiledSmall 10.45 ns 0 0 0
ExpressionFluentSmall -56.51 ns 0 0 0
BigJson -11,428.24 ns 0 0 0
BigJsonCompiled -3,354.18 ns 0 0 0
DeepJson -9,009.85 ns -0.1221 0 0
DeepJsonCompiled 2,173.63 ns 0 0 0
LongJson -4,706.09 ns 0 -0.7324 0
LongJsonCompiled 2,684.18 ns 0.122 -0.3662 0
WideJson 6,996.09 ns 0 0 0
WideJsonCompiled -12,903.23 ns 0 0 0
Lookup -7.78 ns 0 0 0
SkipWhiteSpace_1 -0.07 ns 1E-04 0 0
SkipWhiteSpace_10 -1.43 ns -1E-04 0 0

@sebastienros sebastienros merged commit 9489d70 into sebastienros:main Jul 11, 2024
2 checks passed
@atifaziz atifaziz deleted the ParseResult-x-ref branch July 11, 2024 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants