diff --git a/.gitignore b/.gitignore index 94420dc..c5b5bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -234,3 +234,7 @@ _Pvt_Extensions # FAKE - F# Make .fake/ +/src/DotNetCross.Memory.Copies.Benchmarks2/chart.json +/I5-4590.xlsx +/I7-3610QM.xlsx +/src/DotNetCross.Memory.Copies.Benchmarks2/chart0_8400.json diff --git a/DotNetCross.Memory.Copies.sln b/DotNetCross.Memory.Copies.sln index 6472262..b69def4 100644 --- a/DotNetCross.Memory.Copies.sln +++ b/DotNetCross.Memory.Copies.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetCross.Memory.Copies.Benchmarks", "src\DotNetCross.Memory.Copies.Benchmarks\DotNetCross.Memory.Copies.Benchmarks.csproj", "{DF33082A-C7BE-4305-BCAD-159D667C9420}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DotNetCross.Memory.Copies.Benchmarks2", "src\DotNetCross.Memory.Copies.Benchmarks2\DotNetCross.Memory.Copies.Benchmarks2.xproj", "{9396CFEF-F925-4202-98E7-1F0759DF0C5B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +11,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DF33082A-C7BE-4305-BCAD-159D667C9420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF33082A-C7BE-4305-BCAD-159D667C9420}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF33082A-C7BE-4305-BCAD-159D667C9420}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF33082A-C7BE-4305-BCAD-159D667C9420}.Release|Any CPU.Build.0 = Release|Any CPU + {9396CFEF-F925-4202-98E7-1F0759DF0C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9396CFEF-F925-4202-98E7-1F0759DF0C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9396CFEF-F925-4202-98E7-1F0759DF0C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9396CFEF-F925-4202-98E7-1F0759DF0C5B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/DotNetCross.Memory.Copies.Benchmarks2.xproj b/src/DotNetCross.Memory.Copies.Benchmarks2/DotNetCross.Memory.Copies.Benchmarks2.xproj new file mode 100644 index 0000000..93c0858 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/DotNetCross.Memory.Copies.Benchmarks2.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 9396cfef-f925-4202-98e7-1f0759df0c5b + DotNetCross.Memory.Copies.Benchmarks2 + .\obj + .\bin\ + v4.6.1 + + + 2.0 + + + \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/C.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/C.cs new file mode 100644 index 0000000..c43b042 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/C.cs @@ -0,0 +1,8 @@ +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public class C + { + public object v { get; set; } + public string f { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/Col.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/Col.cs new file mode 100644 index 0000000..bb813aa --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/Col.cs @@ -0,0 +1,9 @@ +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public class Col + { + public string id { get; set; } + public string label { get; set; } + public string type { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/GoogleChart.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/GoogleChart.cs new file mode 100644 index 0000000..707e39c --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/GoogleChart.cs @@ -0,0 +1,8 @@ +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public class GoogleChart + { + public Col[] cols { get; set; } + public Row[] rows { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/Row.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/Row.cs new file mode 100644 index 0000000..3c431e8 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/GoogleChart/Row.cs @@ -0,0 +1,7 @@ +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public class Row + { + public C[] c { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Memcopy/AndermanMovsb.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/Memcopy/AndermanMovsb.cs new file mode 100644 index 0000000..3e586e3 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Memcopy/AndermanMovsb.cs @@ -0,0 +1,105 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + //Used this code only for testing. This code works only on 64bit + public static class AndermanMovsb + { + private const uint PAGE_READWRITE = 0x04; + private const uint PAGE_EXECUTE = 0x10; + private const uint PAGE_EXECUTE_READWRITE = 0x40; + private const uint MEM_COMMIT = 0x1000; + private const uint MEM_RELEASE = 0x8000; + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, out uint lpflOldProtect); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + private static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, uint flProtect); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, uint dwFreeType); + + public delegate int MyMoveSb(ulong RCX, ulong RDX, ulong R8); + + public static MyMoveSb movsb; + public static MyMoveSb pinvoke; + + static AndermanMovsb() + { + //RAX�=�0000008377040000 RBX�=�0000000000000005 RCX�=�0000000000000004 RDX�=�0000000000000005 RSI�=�0000008300018350 RDI�=�0000000000000004 R8 �=�0000000000000006 R9 �=�000007F9C2460F84 R10�=�0000000000000000 R11�=�0000000000000000 R12�=�00000083754BDF70 R13�=�0000000000000004 R14�=�0000000000000006 R15�=�000000837555A4D0 RIP�=�0000008377040000 RSP�=�00000083754BDDF8 RBP�=�00000083754BDEA0 EFL�=�00000246 + /* + 0: 49 89 fb mov r11,rdi + 3: 49 89 f2 mov r10,rsi + 6: 48 89 ce mov rsi,rcx + 9: 48 89 d7 mov rdi,rdx + c: 4c 89 c1 mov rcx,r8 + f: f3 a4 rep movs BYTE PTR es:[rdi],BYTE PTR ds:[rsi] + 11: 4c 89 d6 mov rsi,r10 + 14: 4c 89 df mov rdi,r11 + 17: c3 ret + */ + byte[] assemblyCode = { 0x49, 0x89, 0xFB, 0x49, 0x89, 0xF2, 0x48, 0x89, 0xCE, 0x48, 0x89, 0xD7, 0x4C, 0x89, 0xC1, 0xF3, 0xA4, 0x4C, 0x89, 0xD6, 0x4C, 0x89, 0xDF, 0xC3 }; + byte[] assemblyCode2 = { 0xC3 }; + + + // We need to push the code bytes into a native buffer + + var bufPtr = IntPtr.Zero; + + try + { + // Put the sourcecode in a native buffer + bufPtr = VirtualAlloc(IntPtr.Zero, (IntPtr)4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + Marshal.Copy(assemblyCode, 0, bufPtr, assemblyCode.Length); + Marshal.Copy(assemblyCode2, 0, bufPtr+64, assemblyCode2.Length); + + uint oldProtection; + var result = VirtualProtect(bufPtr, (IntPtr)assemblyCode.Length, PAGE_EXECUTE, out oldProtection); + + if (!result) + { + throw new Win32Exception(); + } + movsb = Marshal.GetDelegateForFunctionPointer(bufPtr); + pinvoke = Marshal.GetDelegateForFunctionPointer(bufPtr+64); + bufPtr = IntPtr.Zero; + } + finally + { + // Free the native buffer + if (bufPtr != IntPtr.Zero) + { + var result = VirtualFree(bufPtr, IntPtr.Zero, MEM_RELEASE); + if (!result) + { + throw new Win32Exception(); + } + } + } + } + + + public static unsafe void Memmove(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count) + { + if (count == 0) return; + if (src == null || dst == null) throw new ArgumentNullException(nameof(src)); + if (srcOffset + count > src.Length) throw new ArgumentException(nameof(src)); + if (count < 0 || srcOffset < 0 || dstOffset < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (dstOffset + count > dst.Length) throw new ArgumentException(nameof(dst)); + + fixed (byte* pSrcOrigin = &src[srcOffset]) + fixed (byte* pDstOrigin = &dst[dstOffset]) + { + var pSrc = pSrcOrigin; + var pDst = pDstOrigin; + var result1 = movsb((ulong)pSrc, (ulong)pDst,(ulong)count); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Memcopy/AndermanOptimized.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/Memcopy/AndermanOptimized.cs new file mode 100644 index 0000000..2666b07 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Memcopy/AndermanOptimized.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public static class AndermanOptimized + { + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Memmove(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count) + { + const int alignment = 0x10; + const int mask = alignment - 1; + + if (count == 0) return; + if (src == null || dst == null) throw new ArgumentNullException(nameof(src)); + if (srcOffset + count > src.Length) throw new ArgumentException(nameof(src)); + if (count < 0 || srcOffset < 0 || dstOffset < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (dstOffset + count > dst.Length) throw new ArgumentException(nameof(dst)); + + fixed (byte* pSrcOrigin = &src[srcOffset]) + fixed (byte* pDstOrigin = &dst[dstOffset]) + { + var pSrc = pSrcOrigin; + var pDst = pDstOrigin; + if (count < 16) + { + if (count < 8) + { + if (count < 4) + { + if (count < 2) + { + *(pDst + 0) = *(pSrc + 0); + } + else + { + *(short*)pDst = *(short*)pSrc; + *(short*)(pDst + (count & 0xf) - 2) = *(short*)(pSrc + (count & 0xf) - 2); + } + } + else + { + *(int*)pDst = *(int*)pSrc; + *(int*)(pDst + (count & 0xf) - 4) = *(int*)(pSrc + (count & 0xf) - 4); + } + + } + else + { + *(long*)pDst = *(long*)pSrc; + *(long*)(pDst + (count & 0xf) - 8) = *(long*)(pSrc + (count & 0xf) - 8); + } + return; + } + + pSrc -= (long)pDst; + Unsafe.Write(pDst, Unsafe.Read>(pSrc + (long)pDst)); + + var offset = (int)((ulong)pDst & mask); + count += offset - alignment; + pDst += alignment - offset; + + while (count >= 4 * Vector.Count) + { + var x1 = Unsafe.Read>(pSrc + (long)pDst); + var x2 = Unsafe.Read>(pSrc + (long)pDst + Vector.Count); + count -= 4 * Vector.Count; + Unsafe.Write(pDst, x1); + Unsafe.Write(pDst + Vector.Count, x2); + pDst += 4 * Vector.Count; + x1 = Unsafe.Read>(pSrc + (long)pDst - 2 * Vector.Count); + x2 = Unsafe.Read>(pSrc + (long)pDst - Vector.Count); + Unsafe.Write(pDst - 2 * Vector.Count, x1); + Unsafe.Write(pDst - Vector.Count, x2); + } + while (count >= 2 * Vector.Count) + { + var x1 = Unsafe.Read>(pSrc + (long)pDst); + var x2 = Unsafe.Read>(pSrc + (long)pDst + Vector.Count); + count -= 2 * Vector.Count; + Unsafe.Write(pDst, x1); + Unsafe.Write(pDst + Vector.Count, x2); + pDst += 2 * Vector.Count; + } + while (count >= 1 * Vector.Count) + { + var x1 = Unsafe.Read>(pSrc + (long)pDst); + count -= Vector.Count; + Unsafe.Write(pDst, x1); + pDst += Vector.Count; + } + if (count > 0) + { + pDst += count - Vector.Count; + Unsafe.Write(pDst, Unsafe.Read>(pSrc + (long) pDst)); + } + } + } + } + +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Program.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/Program.cs new file mode 100644 index 0000000..cd2d667 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Program.cs @@ -0,0 +1,101 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Newtonsoft.Json; + +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public class Program + { + private const ulong TestDuration = 100; + //private static extern bool QueryThreadCycleTime(IntPtr hThread, out ulong cycles); + //private static readonly IntPtr PseudoHandle = (IntPtr)(-2); + public static double LoopOverhead; + public static ulong CyclesPerSecond; + public static double NsPerCycle; + + public static void Main(string[] args) + { + Console.WriteLine($"Warmup..."); + //Tests.Warmup(); + CyclesPerSecond = GetCyclesPerSeond(); + NsPerCycle = 1000*1000*1000.0/CyclesPerSecond; + Tests.TestDuration = TestDuration*CyclesPerSecond/1000; + LoopOverhead = Tests.TestOverhead(1000,1000); + + Console.WriteLine($"offset src: {Tests.GetOffsetSrc():X04}"); + Console.WriteLine($"offset dst: {Tests.GetOffsetDst():X04} "); + Console.WriteLine($"CyclesPerSecond: {CyclesPerSecond,5:0} "); + Console.WriteLine($"nsPerCycle: {NsPerCycle,5:0.0000} "); + Console.WriteLine($"loopOverhead: {LoopOverhead,5:0.0000} Cycles"); + Console.WriteLine($" {LoopOverhead*NsPerCycle,5:0.0000} ns "); + Console.WriteLine($"Starting..."); + + + do + { + var googleChart = new GoogleChart + { + cols = new[] + { + new Col {label = "X", type = "number"}, + new Col {label = "ArrayCopy", type = "number"}, + new Col {label = "MsvcrtMemmove", type = "number"}, + new Col {label = "AndermanMovsb", type = "number"}, + new Col {label = "Anderman", type = "number"} + } + }; + var sizes = new[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 103, 113, 122, 128, 135, + 145, + 154, 160, 167, 177, 186, 192, 199, 209, 218, 224, 231, 241, 250, 256, 263, 273, 282, 288, 295, 305, 314, 320, 327, 337, 346, 352, 359, 369, 378, 384, 391, 401, 410, 416, 423, 433, 442, 448, 455, 465, 474, 480, 487, 497, 506, 512, 519, 529, 538, 544, 551, 561, 570, 576, 583, + 593, 602, 608, 615, 625, 634, 640, 647, 657, 666, 672, 679, 689, 698, 704, 711, 721, 730, 736, 743, 753, 762, 768, 775, 785, 794, 800, 807, 817, 826, 832, 839, 849, 858, 864, 871, 881, 890, 896, 903, 913, 922, 928, 935, 945, 954, 960, 967, 977, 986, 1024, 1086, 1155, 1223, + 1280, 1342, 1411, 1479, 1536, 1598, 1667, 1735, 1792, 1854, 1923, 1991, 2048, 2110, 2179, 2247, 2304, 2366, 2435, 2503, 2560, 2622, 2691, 2759, 2816, 2878, 2947, 3015, 3072, 3134, 3203, 3271, 3328, 3390, 3459, 3527, 3584, 3646, 3715, 3783, 3840, 3902, 3971, 4039, 4096, 4158, + 4227, 4295, 4352, 4414, 4483, 4551, 4608, 4670, 4739, 4807, 4864, 4926, 4995, 5063, 5120, 5182, 5251, 5319, 5376, 5438, 5507, 5575, 5632, 5694, 5763, 5831, 5888, 5950, 6019, 6087, 6144, 6206, 6275, 6343, 6400, 6462, 6531, 6599, 6656, 6718, 6787, 6855, 6912, 6974, 7043, 7111, + 7168, 7230, 7299, 7367, 7424, 7486, 7555, 7623, 7680, 7742, 7811, 7879, 7936, 7998, 8067, 8135, 8192, 8254, 8323, 8391, 16384, 32768, 65536, 131072, 262144,262144+256,262144+512, 524288, 1048576, 2*1048576, 4*1048576, 8*1048576 + }; + var selectedSizes = sizes.Where(x => x >= 0 && x < 10).ToArray(); + googleChart.rows = new Row[selectedSizes.Count()]; + var index = 0; + foreach (var size in selectedSizes) + { + var cycles = Tests.TestArray(0, size) - LoopOverhead; + var cycles0 = Tests.TestArray(0, size) - LoopOverhead; + var cycles2 = Tests.TestMovSb(0, size) - LoopOverhead; + var cycles3 = Tests.TestAnderman(0, size) - LoopOverhead; + googleChart.rows[index++] = new Row + { + c = new[] + { + new C {v = size}, + new C {v = cycles0}, + new C {v = cycles2}, + new C {v = cycles3} + } + }; + + //double cycles3 = TestCode(TestAnderman); + Console.WriteLine($"{size:0} {cycles,8:0.00} {cycles2,8:0.00} {cycles3,8:0.00} "); + } + Console.WriteLine("ready"); + File.WriteAllText(@"chart.json", "chartData=" + JsonConvert.SerializeObject(googleChart)); + + Tests.Warmup(); + } while (false); + } + + private static ulong GetCyclesPerSeond() + { + var sw = Stopwatch.StartNew(); + var startms = Rdtsc.TimestampP(); + do + { + } while (sw.ElapsedMilliseconds < 1000); + var endms = Rdtsc.TimestampP(); + + return endms - startms; + } + } +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/AssemblyInfo.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2feeb81 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("test")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("dbae07cb-2303-4f98-953f-f4128a3c9680")] diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/PublishProfiles/Local-publish.ps1 b/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/PublishProfiles/Local-publish.ps1 new file mode 100644 index 0000000..6d9ff92 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/PublishProfiles/Local-publish.ps1 @@ -0,0 +1,19 @@ +[cmdletbinding(SupportsShouldProcess=$true)] +param($publishProperties=@{}, $packOutput, $pubProfilePath) + +# to learn more about this file visit https://go.microsoft.com/fwlink/?LinkId=524327 + +try{ + if ($publishProperties['ProjectGuid'] -eq $null){ + $publishProperties['ProjectGuid'] = '9396cfef-f925-4202-98e7-1f0759df0c5b' + } + + $publishModulePath = Join-Path (Split-Path $MyInvocation.MyCommand.Path) 'publish-module.psm1' + Import-Module $publishModulePath -DisableNameChecking -Force + + # call Publish-AspNet to perform the publish operation + Publish-AspNet -publishProperties $publishProperties -packOutput $packOutput -pubProfilePath $pubProfilePath +} +catch{ + "An error occurred during publish.`n{0}" -f $_.Exception.Message | Write-Error +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/PublishProfiles/publish-module.psm1 b/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/PublishProfiles/publish-module.psm1 new file mode 100644 index 0000000..adc6ada --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Properties/PublishProfiles/publish-module.psm1 @@ -0,0 +1,1231 @@ +# WARNING: DO NOT MODIFY this file. Visual Studio will override it. +param() + +$script:AspNetPublishHandlers = @{} + +<# +These settings can be overridden with environment variables. +The name of the environment variable should use "Publish" as a +prefix and the names below. For example: + + $env:PublishMSDeployUseChecksum = $true +#> +$global:AspNetPublishSettings = New-Object -TypeName PSCustomObject @{ + MsdeployDefaultProperties = @{ + 'MSDeployUseChecksum'=$false + 'SkipExtraFilesOnServer'=$true + 'retryAttempts' = 20 + 'EnableMSDeployBackup' = $false + 'DeleteExistingFiles' = $false + 'AllowUntrustedCertificate'= $false + 'MSDeployPackageContentFoldername'='website\' + 'EnvironmentName' = 'Production' + 'AuthType'='Basic' + 'MSDeployPublishMethod'='WMSVC' + } +} + +function InternalOverrideSettingsFromEnv{ + [cmdletbinding()] + param( + [Parameter(Position=0)] + [object[]]$settings = ($global:AspNetPublishSettings,$global:AspNetPublishSettings.MsdeployDefaultProperties), + + [Parameter(Position=1)] + [string]$prefix = 'Publish' + ) + process{ + foreach($settingsObj in $settings){ + if($settingsObj -eq $null){ + continue + } + + $settingNames = $null + if($settingsObj -is [hashtable]){ + $settingNames = $settingsObj.Keys + } + else{ + $settingNames = ($settingsObj | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name) + + } + + foreach($name in @($settingNames)){ + $fullname = ('{0}{1}' -f $prefix,$name) + if(Test-Path "env:$fullname"){ + $settingsObj.$name = ((get-childitem "env:$fullname").Value) + } + } + } + } +} + +InternalOverrideSettingsFromEnv -prefix 'Publish' -settings $global:AspNetPublishSettings,$global:AspNetPublishSettings.MsdeployDefaultProperties + +function Register-AspnetPublishHandler{ + [cmdletbinding()] + param( + [Parameter(Mandatory=$true,Position=0)] + $name, + [Parameter(Mandatory=$true,Position=1)] + [ScriptBlock]$handler, + [switch]$force + ) + process{ + if(!($script:AspNetPublishHandlers[$name]) -or $force ){ + 'Adding handler for [{0}]' -f $name | Write-Verbose + $script:AspNetPublishHandlers[$name] = $handler + } + elseif(!($force)){ + 'Ignoring call to Register-AspnetPublishHandler for [name={0}], because a handler with that name exists and -force was not passed.' -f $name | Write-Verbose + } + } +} + +function Get-AspnetPublishHandler{ + [cmdletbinding()] + param( + [Parameter(Mandatory=$true,Position=0)] + $name + ) + process{ + $foundHandler = $script:AspNetPublishHandlers[$name] + + if(!$foundHandler){ + throw ('AspnetPublishHandler with name "{0}" was not found' -f $name) + } + + $foundHandler + } +} + +function GetInternal-ExcludeFilesArg{ + [cmdletbinding()] + param( + $publishProperties + ) + process{ + $excludeFiles = $publishProperties['ExcludeFiles'] + foreach($exclude in $excludeFiles){ + if($exclude){ + [string]$objName = $exclude['objectname'] + + if([string]::IsNullOrEmpty($objName)){ + $objName = 'filePath' + } + + $excludePath = $exclude['absolutepath'] + + # output the result to the return list + ('-skip:objectName={0},absolutePath=''{1}''' -f $objName, $excludePath) + } + } + } +} + +function GetInternal-ReplacementsMSDeployArgs{ + [cmdletbinding()] + param( + $publishProperties + ) + process{ + foreach($replace in ($publishProperties['Replacements'])){ + if($replace){ + $typeValue = $replace['type'] + if(!$typeValue){ $typeValue = 'TextFile' } + + $file = $replace['file'] + $match = $replace['match'] + $newValue = $replace['newValue'] + + if($file -and $match -and $newValue){ + $setParam = ('-setParam:type={0},scope={1},match={2},value={3}' -f $typeValue,$file, $match,$newValue) + 'Adding setparam [{0}]' -f $setParam | Write-Verbose + + # return it + $setParam + } + else{ + 'Skipping replacement because its missing a required value.[file="{0}",match="{1}",newValue="{2}"]' -f $file,$match,$newValue | Write-Verbose + } + } + } + } +} + +<# +.SYNOPSIS +Returns an array of msdeploy arguments that are used across different providers. +For example this will handle useChecksum, AppOffline etc. +This will also add default properties if they are missing. +#> +function GetInternal-SharedMSDeployParametersFrom{ + [cmdletbinding()] + param( + [Parameter(Mandatory=$true,Position=0)] + [HashTable]$publishProperties, + [Parameter(Mandatory=$true,Position=1)] + [System.IO.FileInfo]$packOutput + ) + process{ + $sharedArgs = New-Object psobject -Property @{ + ExtraArgs = @() + DestFragment = '' + EFMigrationData = @{} + } + + # add default properties if they are missing + foreach($propName in $global:AspNetPublishSettings.MsdeployDefaultProperties.Keys){ + if($publishProperties["$propName"] -eq $null){ + $defValue = $global:AspNetPublishSettings.MsdeployDefaultProperties["$propName"] + 'Adding default property to publishProperties ["{0}"="{1}"]' -f $propName,$defValue | Write-Verbose + $publishProperties["$propName"] = $defValue + } + } + + if($publishProperties['MSDeployUseChecksum'] -eq $true){ + $sharedArgs.ExtraArgs += '-usechecksum' + } + + if($publishProperties['EnableMSDeployAppOffline'] -eq $true){ + $sharedArgs.ExtraArgs += '-enablerule:AppOffline' + } + + if($publishProperties['WebPublishMethod'] -eq 'MSDeploy'){ + if($publishProperties['SkipExtraFilesOnServer'] -eq $true){ + $sharedArgs.ExtraArgs += '-enableRule:DoNotDeleteRule' + } + } + + if($publishProperties['WebPublishMethod'] -eq 'FileSystem'){ + if($publishProperties['DeleteExistingFiles'] -eq $false){ + $sharedArgs.ExtraArgs += '-enableRule:DoNotDeleteRule' + } + } + + if($publishProperties['retryAttempts']){ + $sharedArgs.ExtraArgs += ('-retryAttempts:{0}' -f ([int]$publishProperties['retryAttempts'])) + } + + if($publishProperties['EncryptWebConfig'] -eq $true){ + $sharedArgs.ExtraArgs += '-EnableRule:EncryptWebConfig' + } + + if($publishProperties['EnableMSDeployBackup'] -eq $false){ + $sharedArgs.ExtraArgs += '-disablerule:BackupRule' + } + + if($publishProperties['AllowUntrustedCertificate'] -eq $true){ + $sharedArgs.ExtraArgs += '-allowUntrusted' + } + + # add excludes + $sharedArgs.ExtraArgs += (GetInternal-ExcludeFilesArg -publishProperties $publishProperties) + # add replacements + $sharedArgs.ExtraArgs += (GetInternal-ReplacementsMSDeployArgs -publishProperties $publishProperties) + + # add EF Migration + if (($publishProperties['EfMigrations'] -ne $null) -and $publishProperties['EfMigrations'].Count -gt 0){ + if (!(Test-Path -Path $publishProperties['ProjectPath'])) { + throw 'ProjectPath property needs to be defined in the pubxml for EF migration.' + } + try { + # generate T-SQL files + $EFSqlFiles = GenerateInternal-EFMigrationScripts -projectPath $publishProperties['ProjectPath'] -packOutput $packOutput -EFMigrations $publishProperties['EfMigrations'] + $sharedArgs.EFMigrationData.Add('EFSqlFiles',$EFSqlFiles) + } + catch { + throw ('An error occurred while generating EF migrations. {0} {1}' -f $_.Exception,(Get-PSCallStack)) + } + } + # add connection string update + if (($publishProperties['DestinationConnectionStrings'] -ne $null) -and $publishProperties['DestinationConnectionStrings'].Count -gt 0) { + try { + # create/update appsettings.[environment].json + GenerateInternal-AppSettingsFile -packOutput $packOutput -environmentName $publishProperties['EnvironmentName'] -connectionStrings $publishProperties['DestinationConnectionStrings'] + } + catch { + throw ('An error occurred while generating the publish appsettings file. {0} {1}' -f $_.Exception,(Get-PSCallStack)) + } + } + + if(-not [string]::IsNullOrWhiteSpace($publishProperties['ProjectGuid'])) { + AddInternal-ProjectGuidToWebConfig -publishProperties $publishProperties -packOutput $packOutput + } + + # return the args + $sharedArgs + } +} + +<# +.SYNOPSIS +This will publish the folder based on the properties in $publishProperties + +.PARAMETER publishProperties +This is a hashtable containing the publish properties. See the examples here for more info on how to use this parameter. + +.PARAMETER packOutput +The folder path to the output of the dnu publish command. This folder contains the files +that will be published. + +.PARAMETER pubProfilePath +Path to a publish profile (.pubxml file) to import publish properties from. If the same property exists in +publishProperties and the publish profile then publishProperties will win. + +.EXAMPLE + Publish-AspNet -packOutput $packOutput -publishProperties @{ + 'WebPublishMethod'='MSDeploy' + 'MSDeployServiceURL'='contoso.scm.azurewebsites.net:443';` + 'DeployIisAppPath'='contoso';'Username'='$contoso';'Password'="$env:PublishPwd"} + +.EXAMPLE +Publish-AspNet -packOutput $packOutput -publishProperties @{ + 'WebPublishMethod'='FileSystem' + 'publishUrl'="$publishDest" + } + +.EXAMPLE +Publish-AspNet -packOutput $packOutput -publishProperties @{ + 'WebPublishMethod'='MSDeploy' + 'MSDeployServiceURL'='contoso.scm.azurewebsites.net:443';` +'DeployIisAppPath'='contoso';'Username'='$contoso';'Password'="$env:PublishPwd" + 'ExcludeFiles'=@( + @{'absolutepath'='test.txt'}, + @{'absolutepath'='references.js'} +)} + +.EXAMPLE +Publish-AspNet -packOutput $packOutput -publishProperties @{ + 'WebPublishMethod'='FileSystem' + 'publishUrl'="$publishDest" + 'ExcludeFiles'=@( + @{'absolutepath'='test.txt'}, + @{'absolutepath'='_references.js'}) + 'Replacements' = @( + @{'file'='test.txt$';'match'='REPLACEME';'newValue'='updatedValue'}) + } + +Publish-AspNet -packOutput $packOutput -publishProperties @{ + 'WebPublishMethod'='FileSystem' + 'publishUrl'="$publishDest" + 'ExcludeFiles'=@( + @{'absolutepath'='test.txt'}, + @{'absolutepath'='c:\\full\\path\\ok\\as\\well\\_references.js'}) + 'Replacements' = @( + @{'file'='test.txt$';'match'='REPLACEME';'newValue'='updatedValue'}) + } + +.EXAMPLE +Publish-AspNet -packOutput $packOutput -publishProperties @{ + 'WebPublishMethod'='FileSystem' + 'publishUrl'="$publishDest" + 'EnableMSDeployAppOffline'='true' + 'AppOfflineTemplate'='offline-template.html' + 'MSDeployUseChecksum'='true' +} +#> +function Publish-AspNet{ + param( + [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [hashtable]$publishProperties = @{}, + + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + [System.IO.FileInfo]$packOutput, + + [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)] + [System.IO.FileInfo]$pubProfilePath + ) + process{ + if($publishProperties['WebPublishMethodOverride']){ + 'Overriding publish method from $publishProperties[''WebPublishMethodOverride''] to [{0}]' -f ($publishProperties['WebPublishMethodOverride']) | Write-Verbose + $publishProperties['WebPublishMethod'] = $publishProperties['WebPublishMethodOverride'] + } + + if(-not [string]::IsNullOrWhiteSpace($pubProfilePath)){ + $profileProperties = Get-PropertiesFromPublishProfile -filepath $pubProfilePath + foreach($key in $profileProperties.Keys){ + if(-not ($publishProperties.ContainsKey($key))){ + 'Adding properties from publish profile [''{0}''=''{1}'']' -f $key,$profileProperties[$key] | Write-Verbose + $publishProperties.Add($key,$profileProperties[$key]) + } + } + } + + if(!([System.IO.Path]::IsPathRooted($packOutput))){ + $packOutput = [System.IO.Path]::GetFullPath((Join-Path $pwd $packOutput)) + } + + $pubMethod = $publishProperties['WebPublishMethod'] + 'Publishing with publish method [{0}]' -f $pubMethod | Write-Output + + # get the handler based on WebPublishMethod, and call it. + &(Get-AspnetPublishHandler -name $pubMethod) $publishProperties $packOutput + } +} + +<# +.SYNOPSIS + +Inputs: + +Example of $xmlDocument: '' +Example of $providerDataArray: + + [System.Collections.ArrayList]$providerDataArray = @() + + $iisAppSourceKeyValue=@{"iisApp" = @{"path"='c:\temp\pathtofiles';"appOfflineTemplate" ='offline-template.html'}} + $providerDataArray.Add($iisAppSourceKeyValue) + + $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile"}} + $providerDataArray.Add($dbfullsqlKeyValue) + + $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile2"}} + $providerDataArray.Add($dbfullsqlKeyValue) + + Manifest File content: + + + + + + +#> +function AddInternal-ProviderDataToManifest { + [cmdletbinding()] + param( + [Parameter(Mandatory=$true, Position=0)] + [XML]$xmlDocument, + [Parameter(Position=1)] + [System.Collections.ArrayList]$providerDataArray + ) + process { + $siteNode = $xmlDocument.SelectSingleNode("/sitemanifest") + if ($siteNode -eq $null) { + throw 'sitemanifest element is missing in the xml object' + } + foreach ($providerData in $providerDataArray) { + foreach ($providerName in $providerData.Keys) { + $providerValue = $providerData[$providerName] + $xmlNode = $xmlDocument.CreateElement($providerName) + foreach ($providerValueKey in $providerValue.Keys) { + $xmlNode.SetAttribute($providerValueKey, $providerValue[$providerValueKey]) | Out-Null + } + $siteNode.AppendChild($xmlNode) | Out-Null + } + } + } +} + +function AddInternal-ProjectGuidToWebConfig { + [cmdletbinding()] + param( + [Parameter(Position=0)] + [HashTable]$publishProperties, + [Parameter(Position=1)] + [System.IO.FileInfo]$packOutput + ) + process { + try { + [Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq") | Out-Null + $webConfigPath = Join-Path $packOutput 'web.config' + $projectGuidCommentValue = 'ProjectGuid: {0}' -f $publishProperties['ProjectGuid'] + $xDoc = [System.Xml.Linq.XDocument]::Load($webConfigPath) + $allNodes = $xDoc.DescendantNodes() + $projectGuidComment = $allNodes | Where-Object { $_.NodeType -eq [System.Xml.XmlNodeType]::Comment -and $_.Value -eq $projectGuidCommentValue } | Select -First 1 + if($projectGuidComment -ne $null) { + if($publishProperties['IgnoreProjectGuid'] -eq $true) { + $projectGuidComment.Remove() | Out-Null + $xDoc.Save($webConfigPath) | Out-Null + } + } + else { + if(-not ($publishProperties['IgnoreProjectGuid'] -eq $true)) { + $projectGuidComment = New-Object -TypeName System.Xml.Linq.XComment -ArgumentList $projectGuidCommentValue + $xDoc.LastNode.AddAfterSelf($projectGuidComment) | Out-Null + $xDoc.Save($webConfigPath) | Out-Null + } + } + } + catch { + } + } +} + +<# +.SYNOPSIS + +Example of $EFMigrations: + $EFMigrations = @{'CarContext'='Car Context ConnectionString';'MovieContext'='Movie Context Connection String'} + +#> + +function GenerateInternal-EFMigrationScripts { + [cmdletbinding()] + param( + [Parameter(Mandatory=$true,Position=0)] + [System.IO.FileInfo]$projectPath, + [Parameter(Mandatory=$true,Position=1)] + [System.IO.FileInfo]$packOutput, + [Parameter(Position=2)] + [HashTable]$EFMigrations + ) + process { + $files = @{} + $dotnetExePath = GetInternal-DotNetExePath + foreach ($dbContextName in $EFMigrations.Keys) { + try + { + $tempDir = GetInternal-PublishTempPath -packOutput $packOutput + $efScriptFile = Join-Path $tempDir ('{0}.sql' -f $dbContextName) + $arg = ('ef migrations script --idempotent --output {0} --context {1}' -f + $efScriptFile, + $dbContextName) + + Execute-Command $dotnetExePath $arg $projectPath | Out-Null + if (Test-Path -Path $efScriptFile) { + if (!($files.ContainsKey($dbContextName))) { + $files.Add($dbContextName, $efScriptFile) | Out-Null + } + } + } + catch + { + throw 'error occured when executing dotnet.exe to generate EF T-SQL file' + } + } + # return files object + $files + } +} + +<# +.SYNOPSIS + +Example of $connectionStrings: + $connectionStrings = @{'DefaultConnection'='Default ConnectionString';'CarConnection'='Car Connection String'} + +#> +function GenerateInternal-AppSettingsFile { + [cmdletbinding()] + param( + [Parameter(Mandatory = $true,Position=0)] + [System.IO.FileInfo]$packOutput, + [Parameter(Mandatory = $true,Position=1)] + [string]$environmentName, + [Parameter(Position=2)] + [HashTable]$connectionStrings + ) + process { + $configProdJsonFile = 'appsettings.{0}.json' -f $environmentName + $configProdJsonFilePath = Join-Path -Path $packOutput -ChildPath $configProdJsonFile + + if ([string]::IsNullOrEmpty($configProdJsonFilePath)) { + throw ('The path of {0} is empty' -f $configProdJsonFilePath) + } + + if(!(Test-Path -Path $configProdJsonFilePath)) { + # create new file + '{}' | out-file -encoding utf8 -filePath $configProdJsonFilePath -Force + } + + $jsonObj = ConvertFrom-Json -InputObject (Get-Content -Path $configProdJsonFilePath -Raw) + # update when there exists one or more connection strings + if ($connectionStrings -ne $null) { + foreach ($name in $connectionStrings.Keys) { + #check for hierarchy style + if ($jsonObj.ConnectionStrings.$name) { + $jsonObj.ConnectionStrings.$name = $connectionStrings[$name] + continue + } + #check for horizontal style + $horizontalName = 'ConnectionStrings.{0}:' -f $name + if ($jsonObj.$horizontalName) { + $jsonObj.$horizontalName = $connectionStrings[$name] + continue + } + # create new one + if (!($jsonObj.ConnectionStrings)) { + $contentForDefaultConnection = '{}' + $jsonObj | Add-Member -name 'ConnectionStrings' -value (ConvertFrom-Json -InputObject $contentForDefaultConnection) -MemberType NoteProperty | Out-Null + } + if (!($jsonObj.ConnectionStrings.$name)) { + $jsonObj.ConnectionStrings | Add-Member -name $name -value $connectionStrings[$name] -MemberType NoteProperty | Out-Null + } + } + } + + $jsonObj | ConvertTo-Json | out-file -encoding utf8 -filePath $configProdJsonFilePath -Force + + #return the path of config.[environment].json + $configProdJsonFilePath + } +} + +<# +.SYNOPSIS + +Inputs: +Example of $providerDataArray: + + [System.Collections.ArrayList]$providerDataArray = @() + + $iisAppSourceKeyValue=@{"iisApp" = @{"path"='c:\temp\pathtofiles';"appOfflineTemplate" ='offline-template.html'}} + $providerDataArray.Add($iisAppSourceKeyValue) + + $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile"}} + $providerDataArray.Add($dbfullsqlKeyValue) + + $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile2"}} + $providerDataArray.Add($dbfullsqlKeyValue) + + Manifest File content: + + + + + + + +#> + +function GenerateInternal-ManifestFile { + [cmdletbinding()] + param( + [Parameter(Mandatory=$true,Position=0)] + [System.IO.FileInfo]$packOutput, + [Parameter(Mandatory=$true,Position=1)] + $publishProperties, + [Parameter(Mandatory=$true,Position=2)] + [System.Collections.ArrayList]$providerDataArray, + [Parameter(Mandatory=$true,Position=3)] + [ValidateNotNull()] + $manifestFileName + ) + process{ + $xmlDocument = [xml]'' + AddInternal-ProviderDataToManifest -xmlDocument $xmlDocument -providerDataArray $providerDataArray | Out-Null + $publishTempDir = GetInternal-PublishTempPath -packOutput $packOutput + $XMLFile = Join-Path $publishTempDir $manifestFileName + $xmlDocument.OuterXml | out-file -encoding utf8 -filePath $XMLFile -Force + + # return + [System.IO.FileInfo]$XMLFile + } +} + +function GetInternal-PublishTempPath { + [cmdletbinding()] + param( + [Parameter(Mandatory=$true, Position=0)] + [System.IO.FileInfo]$packOutput + ) + process { + $tempDir = [io.path]::GetTempPath() + $packOutputFolderName = Split-Path $packOutput -Leaf + $publishTempDir = [io.path]::combine($tempDir,'PublishTemp','obj',$packOutputFolderName) + if (!(Test-Path -Path $publishTempDir)) { + New-Item -Path $publishTempDir -type directory | Out-Null + } + # return + [System.IO.FileInfo]$publishTempDir + } +} + +function Publish-AspNetMSDeploy{ + param( + [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + $publishProperties, + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + $packOutput + ) + process{ + if($publishProperties){ + $publishPwd = $publishProperties['Password'] + + $sharedArgs = GetInternal-SharedMSDeployParametersFrom -publishProperties $publishProperties -packOutput $packOutput + $iisAppPath = $publishProperties['DeployIisAppPath'] + + # create source manifest + + # e.g + # + # + # + # + # + # + + [System.Collections.ArrayList]$providerDataArray = @() + $iisAppValues = @{"path"=$packOutput}; + $iisAppSourceKeyValue=@{"iisApp" = $iisAppValues} + $providerDataArray.Add($iisAppSourceKeyValue) | Out-Null + + if ($sharedArgs.EFMigrationData -ne $null -and $sharedArgs.EFMigrationData.Contains('EFSqlFiles')) { + foreach ($sqlFile in $sharedArgs.EFMigrationData['EFSqlFiles'].Values) { + $dbFullSqlSourceKeyValue=@{"dbFullSql" = @{"path"=$sqlFile}} + $providerDataArray.Add($dbFullSqlSourceKeyValue) | Out-Null + } + } + + [System.IO.FileInfo]$sourceXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'SourceManifest.xml' + + $providerDataArray.Clear() | Out-Null + # create destination manifest + + # e.g + # + # + # + # + # + + $iisAppValues = @{"path"=$iisAppPath}; + if(-not [string]::IsNullOrWhiteSpace($publishProperties['AppOfflineTemplate'])){ + $iisAppValues.Add("appOfflineTemplate", $publishProperties['AppOfflineTemplate']) | Out-Null + } + + $iisAppDestinationKeyValue=@{"iisApp" = $iisAppValues} + $providerDataArray.Add($iisAppDestinationKeyValue) | Out-Null + + if ($publishProperties['EfMigrations'] -ne $null -and $publishProperties['EfMigrations'].Count -gt 0) { + foreach ($connectionString in $publishProperties['EfMigrations'].Values) { + $dbFullSqlDestinationKeyValue=@{"dbFullSql" = @{"path"=$connectionString}} + $providerDataArray.Add($dbFullSqlDestinationKeyValue) | Out-Null + } + } + + + [System.IO.FileInfo]$destXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'DestinationManifest.xml' + + <# + "C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe" + -source:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\SourceManifest.xml' + -dest:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\DestManifest.xml',ComputerName='https://contoso.scm.azurewebsites.net/msdeploy.axd',UserName='$contoso',Password='',IncludeAcls='False',AuthType='Basic' + -verb:sync + -enableRule:DoNotDeleteRule + -retryAttempts=2" + #> + + if(-not [string]::IsNullOrWhiteSpace($publishProperties['MSDeployPublishMethod'])){ + $serviceMethod = $publishProperties['MSDeployPublishMethod'] + } + + $msdeployComputerName= InternalNormalize-MSDeployUrl -serviceUrl $publishProperties['MSDeployServiceURL'] -siteName $iisAppPath -serviceMethod $publishProperties['MSDeployPublishMethod'] + if($publishProperties['UseMSDeployServiceURLAsIs'] -eq $true){ + $msdeployComputerName = $publishProperties['MSDeployServiceURL'] + } + + $publishArgs = @() + #use manifest to publish + $publishArgs += ('-source:manifest=''{0}''' -f $sourceXMLFile.FullName) + $publishArgs += ('-dest:manifest=''{0}'',ComputerName=''{1}'',UserName=''{2}'',Password=''{3}'',IncludeAcls=''False'',AuthType=''{4}''{5}' -f + $destXMLFile.FullName, + $msdeployComputerName, + $publishProperties['UserName'], + $publishPwd, + $publishProperties['AuthType'], + $sharedArgs.DestFragment) + $publishArgs += '-verb:sync' + $publishArgs += $sharedArgs.ExtraArgs + + $command = '"{0}" {1}' -f (Get-MSDeploy),($publishArgs -join ' ') + + if (! [String]::IsNullOrEmpty($publishPwd)) { + $command.Replace($publishPwd,'{PASSWORD-REMOVED-FROM-LOG}') | Print-CommandString + } + Execute-Command -exePath (Get-MSDeploy) -arguments ($publishArgs -join ' ') + } + else{ + throw 'publishProperties is empty, cannot publish' + } + } +} + +function Escape-TextForRegularExpressions{ + [cmdletbinding()] + param( + [Parameter(Position=0,Mandatory=$true)] + [string]$text + ) + process{ + [regex]::Escape($text) + } +} + +function Publish-AspNetMSDeployPackage{ + param( + [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + $publishProperties, + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + $packOutput + ) + process{ + if($publishProperties){ + $packageDestinationFilepath = $publishProperties['DesktopBuildPackageLocation'] + + if(!$packageDestinationFilepath){ + throw ('The package destination property (DesktopBuildPackageLocation) was not found in the publish properties') + } + + if(!([System.IO.Path]::IsPathRooted($packageDestinationFilepath))){ + $packageDestinationFilepath = [System.IO.Path]::GetFullPath((Join-Path $pwd $packageDestinationFilepath)) + } + + # if the dir doesn't exist create it + $pkgDir = ((new-object -typename System.IO.FileInfo($packageDestinationFilepath)).Directory) + if(!(Test-Path -Path $pkgDir)) { + New-Item $pkgDir -type Directory | Out-Null + } + + <# + "C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe" + -source:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\SourceManifest.xml' + -dest:package=c:\temp\path\contosoweb.zip + -verb:sync + -enableRule:DoNotDeleteRule + -retryAttempts=2 + #> + + $sharedArgs = GetInternal-SharedMSDeployParametersFrom -publishProperties $publishProperties -packOutput $packOutput + + # create source manifest + + # e.g + # + # + # + # + + [System.Collections.ArrayList]$providerDataArray = @() + $iisAppSourceKeyValue=@{"iisApp" = @{"path"=$packOutput}} + $providerDataArray.Add($iisAppSourceKeyValue) | Out-Null + + [System.IO.FileInfo]$sourceXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'SourceManifest.xml' + + $publishArgs = @() + $publishArgs += ('-source:manifest=''{0}''' -f $sourceXMLFile.FullName) + $publishArgs += ('-dest:package=''{0}''' -f $packageDestinationFilepath) + $publishArgs += '-verb:sync' + $packageContentFolder = $publishProperties['MSDeployPackageContentFoldername'] + if(!$packageContentFolder){ $packageContentFolder = 'website' } + $publishArgs += ('-replace:match=''{0}'',replace=''{1}''' -f (Escape-TextForRegularExpressions $packOutput), $packageContentFolder ) + $publishArgs += $sharedArgs.ExtraArgs + + $command = '"{0}" {1}' -f (Get-MSDeploy),($publishArgs -join ' ') + $command | Print-CommandString + Execute-Command -exePath (Get-MSDeploy) -arguments ($publishArgs -join ' ') + } + else{ + throw 'publishProperties is empty, cannot publish' + } + } +} + +function Publish-AspNetFileSystem{ + param( + [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + $publishProperties, + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + $packOutput + ) + process{ + $pubOut = $publishProperties['publishUrl'] + + if([string]::IsNullOrWhiteSpace($pubOut)){ + throw ('publishUrl is a required property for FileSystem publish but it was empty.') + } + + # if it's a relative path then update it to a full path + if(!([System.IO.Path]::IsPathRooted($pubOut))){ + $pubOut = [System.IO.Path]::GetFullPath((Join-Path $pwd $pubOut)) + $publishProperties['publishUrl'] = "$pubOut" + } + + 'Publishing files to {0}' -f $pubOut | Write-Output + + # we use msdeploy.exe because it supports incremental publish/skips/replacements/etc + # msdeploy.exe -verb:sync -source:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\SourceManifest.xml' -dest:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\DestManifest.xml' + + $sharedArgs = GetInternal-SharedMSDeployParametersFrom -publishProperties $publishProperties -packOutput $packOutput + + # create source manifest + + # e.g + # + # + # + # + + [System.Collections.ArrayList]$providerDataArray = @() + $contentPathValues = @{"path"=$packOutput}; + $contentPathSourceKeyValue=@{"contentPath" = $contentPathValues} + $providerDataArray.Add($contentPathSourceKeyValue) | Out-Null + + [System.IO.FileInfo]$sourceXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'SourceManifest.xml' + + $providerDataArray.Clear() | Out-Null + # create destination manifest + + # e.g + # + # + # + $contentPathValues = @{"path"=$publishProperties['publishUrl']}; + if(-not [string]::IsNullOrWhiteSpace($publishProperties['AppOfflineTemplate'])){ + $contentPathValues.Add("appOfflineTemplate", $publishProperties['AppOfflineTemplate']) | Out-Null + } + $contentPathDestinationKeyValue=@{"contentPath" = $contentPathValues} + $providerDataArray.Add($contentPathDestinationKeyValue) | Out-Null + + [System.IO.FileInfo]$destXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'DestinationManifest.xml' + + $publishArgs = @() + $publishArgs += ('-source:manifest=''{0}''' -f $sourceXMLFile.FullName) + $publishArgs += ('-dest:manifest=''{0}''{1}' -f $destXMLFile.FullName, $sharedArgs.DestFragment) + $publishArgs += '-verb:sync' + $publishArgs += $sharedArgs.ExtraArgs + + $command = '"{0}" {1}' -f (Get-MSDeploy),($publishArgs -join ' ') + $command | Print-CommandString + Execute-Command -exePath (Get-MSDeploy) -arguments ($publishArgs -join ' ') + + # copy sql script to script folder + if (($sharedArgs.EFMigrationData['EFSqlFiles'] -ne $null) -and ($sharedArgs.EFMigrationData['EFSqlFiles'].Count -gt 0)) { + $scriptsDir = Join-Path $pubOut 'efscripts' + + if (!(Test-Path -Path $scriptsDir)) { + New-Item -Path $scriptsDir -type directory | Out-Null + } + + foreach ($sqlFile in $sharedArgs.EFMigrationData['EFSqlFiles'].Values) { + Copy-Item $sqlFile -Destination $scriptsDir -Force -Recurse | Out-Null + } + } + } +} + +<# +.SYNOPSIS + This can be used to read a publish profile to extract the property values into a hashtable. + +.PARAMETER filepath + Path to the publish profile to get the properties from. Currenlty this only supports reading + .pubxml files. + +.EXAMPLE + Get-PropertiesFromPublishProfile -filepath c:\projects\publish\devpublish.pubxml +#> +function Get-PropertiesFromPublishProfile{ + [cmdletbinding()] + param( + [Parameter(Position=0,Mandatory=$true)] + [ValidateNotNull()] + [ValidateScript({Test-Path $_})] + [System.IO.FileInfo]$filepath + ) + begin{ + Add-Type -AssemblyName System.Core + Add-Type -AssemblyName Microsoft.Build + } + process{ + 'Reading publish properties from profile [{0}]' -f $filepath | Write-Verbose + # use MSBuild to get the project and read properties + $projectCollection = (New-Object Microsoft.Build.Evaluation.ProjectCollection) + if(!([System.IO.Path]::IsPathRooted($filepath))){ + $filepath = [System.IO.Path]::GetFullPath((Join-Path $pwd $filepath)) + } + $project = ([Microsoft.Build.Construction.ProjectRootElement]::Open([string]$filepath.Fullname, $projectCollection)) + + $properties = @{} + foreach($property in $project.Properties){ + $properties[$property.Name]=$property.Value + } + + $properties + } +} + +function Print-CommandString{ + [cmdletbinding()] + param( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] + $command + ) + process{ + 'Executing command [{0}]' -f $command | Write-Output + } +} + +function Execute-CommandString{ + [cmdletbinding()] + param( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] + [string[]]$command, + + [switch] + $useInvokeExpression, + + [switch] + $ignoreErrors + ) + process{ + foreach($cmdToExec in $command){ + 'Executing command [{0}]' -f $cmdToExec | Write-Verbose + if($useInvokeExpression){ + try { + Invoke-Expression -Command $cmdToExec + } + catch { + if(-not $ignoreErrors){ + $msg = ('The command [{0}] exited with exception [{1}]' -f $cmdToExec, $_.ToString()) + throw $msg + } + } + } + else { + cmd.exe /D /C $cmdToExec + + if(-not $ignoreErrors -and ($LASTEXITCODE -ne 0)){ + $msg = ('The command [{0}] exited with code [{1}]' -f $cmdToExec, $LASTEXITCODE) + throw $msg + } + } + } + } +} + +function Execute-Command { + [cmdletbinding()] + param( + [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [String]$exePath, + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + [String]$arguments, + [Parameter(Position=2)] + [System.IO.FileInfo]$workingDirectory + ) + process{ + $psi = New-Object -TypeName System.Diagnostics.ProcessStartInfo + $psi.CreateNoWindow = $true + $psi.UseShellExecute = $false + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError=$true + $psi.FileName = $exePath + $psi.Arguments = $arguments + if($workingDirectory -and (Test-Path -Path $workingDirectory)) { + $psi.WorkingDirectory = $workingDirectory + } + + $process = New-Object -TypeName System.Diagnostics.Process + $process.StartInfo = $psi + $process.EnableRaisingEvents=$true + + # Register the event handler for error + $stdErrEvent = Register-ObjectEvent -InputObject $process -EventName 'ErrorDataReceived' -Action { + if (! [String]::IsNullOrEmpty($EventArgs.Data)) { + $EventArgs.Data | Write-Error + } + } + + # Starting process. + $process.Start() | Out-Null + $process.BeginErrorReadLine() | Out-Null + $output = $process.StandardOutput.ReadToEnd() + $process.WaitForExit() | Out-Null + $output | Write-Output + + # UnRegister the event handler for error + Unregister-Event -SourceIdentifier $stdErrEvent.Name | Out-Null + } +} + + +function GetInternal-DotNetExePath { + process { + $dotnetinstallpath = $env:dotnetinstallpath + if (!$dotnetinstallpath) { + $DotNetRegItem = Get-ItemProperty -Path 'hklm:\software\dotnet\setup\' + if ($env:DOTNET_HOME) { + $dotnetinstallpath = Join-Path $env:DOTNET_HOME -ChildPath 'dotnet.exe' + } + elseif ($DotNetRegItem -and $DotNetRegItem.InstallDir){ + $dotnetinstallpath = Join-Path $DotNetRegItem.InstallDir -ChildPath 'dotnet.exe' + } + } + if (!(Test-Path $dotnetinstallpath)) { + throw 'Unable to find dotnet.exe, please install it and try again' + } + # return + [System.IO.FileInfo]$dotnetinstallpath + } +} + +function Get-MSDeploy{ + [cmdletbinding()] + param() + process{ + $installPath = $env:msdeployinstallpath + + if(!$installPath){ + $keysToCheck = @('hklm:\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\3','hklm:\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\2','hklm:\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\1') + + foreach($keyToCheck in $keysToCheck){ + if(Test-Path $keyToCheck){ + $installPath = (Get-itemproperty $keyToCheck -Name InstallPath -ErrorAction SilentlyContinue | select -ExpandProperty InstallPath -ErrorAction SilentlyContinue) + } + + if($installPath){ + break; + } + } + } + + if(!$installPath){ + throw "Unable to find msdeploy.exe, please install it and try again" + } + + [string]$msdInstallLoc = (join-path $installPath 'msdeploy.exe') + + "Found msdeploy.exe at [{0}]" -f $msdInstallLoc | Write-Verbose + + $msdInstallLoc + } +} + +function InternalNormalize-MSDeployUrl{ + [cmdletbinding()] + param( + [Parameter(Position=0,Mandatory=$true)] + [string]$serviceUrl, + + [string] $siteName, + + [ValidateSet('WMSVC','RemoteAgent','InProc')] + [string]$serviceMethod = 'WMSVC' + ) + process{ + $tempUrl = $serviceUrl + $resultUrl = $serviceUrl + + $httpsStr = 'https://' + $httpStr = 'http://' + $msdeployAxd = 'msdeploy.axd' + + if(-not [string]::IsNullOrWhiteSpace($serviceUrl)){ + if([string]::Compare($serviceMethod,'WMSVC',[StringComparison]::OrdinalIgnoreCase) -eq 0){ + # if no http or https then add one + if(-not ($serviceUrl.StartsWith($httpStr,[StringComparison]::OrdinalIgnoreCase) -or + $serviceUrl.StartsWith($httpsStr,[StringComparison]::OrdinalIgnoreCase)) ){ + + $serviceUrl = [string]::Concat($httpsStr,$serviceUrl.TrimStart()) + } + [System.Uri]$serviceUri = New-Object -TypeName 'System.Uri' $serviceUrl + [System.UriBuilder]$serviceUriBuilder = New-Object -TypeName 'System.UriBuilder' $serviceUrl + + # if it's https and the port was not passed in override it to 8172 + if( ([string]::Compare('https',$serviceUriBuilder.Scheme,[StringComparison]::OrdinalIgnoreCase) -eq 0) -and + -not $serviceUrl.Contains((':{0}' -f $serviceUriBuilder.Port)) ) { + $serviceUriBuilder.Port = 8172 + } + + # if no path then add one + if([string]::Compare('/',$serviceUriBuilder.Path,[StringComparison]::OrdinalIgnoreCase) -eq 0){ + $serviceUriBuilder.Path = $msdeployAxd + } + + if ([string]::IsNullOrEmpty($serviceUriBuilder.Query) -and -not([string]::IsNullOrEmpty($siteName))) + { + $serviceUriBuilder.Query = "site=" + $siteName; + } + + $resultUrl = $serviceUriBuilder.Uri.AbsoluteUri + } + elseif([string]::Compare($serviceMethod,'RemoteAgent',[StringComparison]::OrdinalIgnoreCase) -eq 0){ + [System.UriBuilder]$serviceUriBuilder = New-Object -TypeName 'System.UriBuilder' $serviceUrl + # http://{computername}/MSDEPLOYAGENTSERVICE + # remote agent must use http + $serviceUriBuilder.Scheme = 'http' + $serviceUriBuilder.Path = '/MSDEPLOYAGENTSERVICE' + + $resultUrl = $serviceUriBuilder.Uri.AbsoluteUri + } + else{ + # see if it's for localhost + [System.Uri]$serviceUri = New-Object -TypeName 'System.Uri' $serviceUrl + $resultUrl = $serviceUri.AbsoluteUri + } + } + + # return the result to the caller + $resultUrl + } +} + +function InternalRegister-AspNetKnownPublishHandlers{ + [cmdletbinding()] + param() + process{ + 'Registering MSDeploy handler' | Write-Verbose + Register-AspnetPublishHandler -name 'MSDeploy' -force -handler { + [cmdletbinding()] + param( + [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + $publishProperties, + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + $packOutput + ) + + Publish-AspNetMSDeploy -publishProperties $publishProperties -packOutput $packOutput + } + + 'Registering MSDeploy package handler' | Write-Verbose + Register-AspnetPublishHandler -name 'Package' -force -handler { + [cmdletbinding()] + param( + [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + $publishProperties, + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + $packOutput + ) + + Publish-AspNetMSDeployPackage -publishProperties $publishProperties -packOutput $packOutput + } + + 'Registering FileSystem handler' | Write-Verbose + Register-AspnetPublishHandler -name 'FileSystem' -force -handler { + [cmdletbinding()] + param( + [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + $publishProperties, + [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] + $packOutput + ) + + Publish-AspNetFileSystem -publishProperties $publishProperties -packOutput $packOutput + } + } +} + +<# +.SYNOPSIS + Used for testing purposes only. +#> +function InternalReset-AspNetPublishHandlers{ + [cmdletbinding()] + param() + process{ + $script:AspNetPublishHandlers = @{} + InternalRegister-AspNetKnownPublishHandlers + } +} + +Export-ModuleMember -function Get-*,Publish-*,Register-*,Enable-* +if($env:IsDeveloperMachine){ + # you can set the env var to expose all functions to importer. easy for development. + # this is required for executing pester test cases, it's set by build.ps1 + Export-ModuleMember -function * +} + +# register the handlers so that Publish-AspNet can be called +InternalRegister-AspNetKnownPublishHandlers + diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Rdtsc.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/Rdtsc.cs new file mode 100644 index 0000000..42aeac9 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Rdtsc.cs @@ -0,0 +1,287 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public static class Rdtsc + { + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate ulong FuncUInt64(); + + private const uint PAGE_READWRITE = 0x04; + private const uint PAGE_EXECUTE = 0x10; + private const uint PAGE_EXECUTE_READWRITE = 0x40; + private const uint MEM_COMMIT = 0x1000; + private const uint MEM_RELEASE = 0x8000; + + /// + /// Uses rdtsc. On non-Intel uses Stopwatch.GetTimestamp. + /// + public static readonly FuncUInt64 Timestamp; + + /// + /// Uses rdtscp if present. Otherwise uses cpuid + rdtsc. On + /// non-Intel uses Stopwatch.GetTimestamp. + /// + public static readonly FuncUInt64 TimestampP; + + public static readonly bool IsRdtscSupported; + public static readonly bool IsRdtscPSupported; + + static Rdtsc() + { + SystemInfo systemInfo; + GetNativeSystemInfo(out systemInfo); + + if (systemInfo.wProcessorArchitecture != 0 /* PROCESSOR_ARCHITECTURE_INTEL */&& + systemInfo.wProcessorArchitecture != 9 /* PROCESSOR_ARCHITECTURE_AMD64 */) + { + // Fallback for ARM/IA64/... + Timestamp = StopwatchGetTimestamp; + TimestampP = StopwatchGetTimestamp; + IsRdtscSupported = false; + IsRdtscPSupported = false; + return; + } + + byte[] cpuid, rdtsc, rdtscp, rdtsccpuid, dummy; + + IsRdtscSupported = true; + + // Assembly generated with https://defuse.ca/online-x86-assembler.htm + + /* CPUID x64: + push rbx; + mov eax, 0x80000000; + cpuid; + mov ebx, 0x80000001; + cmp eax, ebx; + jb Error; + mov eax, ebx; + cpuid; + mov eax, ecx; + shl rax, 0x20; + or rax, rdx + jmp End; + Error: + xor rax, rax; + End: + pop rbx; + ret; + + 0: 53 push rbx + 1: b8 00 00 00 80 mov eax,0x80000000 + 6: 0f a2 cpuid + 8: bb 01 00 00 80 mov ebx,0x80000001 + d: 39 d8 cmp eax,ebx + f: 72 0f jb 20 + 11: 89 d8 mov eax,ebx + 13: 0f a2 cpuid + 15: 89 c8 mov eax,ecx + 17: 48 c1 e0 20 shl rax,0x20 + 1b: 48 09 d0 or rax,rdx + 1e: eb 03 jmp 23 + 0000000000000020 : + 20: 48 31 c0 xor rax,rax + 0000000000000023 : + 23: 5b pop rbx + 24: c3 ret + */ + cpuid = new byte[] { 0x53, 0xB8, 0x00, 0x00, 0x00, 0x80, 0x0F, 0xA2, 0xBB, 0x01, 0x00, 0x00, 0x80, 0x39, 0xD8, 0x72, 0x16, 0x89, 0xD8, 0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xA2, 0x89, 0xC8, 0x48, 0xC1, 0xE0, 0x20, 0x48, 0x09, 0xD0, 0xEB, 0x03, 0x48, 0x31, 0xC0, 0x5B, 0xC3 }; + + /* RDTSC x64: + rdtsc; + shl rdx, 0x20; + or rax,rdx; + ret; + + 0: 0f 31 rdtsc + 2: 48 c1 e2 20 shl rdx,0x20 + 6: 48 09 d0 or rax,rdx + 9: c3 ret + */ + rdtsc = new byte[] { 0x0F, 0x31, 0x48, 0xC1, 0xE2, 0x20, 0x48, 0x09, 0xD0, 0xC3 }; + + /* RDTSCP x64 + rdtscp; + shl rdx, 0x20; + or rax, rdx; + ret; + + 0: 0f 01 f9 rdtscp + 3: 48 c1 e2 20 shl rdx,0x20 + 7: 48 09 d0 or rax,rdx + a: c3 ret + */ + rdtscp = new byte[] { 0x0F, 0x01, 0xF9, 0x48, 0xC1, 0xE2, 0x20, 0x48, 0x09, 0xD0, 0xC3 }; + + /* RDTSC + CPUID x64 + push rbx; + xor eax, eax; + cpuid; + rdtsc; + shl rdx, 0x20; + or rax, rdx; + pop rbx; + ret; + + 0: 53 push rbx + 1: 31 c0 xor eax,eax + 3: 0f a2 cpuid + 5: 0f 31 rdtsc + 7: 48 c1 e2 20 shl rdx,0x20 + b: 48 09 d0 or rax,rdx + e: 5b pop rbx + f: c3 ret + */ + rdtsccpuid = new byte[] { 0x53, 0x31, 0xC0, 0x0F, 0xA2, 0x0F, 0x31, 0x48, 0xC1, 0xE2, 0x20, 0x48, 0x09, 0xD0, 0x5B, 0xC3 }; + dummy = new byte[] {0xC3}; + + var buf = IntPtr.Zero; + + try + { + // We pad the functions to 64 bytes (the length of a cache + // line on the Intel processors) + var cpuidLength = (cpuid.Length & 63) != 0 ? (cpuid.Length | 63) + 1 : cpuid.Length; + var rdtscLength = (rdtsc.Length & 63) != 0 ? (rdtsc.Length | 63) + 1 : rdtsc.Length; + var rdtscpLength = (rdtscp.Length & 63) != 0 ? (rdtscp.Length | 63) + 1 : rdtscp.Length; + var rdtsccpuidLength = (rdtsccpuid.Length & 63) != 0 ? (rdtsccpuid.Length | 63) + 1 : rdtsccpuid.Length; + var dummyLength = (dummy.Length & 63) != 0 ? (dummy.Length | 63) + 1 : dummy.Length; + + // We don't know which one of rdtscp or rdtsccpuid we will + // use, so we calculate space for the biggest one. + // Note that it is very unlikely that we will go over 4096 + // bytes (the minimum size of memory allocated by + // VirtualAlloc) + var totalLength = cpuidLength + rdtscLength + Math.Max(rdtscpLength, rdtsccpuidLength)+dummyLength; + + // We VirtualAlloc totalLength bytes, with R/W access + // Note that from what I've read, MEM_RESERVE is useless + // if the first parameter is IntPtr.Zero + buf = VirtualAlloc(IntPtr.Zero, (IntPtr)totalLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + if (buf == IntPtr.Zero) + { + throw new Win32Exception(); + } + + // Copy cpuid instructions in the buf + Marshal.Copy(cpuid, 0, buf, cpuid.Length); + + for (var i = cpuid.Length; i < cpuidLength; i++) + { + Marshal.WriteByte(buf, i, 0x90); // nop + } + + // Copy rdtsc instructions in the buf + Marshal.Copy(rdtsc, 0, buf + cpuidLength, rdtsc.Length); + + for (var i = rdtsc.Length; i < rdtscLength; i++) + { + Marshal.WriteByte(buf, cpuidLength + i, 0x90); // nop + } + var cpuidFunc = Marshal.GetDelegateForFunctionPointer(buf); + + // We use cpuid, EAX=0x80000001 to check for the rdtscp + var supportedFeatures = cpuidFunc(); + + byte[] rdtscpSelected; + int rdtscpSelectedLength; + + // Check the rdtscp flag + if ((supportedFeatures & (1L << 27)) != 0) + { + // rdtscp supported + rdtscpSelected = rdtscp; + rdtscpSelectedLength = rdtscpLength; + IsRdtscPSupported = true; + } + else + { + // rdtscp not supported. We use cpuid + rdtsc + rdtscpSelected = rdtsccpuid; + rdtscpSelectedLength = rdtsccpuidLength; + IsRdtscPSupported = false; + } + + // Copy rdtscp/rdtsccpuid instructions in the buf + Marshal.Copy(rdtscpSelected, 0, buf + cpuidLength + rdtscLength, rdtscpSelected.Length); + + for (var i = rdtscpSelected.Length; i < rdtscpSelectedLength; i++) + { + Marshal.WriteByte(buf, cpuidLength + rdtscLength + i, 0x90); // nop + } + + + // Change the access of the allocated memory from R/W to Execute + uint oldProtection; + var result = VirtualProtect(buf, (IntPtr)totalLength, PAGE_EXECUTE, out oldProtection); + + if (!result) + { + throw new Win32Exception(); + } + + // Create a delegate to the "function" + Timestamp = Marshal.GetDelegateForFunctionPointer(buf + cpuidLength); + TimestampP = Marshal.GetDelegateForFunctionPointer(buf + cpuidLength + rdtscLength); + buf = IntPtr.Zero; + } + finally + { + // There was an error! + if (buf != IntPtr.Zero) + { + // Free the allocated memory + var result = VirtualFree(buf, IntPtr.Zero, MEM_RELEASE); + + if (!result) + { + throw new Win32Exception(); + } + } + } + } + + + [DllImport("kernel32.dll", ExactSpelling = true)] + private static extern void GetNativeSystemInfo(out SystemInfo lpSystemInfo); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + private static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, uint flProtect); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, out uint lpflOldProtect); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, uint dwFreeType); + + // Fallback if rdtsc isn't available. We can't use directly + // Stopwatch.GetTimestamp() because the return type is different. + private static ulong StopwatchGetTimestamp() + { + return unchecked((ulong)Stopwatch.GetTimestamp()); + } + + [StructLayout(LayoutKind.Sequential)] + private struct SystemInfo + { + public readonly ushort wProcessorArchitecture; + public readonly ushort wReserved; + public readonly uint dwPageSize; + public readonly IntPtr lpMinimumApplicationAddress; + public readonly IntPtr lpMaximumApplicationAddress; + public readonly IntPtr dwActiveProcessorMask; + public readonly uint dwNumberOfProcessors; + public readonly uint dwProcessorType; + public readonly uint dwAllocationGranularity; + public readonly ushort wProcessorLevel; + public readonly ushort wProcessorRevision; + } + } +} diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/Tests.cs b/src/DotNetCross.Memory.Copies.Benchmarks2/Tests.cs new file mode 100644 index 0000000..6105df1 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/Tests.cs @@ -0,0 +1,193 @@ +using System; + +namespace DotNetCross.Memory.Copies.Benchmarks2 +{ + public static class Tests + { + private const int BufferSize = 1024*1024*1024; + private static readonly byte[] _src = new byte[BufferSize]; + private static readonly byte[] _dst = new byte[BufferSize]; + + private const int Cached = 0; //Pseudo Random Test + private const int Randomizor = +0x3313751; //Pseudo Random Test + private const int Alignment = 1; //Alignment test + private const int Sequence = -1; //read seq through array + private const int TestMode = Randomizor; //Alignment test + + private const int MinIterations = 50; + public static ulong TestDuration = 1000000; + + public static unsafe int GetOffsetDst() + { + fixed (byte* pSrcOrigin = &_dst[0]) + { + return 0x10 - (int) ((ulong) pSrcOrigin & 0xF); + } + } + + public static unsafe int GetOffsetSrc() + { + fixed (byte* pSrcOrigin = &_src[0]) + { + return 0x10 - (int) ((ulong) pSrcOrigin & 0xF); + } + } + + public static void Warmup() + { + for (var i = 1; i < 256; i++) + { + _src[i] = (byte) i; + _dst[i] = 0; + } + for (var size = 0; size < 32768; size++) + { + AndermanOptimized.Memmove(_src, 0, _dst, 0, size); + for (var j = 0; j < size + 0; j++) + { + if (_src[j] != _dst[j]) + throw new Exception($"i={size}, j={j} _src[j]({_src[j]}) != _dst[j]({_dst[j]})"); + _dst[j] = 0; + } + } + } + + public static double TestOverhead(int offset, int size) + { + var mincycles = ulong.MaxValue; + var startTest = Rdtsc.TimestampP(); + var testCycles = 0UL; + + do + { + ulong cycles = 0; + for (var j = 1; j < 1000; j++) + { + var start = Rdtsc.TimestampP(); + for (var h = 1; h < MinIterations; h++) + { + offset += TestMode == -1 ? size : TestMode; + if (offset + size >= BufferSize) offset &= 0xFFfff; + } + var end = Rdtsc.TimestampP(); + cycles += end - start; + } + + if (cycles < mincycles) mincycles = cycles; + + testCycles = Rdtsc.TimestampP() - startTest; + } while (testCycles < TestDuration && testCycles > 0); + return offset >= 0 ? mincycles/1000.0/MinIterations : 0; + } + + public static double TestAnderman(int offset, int size) + { + var mincycles = ulong.MaxValue; + var startTest = Rdtsc.TimestampP(); + var testCycles = 0UL; + + do + { + var start = Rdtsc.TimestampP(); + for (var j = 1; j < MinIterations; j++) + { + offset += TestMode == -1 ? size : TestMode; + if (offset + size >= BufferSize) offset &= 0xFFfff; + AndermanOptimized.Memmove(_src, offset, _dst, offset, size); + } + var end = Rdtsc.TimestampP(); + var cycles = end - start; + if (cycles <= mincycles) + { + mincycles = cycles; + } + testCycles = Rdtsc.TimestampP() - startTest; + } while (testCycles < TestDuration && testCycles > 0); + return mincycles/(double) MinIterations; + } + + public static double TestArray(int offset, int size) + { + var mincycles = ulong.MaxValue; + var startTest = Rdtsc.TimestampP(); + var testCycles = 0UL; + + do + { + var start = Rdtsc.TimestampP(); + for (var j = 1; j < MinIterations; j++) + { + offset += TestMode == -1 ? size : TestMode; + if (offset + size >= BufferSize) offset &= 0xFFfff; + Array.Copy(_src, offset, _dst, offset, size); + } + var end = Rdtsc.TimestampP(); + var cycles = end - start; + if (cycles <= mincycles) + { + mincycles = cycles; + } + testCycles = Rdtsc.TimestampP() - startTest; + } while (testCycles < TestDuration && testCycles > 0); + return mincycles/(double) MinIterations; + } + + public static double TestMovSb(int offset, int size) + { + var mincycles = ulong.MaxValue; + var startTest = Rdtsc.TimestampP(); + var testCycles = 0UL; + + do + { + var start = Rdtsc.TimestampP(); + for (var j = 1; j < MinIterations; j++) + { + offset += TestMode == -1 ? size : TestMode; + if (offset + size >= BufferSize) offset &= 0xFFfff; + AndermanMovsb.Memmove(_src, offset, _dst, offset, size); + } + var end = Rdtsc.TimestampP(); + var cycles = end - start; + if (cycles <= mincycles) + { + mincycles = cycles; + } + testCycles = Rdtsc.TimestampP() - startTest; + } while (testCycles < TestDuration && testCycles > 0); + return mincycles/(double) MinIterations; + } + + public static double TestDelegate(Func copyAction, int offset, int size) + { + var mincycles = ulong.MaxValue; + var startTest = Rdtsc.TimestampP(); + var testCycles = 0UL; + + do + { + var cycles = copyAction(offset, offset, size); + if (cycles <= mincycles) + { + mincycles = cycles; + } + testCycles = Rdtsc.TimestampP() - startTest; + } while (testCycles < TestDuration && testCycles > 0); + + return mincycles/(double) MinIterations; + } + + public static ulong TestAndermanDelegate(int offset, int size) + { + var start = Rdtsc.TimestampP(); + for (var j = 1; j < MinIterations; j++) + { + offset += TestMode == -1 ? size : TestMode; + if (offset + size >= BufferSize) offset &= 0xFFfff; + AndermanOptimized.Memmove(_src, offset, _dst, offset, size); + } + var end = Rdtsc.TimestampP(); + return end - start; + } + } +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/chart.html b/src/DotNetCross.Memory.Copies.Benchmarks2/chart.html new file mode 100644 index 0000000..697521b --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/chart.html @@ -0,0 +1,36 @@ + + + + chart + + + + + + +
+ + \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks2/project.json b/src/DotNetCross.Memory.Copies.Benchmarks2/project.json new file mode 100644 index 0000000..259edb9 --- /dev/null +++ b/src/DotNetCross.Memory.Copies.Benchmarks2/project.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "optimize": true, + "allowUnsafe": true + }, + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.1" + }, + "System.Runtime.CompilerServices.Unsafe": "4.0.0", + "System.Linq": "4.1.0", + "System.Reflection": "4.1.0", + "Newtonsoft.Json": "9.0.1" + }, + "runtimes": { + "win8-x64": {} + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + } + } + } +} diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/Anderman.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/Anderman.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/Anderman.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/Anderman.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/App.config b/src/old/DotNetCross.Memory.Copies.Benchmarks/App.config similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/App.config rename to src/old/DotNetCross.Memory.Copies.Benchmarks/App.config diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/CopiesBenchmark.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/CopiesBenchmark.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/CopiesBenchmark.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/CopiesBenchmark.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/DotNetCross.Memory.Copies.Benchmarks.csproj b/src/old/DotNetCross.Memory.Copies.Benchmarks/DotNetCross.Memory.Copies.Benchmarks.csproj similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/DotNetCross.Memory.Copies.Benchmarks.csproj rename to src/old/DotNetCross.Memory.Copies.Benchmarks/DotNetCross.Memory.Copies.Benchmarks.csproj diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/Illyriad.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/Illyriad.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/Illyriad.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/Illyriad.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/Msvcrt.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/Msvcrt.cs similarity index 72% rename from src/DotNetCross.Memory.Copies.Benchmarks/Msvcrt.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/Msvcrt.cs index 615176a..c548286 100644 --- a/src/DotNetCross.Memory.Copies.Benchmarks/Msvcrt.cs +++ b/src/old/DotNetCross.Memory.Copies.Benchmarks/Msvcrt.cs @@ -12,17 +12,14 @@ public static unsafe void Memmove(byte[] src, int srcOffset, byte[] dst, int dst if (srcOffset + count > src.Length) throw new ArgumentException(nameof(src)); if (dstOffset + count > dst.Length) throw new ArgumentException(nameof(dst)); - fixed (byte* srcOrigin = src) - fixed (byte* dstOrigin = dst) + fixed (byte* pSrcOrigin = &src[srcOffset]) + fixed (byte* pDstOrigin = &dst[dstOffset]) { - var pSrc = srcOrigin + srcOffset; - var pDst = dstOrigin + dstOffset; - - memmove(pDst, pSrc, count); + memmove(pDstOrigin, pSrcOrigin, count); } } [DllImport("msvcrt.dll", SetLastError = false)] - public static unsafe extern IntPtr memmove(void* dest, void* src, int count); + public static extern unsafe IntPtr memmove(void* dest, void* src, int count); } -} +} \ No newline at end of file diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/Program.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/Program.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/Program.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/Program.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/Properties/AssemblyInfo.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/Properties/AssemblyInfo.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/Properties/AssemblyInfo.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/Properties/AssemblyInfo.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman2.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman2.cs similarity index 52% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman2.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman2.cs index 29cbc55..9a18b82 100644 --- a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman2.cs +++ b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeAnderman2.cs @@ -29,134 +29,92 @@ public static class UnsafeAnderman2 /// Code must be optimized, in release mode and .IsHardwareAccelerated must be true for the /// performance benefits. /// - public static unsafe void VectorizedCopy2(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count) { - if (count > 512 + 64) - { - // TEST: Disable Array-Copy fall back - // In-built copy faster for large arrays (vs repeated bounds checks on Vector.ctor?) - //Array.Copy(src, srcOffset, dst, dstOffset, count); - //return; - } + const int alignment = 0x10; + const int mask = alignment - 1; + + if (count == 0) return; if (src == null || dst == null) throw new ArgumentNullException(nameof(src)); if (srcOffset + count > src.Length) throw new ArgumentException(nameof(src)); - if (dstOffset + count > dst.Length) throw new ArgumentException(nameof(dst)); if (count < 0 || srcOffset < 0 || dstOffset < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (dstOffset + count > dst.Length) throw new ArgumentException(nameof(dst)); + fixed (byte* pSrcOrigin = &src[srcOffset]) + fixed (byte* pDstOrigin = &dst[dstOffset]) { var pSrc = pSrcOrigin; - fixed (byte* pDstOrigin = &dst[dstOffset]) + var pDst = pDstOrigin; + if (count < 16) { - var pDst = pDstOrigin; + if (count < 8) { - if (count >= Vector.Count) + if (count < 4) { - while (count > Vector.Count) + if (count < 2) + { + *(pDst + 0) = *(pSrc + 0); + } + else { - Unsafe.Write(pDst, Unsafe.Read>(pSrc)); - count -= Vector.Count; - pSrc += Vector.Count; - pDst += Vector.Count; + *(short*) pDst = *(short*) pSrc; + *(short*) (pDst + (count & 0xf) - 2) = *(short*) (pSrc + (count & 0xf) - 2); } - pDst += count - Vector.Count; - pSrc += count - Vector.Count; - Unsafe.Write(pDst, Unsafe.Read>(pSrc)); - return; } - - switch (count) + else { - case 15: - { - *(long*)pDst = *(long*)pSrc; - *(long*)(pDst + 7) = *(long*)(pSrc + 7); - return; - } - case 14: - { - *(long*)pDst = *(long*)pSrc; - *(long*)(pDst + 6) = *(long*)(pSrc + 6); - return; - } - case 13: - { - *(long*)pDst = *(long*)pSrc; - *(long*)(pDst + 5) = *(long*)(pSrc + 5); - return; - } - case 12: - { - *(long*)pDst = *(long*)pSrc; - *(int*)(pDst + 8) = *(int*)(pSrc + 8); - return; - } - case 11: - { - *(long*)pDst = *(long*)pSrc; - *(int*)(pDst + 7) = *(int*)(pSrc + 7); - return; - } - case 10: - { - *(long*)pDst = *(long*)pSrc; - *(short*)(pDst + 8) = *(short*)(pSrc + 8); - return; - } - case 9: - { - *(long*)pDst = *(long*)pSrc; - *(pDst + 8) = *(pSrc + 8); - return; - } - case 8: - { - *(long*)pDst = *(long*)pSrc; - return; - } - case 7: - { - *(int*)pDst = *(int*)pSrc; - *(int*)(pDst + 3) = *(int*)(pSrc + 3); - return; - } - case 6: - { - *(int*)pDst = *(int*)pSrc; - *(short*)(pDst + 4) = *(short*)(pSrc + 4); - return; - } - case 5: - { - *(int*)pDst = *(int*)pSrc; - *(pDst + 4) = *(pSrc + 4); - return; - } - case 4: - { - *(int*)pDst = *(int*)pSrc; - return; - } - case 3: - { - *(short*)pDst = *(short*)pSrc; - *(pDst + 2) = *(pSrc + 2); - return; - } - case 2: - { - *(short*)pDst = *(short*)pSrc; - return; - } - case 1: - { - *pDst = *pSrc; - return; - } - case 0: - return; + *(int*) pDst = *(int*) pSrc; + *(int*) (pDst + (count & 0xf) - 4) = *(int*) (pSrc + (count & 0xf) - 4); } } + else + { + *(long*) pDst = *(long*) pSrc; + *(long*) (pDst + (count & 0xf) - 8) = *(long*) (pSrc + (count & 0xf) - 8); + } + return; + } + + pSrc -= (long) pDst; + Unsafe.Write(pDst, Unsafe.Read>(pSrc + (long) pDst)); + + var offset = (int) ((ulong) pDst & mask); + count += offset - alignment; + pDst += alignment - offset; + + while (count >= 4*Vector.Count) + { + var x1 = Unsafe.Read>(pSrc + (long) pDst); + var x2 = Unsafe.Read>(pSrc + (long) pDst + Vector.Count); + count -= 4*Vector.Count; + Unsafe.Write(pDst, x1); + Unsafe.Write(pDst + Vector.Count, x2); + pDst += 4*Vector.Count; + x1 = Unsafe.Read>(pSrc + (long) pDst - 2*Vector.Count); + x2 = Unsafe.Read>(pSrc + (long) pDst - Vector.Count); + Unsafe.Write(pDst - 2*Vector.Count, x1); + Unsafe.Write(pDst - Vector.Count, x2); + } + while (count >= 2*Vector.Count) + { + var x1 = Unsafe.Read>(pSrc + (long) pDst); + var x2 = Unsafe.Read>(pSrc + (long) pDst + Vector.Count); + count -= 2*Vector.Count; + Unsafe.Write(pDst, x1); + Unsafe.Write(pDst + Vector.Count, x2); + pDst += 2*Vector.Count; + } + while (count >= 1*Vector.Count) + { + var x1 = Unsafe.Read>(pSrc + (long) pDst); + count -= Vector.Count; + Unsafe.Write(pDst, x1); + pDst += Vector.Count; + } + if (count > 0) + { + pDst += count - Vector.Count; + Unsafe.Write(pDst, Unsafe.Read>(pSrc + (long) pDst)); } } } @@ -261,14 +219,14 @@ public static unsafe void UnsafeVectorizedCopy2(byte* pDst, byte* pSrc, int coun return; } case 1: - { - *pDst = *pSrc; - return; - } + { + *pDst = *pSrc; + return; + } case 0: - { - return; - } + { + return; + } } } } diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo2.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo2.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo2.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveJamesqo2.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveOriginal.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveOriginal.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveOriginal.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveOriginal.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding2.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding2.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding2.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemmoveTannerGooding2.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemoryCopy.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemoryCopy.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemoryCopy.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeBufferMemoryCopy.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeCpblk.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeCpblk.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeCpblk.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeCpblk.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeIllyriad.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeIllyriad.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeIllyriad.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeIllyriad.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeNietras.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeNietras.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeNietras.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeNietras.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/UnsafeNoChecksCopiesBenchmark.cs b/src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeNoChecksCopiesBenchmark.cs similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/UnsafeNoChecksCopiesBenchmark.cs rename to src/old/DotNetCross.Memory.Copies.Benchmarks/UnsafeNoChecksCopiesBenchmark.cs diff --git a/src/DotNetCross.Memory.Copies.Benchmarks/packages.config b/src/old/DotNetCross.Memory.Copies.Benchmarks/packages.config similarity index 100% rename from src/DotNetCross.Memory.Copies.Benchmarks/packages.config rename to src/old/DotNetCross.Memory.Copies.Benchmarks/packages.config diff --git a/src/old/mov_repsb/AndermanMovsb.cs b/src/old/mov_repsb/AndermanMovsb.cs new file mode 100644 index 0000000..461242b --- /dev/null +++ b/src/old/mov_repsb/AndermanMovsb.cs @@ -0,0 +1,101 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace mov_repsb +{ + public static class AndermanMovsb + { + private const uint PAGE_READWRITE = 0x04; + private const uint PAGE_EXECUTE = 0x10; + private const uint PAGE_EXECUTE_READWRITE = 0x40; + private const uint MEM_COMMIT = 0x1000; + private const uint MEM_RELEASE = 0x8000; + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, out uint lpflOldProtect); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + private static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, uint flProtect); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, uint dwFreeType); + + public delegate int MyMoveSb(ulong RCX, ulong RDX, ulong R8); + + public static MyMoveSb MyAdd; + + static AndermanMovsb() + { + //RAX�=�0000008377040000 RBX�=�0000000000000005 RCX�=�0000000000000004 RDX�=�0000000000000005 RSI�=�0000008300018350 RDI�=�0000000000000004 R8 �=�0000000000000006 R9 �=�000007F9C2460F84 R10�=�0000000000000000 R11�=�0000000000000000 R12�=�00000083754BDF70 R13�=�0000000000000004 R14�=�0000000000000006 R15�=�000000837555A4D0 RIP�=�0000008377040000 RSP�=�00000083754BDDF8 RBP�=�00000083754BDEA0 EFL�=�00000246 + /* + 0: 49 89 fb mov r11,rdi + 3: 49 89 f2 mov r10,rsi + 6: 48 89 ce mov rsi,rcx + 9: 48 89 d7 mov rdi,rdx + c: 4c 89 c1 mov rcx,r8 + f: f3 a4 rep movs BYTE PTR es:[rdi],BYTE PTR ds:[rsi] + 11: 4c 89 d6 mov rsi,r10 + 14: 4c 89 df mov rdi,r11 + 17: c3 ret + */ + byte[] assemblyCode = { 0x49, 0x89, 0xFB, 0x49, 0x89, 0xF2, 0x48, 0x89, 0xCE, 0x48, 0x89, 0xD7, 0x4C, 0x89, 0xC1, 0xF3, 0xA4, 0x4C, 0x89, 0xD6, 0x4C, 0x89, 0xDF, 0xC3 }; + + + // We need to push the code bytes into a native buffer + + var bufPtr = IntPtr.Zero; + + try + { + // Put the sourcecode in a native buffer + bufPtr = VirtualAlloc(IntPtr.Zero, (IntPtr)assemblyCode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + Marshal.Copy(assemblyCode, 0, bufPtr, assemblyCode.Length); + uint oldProtection; + var result = VirtualProtect(bufPtr, (IntPtr)assemblyCode.Length, PAGE_EXECUTE, out oldProtection); + + if (!result) + { + throw new Win32Exception(); + } + MyAdd = Marshal.GetDelegateForFunctionPointer(bufPtr); + bufPtr = IntPtr.Zero; + } + finally + { + // Free the native buffer + if (bufPtr != IntPtr.Zero) + { + var result = VirtualFree(bufPtr, IntPtr.Zero, MEM_RELEASE); + if (!result) + { + throw new Win32Exception(); + } + } + } + } + private const int BufferSize = 1024 * 1024 * 1024; + private static readonly byte[] _src = new byte[BufferSize]; + private static readonly byte[] _dst = new byte[BufferSize]; + + public static unsafe void VectorizedCopyAnderman() + { + for (var i = 0; i < 256; i++) + _src[i] = (byte)i; + fixed (byte* pSrcOrigin = &_src[0]) + fixed (byte* pDstOrigin = &_dst[0]) + { + var pSrc = pSrcOrigin; + var pDst = pDstOrigin; + var result1 = MyAdd((ulong)pSrc, (ulong)pDst,256); + for (var i = 0; i < 256; i++) + Console.WriteLine("Result: {0}", _dst[i]); + Console.ReadKey(); + + } + } + } +} \ No newline at end of file diff --git a/src/old/mov_repsb/Program.cs b/src/old/mov_repsb/Program.cs new file mode 100644 index 0000000..c6c7101 --- /dev/null +++ b/src/old/mov_repsb/Program.cs @@ -0,0 +1,14 @@ +using System.Diagnostics; + +namespace mov_repsb +{ + internal class Program + + { + private static void Main() + + { + AndermanMovsb.VectorizedCopyAnderman(); + } + } +} \ No newline at end of file diff --git a/src/old/mov_repsb/Properties/AssemblyInfo.cs b/src/old/mov_repsb/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1c454eb --- /dev/null +++ b/src/old/mov_repsb/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("mov_repsb")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7720672a-d8a5-4db9-a4f6-810fab98e3cc")] diff --git a/src/old/mov_repsb/mov_repsb.xproj b/src/old/mov_repsb/mov_repsb.xproj new file mode 100644 index 0000000..80bbe97 --- /dev/null +++ b/src/old/mov_repsb/mov_repsb.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 7720672a-d8a5-4db9-a4f6-810fab98e3cc + mov_repsb + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/old/mov_repsb/project.json b/src/old/mov_repsb/project.json new file mode 100644 index 0000000..c66185f --- /dev/null +++ b/src/old/mov_repsb/project.json @@ -0,0 +1,21 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "optimize": true, + "allowUnsafe": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50" + } + } +}