mycroes / Sally7

C# implementation of Siemens S7 connections with a focus on performance
MIT License
56 stars 22 forks source link

Optimized FromS7Conversions for arrays #26

Closed gfoidl closed 2 years ago

gfoidl commented 2 years ago

Method PR2 is this PR finally (and it produces almost the best machine code for that task).

| Method |     Mean |   Error |  StdDev | Ratio | RatioSD | Code Size | Allocated |
|------- |---------:|--------:|--------:|------:|--------:|----------:|----------:|
|   Main | 199.8 ns | 1.98 ns | 1.86 ns |  1.00 |    0.00 |     268 B |         - |
|     PR | 162.3 ns | 0.79 ns | 0.62 ns |  0.81 |    0.01 |     236 B |         - |
|    PR1 | 162.3 ns | 1.32 ns | 1.17 ns |  0.81 |    0.01 |     224 B |         - |
|    PR2 | 155.4 ns | 3.09 ns | 4.13 ns |  0.78 |    0.03 |     216 B |         - |

I believe ToS7Conversions is quite ideal, so didn't touch it for now (and for my use case I'm just reading too).

Benchmark code ```c# using System; using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; BenchmarkRunner.Run(); [MemoryDiagnoser] [DisassemblyDiagnoser] public class Bench { private const int Size = 100; private long[]? _value; private byte[] _data = null!; [AllowNull] public long[] Value { get => _value!; set => _value = value; } [GlobalSetup] public void Setup() { _data = new byte[Size * sizeof(long)]; Random rnd = new(); rnd.NextBytes(_data); _value = null; } [Benchmark(Baseline = true)] public void Main() => ConvertToLongArray_Main(ref _value, _data, Size); [Benchmark] public void PR() => ConvertToLongArray_PR(ref _value, _data, Size); [Benchmark] public void PR1() => ConvertToLongArray_PR1(ref _value, _data, Size); [Benchmark] public void PR2() => ConvertToLongArray_PR2(ref _value, _data, Size); private static void ConvertToLong(ref long value, ReadOnlySpan input, int length) => value = BinaryPrimitives.ReadInt64BigEndian(input); private static void ConvertToLongArray_Main(ref long[]? value, ReadOnlySpan input, int length) { value ??= Unsafe.As(Array.CreateInstance(typeof(TTarget).GetElementType()!, length)); for (var i = 0; i < input.Length / sizeof(long); i++) ConvertToLong(ref value![i], input.Slice(i * sizeof(long)), 1); } private static void ConvertToLongArray_PR(ref long[]? value, ReadOnlySpan input, int length) { value ??= new long[length]; for (var i = 0; i < input.Length / sizeof(long); i++) ConvertToLong(ref value![i], input.Slice(i * sizeof(long)), 1); } private static void ConvertToLongArray_PR1(ref long[]? value, ReadOnlySpan input, int length) { value ??= new long[length]; int valueIdx = 0; for (var i = 0; i < input.Length; i += sizeof(long)) ConvertToLong(ref value![valueIdx++], input.Slice(i), 1); } private static void ConvertToLongArray_PR2(ref long[]? value, ReadOnlySpan input, int length) { value ??= new long[length]; int i = 0; while (!input.IsEmpty) { ConvertToLong(ref value[i++], input, 1); input = input.Slice(sizeof(long)); } } } ```
Machine Code for .NET 5 ```assembly ; Bench.Main() sub rsp,38 xor eax,eax mov [rsp+28],rax lea rdx,[rcx+8] mov r8,[rcx+10] test r8,r8 jne short M00_L00 xor ecx,ecx xor r9d,r9d jmp short M00_L01 M00_L00: lea rcx,[r8+10] mov r9d,[r8+8] M00_L01: lea r8,[rsp+28] mov [r8],rcx mov [r8+8],r9d lea r8,[rsp+28] mov rcx,offset MD_Bench.ConvertToLongArray_Main(Int64[] ByRef, System.ReadOnlySpan`1, Int32) mov r9d,64 call Bench.ConvertToLongArray_Main[[System.__Canon, System.Private.CoreLib]](Int64[] ByRef, System.ReadOnlySpan`1, Int32) nop add rsp,38 ret ; Total bytes of code 83 ; Bench.ConvertToLongArray_Main[[System.__Canon, System.Private.CoreLib]](Int64[] ByRef, System.ReadOnlySpan`1, Int32) push rdi push rsi push rbp push rbx sub rsp,28 mov [rsp+20],rcx mov rsi,rdx mov edi,r9d mov rbx,[r8] mov ebp,[r8+8] cmp qword ptr [rsi],0 jne short M01_L00 mov rcx,[rcx+10] mov rcx,[rcx] call CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE mov rcx,rax call 00007FFB8F85EBC0 mov rcx,rax mov edx,edi call System.Array.CreateInstance(System.Type, Int32) mov rcx,rsi mov rdx,rax call CORINFO_HELP_CHECKED_ASSIGN_REF M01_L00: xor eax,eax mov edx,ebp sar edx,1F and edx,7 add edx,ebp sar edx,3 test edx,edx jle short M01_L02 M01_L01: mov rcx,[rsi] cmp eax,[rcx+8] jae short M01_L05 movsxd r8,eax lea rcx,[rcx+r8*8+10] mov r8d,eax shl r8d,3 cmp r8d,ebp ja short M01_L03 mov r9d,ebp sub r9d,r8d movsxd r8,r8d add r8,rbx cmp r9d,8 jl short M01_L04 mov r8,[r8] bswap r8 mov [rcx],r8 inc eax cmp edx,eax jg short M01_L01 M01_L02: add rsp,28 pop rbx pop rbp pop rsi pop rdi ret M01_L03: call System.ThrowHelper.ThrowArgumentOutOfRangeException() int 3 M01_L04: mov ecx,28 call System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument) int 3 M01_L05: call CORINFO_HELP_RNGCHKFAIL int 3 ; Total bytes of code 185 ; Bench.PR() sub rsp,38 xor eax,eax mov [rsp+28],rax lea rdx,[rcx+8] mov rcx,[rcx+10] test rcx,rcx jne short M00_L00 xor r8d,r8d xor eax,eax jmp short M00_L01 M00_L00: lea r8,[rcx+10] mov eax,[rcx+8] M00_L01: mov rcx,rdx lea rdx,[rsp+28] mov [rdx],r8 mov [rdx+8],eax lea rdx,[rsp+28] mov r8d,64 call Bench.ConvertToLongArray_PR(Int64[] ByRef, System.ReadOnlySpan`1, Int32) nop add rsp,38 ret ; Total bytes of code 74 ; Bench.ConvertToLongArray_PR(Int64[] ByRef, System.ReadOnlySpan`1, Int32) push rdi push rsi push rbx sub rsp,20 mov rsi,rcx mov rdi,[rdx] mov ebx,[rdx+8] cmp qword ptr [rsi],0 jne short M01_L00 movsxd rdx,r8d mov rcx,offset MT_System.Int64[] call CORINFO_HELP_NEWARR_1_VC mov rdx,rax mov rcx,rsi call CORINFO_HELP_CHECKED_ASSIGN_REF M01_L00: xor eax,eax mov edx,ebx sar edx,1F and edx,7 add edx,ebx sar edx,3 test edx,edx jle short M01_L02 M01_L01: mov rcx,[rsi] cmp eax,[rcx+8] jae short M01_L05 movsxd r8,eax lea rcx,[rcx+r8*8+10] mov r8d,eax shl r8d,3 cmp r8d,ebx ja short M01_L03 mov r9d,ebx sub r9d,r8d movsxd r8,r8d add r8,rdi cmp r9d,8 jl short M01_L04 mov r8,[r8] bswap r8 mov [rcx],r8 inc eax cmp edx,eax jg short M01_L01 M01_L02: add rsp,20 pop rbx pop rsi pop rdi ret M01_L03: call System.ThrowHelper.ThrowArgumentOutOfRangeException() int 3 M01_L04: mov ecx,28 call System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument) int 3 M01_L05: call CORINFO_HELP_RNGCHKFAIL int 3 ; Total bytes of code 162 ; Bench.PR1() sub rsp,38 xor eax,eax mov [rsp+28],rax lea rdx,[rcx+8] mov rcx,[rcx+10] test rcx,rcx jne short M00_L00 xor r8d,r8d xor eax,eax jmp short M00_L01 M00_L00: lea r8,[rcx+10] mov eax,[rcx+8] M00_L01: mov rcx,rdx lea rdx,[rsp+28] mov [rdx],r8 mov [rdx+8],eax lea rdx,[rsp+28] mov r8d,64 call Bench.ConvertToLongArray_PR1(Int64[] ByRef, System.ReadOnlySpan`1, Int32) nop add rsp,38 ret ; Total bytes of code 74 ; Bench.ConvertToLongArray_PR1(Int64[] ByRef, System.ReadOnlySpan`1, Int32) push rdi push rsi push rbx sub rsp,20 mov rsi,rcx mov rdi,[rdx] mov ebx,[rdx+8] cmp qword ptr [rsi],0 jne short M01_L00 movsxd rdx,r8d mov rcx,offset MT_System.Int64[] call CORINFO_HELP_NEWARR_1_VC mov rdx,rax mov rcx,rsi call CORINFO_HELP_CHECKED_ASSIGN_REF M01_L00: xor eax,eax xor edx,edx test ebx,ebx jle short M01_L02 M01_L01: lea ecx,[rax+1] mov r8,[rsi] cmp eax,[r8+8] jae short M01_L05 movsxd rax,eax lea rax,[r8+rax*8+10] cmp edx,ebx ja short M01_L03 mov r8d,ebx sub r8d,edx movsxd r9,edx add r9,rdi cmp r8d,8 jl short M01_L04 mov r8,[r9] bswap r8 mov [rax],r8 add edx,8 cmp edx,ebx mov eax,ecx jl short M01_L01 M01_L02: add rsp,20 pop rbx pop rsi pop rdi ret M01_L03: call System.ThrowHelper.ThrowArgumentOutOfRangeException() int 3 M01_L04: mov ecx,28 call System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument) int 3 M01_L05: call CORINFO_HELP_RNGCHKFAIL int 3 ; Total bytes of code 150 ; Bench.PR2() sub rsp,38 xor eax,eax mov [rsp+28],rax lea rdx,[rcx+8] mov rcx,[rcx+10] test rcx,rcx jne short M00_L00 xor r8d,r8d xor eax,eax jmp short M00_L01 M00_L00: lea r8,[rcx+10] mov eax,[rcx+8] M00_L01: mov rcx,rdx lea rdx,[rsp+28] mov [rdx],r8 mov [rdx+8],eax lea rdx,[rsp+28] mov r8d,64 call Bench.ConvertToLongArray_PR2(Int64[] ByRef, System.ReadOnlySpan`1, Int32) nop add rsp,38 ret ; Total bytes of code 74 ; Bench.ConvertToLongArray_PR2(Int64[] ByRef, System.ReadOnlySpan`1, Int32) push rdi push rsi push rbx sub rsp,20 mov rsi,rcx mov rdi,[rdx] mov ebx,[rdx+8] cmp qword ptr [rsi],0 jne short M01_L00 movsxd rdx,r8d mov rcx,offset MT_System.Int64[] call CORINFO_HELP_NEWARR_1_VC mov rdx,rax mov rcx,rsi call CORINFO_HELP_CHECKED_ASSIGN_REF M01_L00: xor ecx,ecx test ebx,ebx jbe short M01_L02 M01_L01: lea eax,[rcx+1] mov rdx,[rsi] cmp ecx,[rdx+8] jae short M01_L05 movsxd rcx,ecx lea rcx,[rdx+rcx*8+10] mov rdx,rdi cmp ebx,8 jl short M01_L03 mov rdx,[rdx] bswap rdx mov [rcx],rdx cmp ebx,8 jb short M01_L04 add ebx,0FFFFFFF8 add rdi,8 test ebx,ebx mov ecx,eax ja short M01_L01 M01_L02: add rsp,20 pop rbx pop rsi pop rdi ret M01_L03: mov ecx,28 call System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument) int 3 M01_L04: call System.ThrowHelper.ThrowArgumentOutOfRangeException() int 3 M01_L05: call CORINFO_HELP_RNGCHKFAIL int 3 ; Total bytes of code 142 ```
mycroes commented 2 years ago

Took a while for me to review, but again thanks for the contribution.