Here's how to get a file extension comparing P/Invoke Win32 API Native PathFindExtension with .NET IO.Path.GetExtension method.
Analysis
P/Invoke PathRemoveExtension on 1st run is 'slow' due to overhead and loading metadata into cache. On successive runs, this drops down to 4 ticks.
Likewise, Path.GetExtension on initial load is 'slow' to load metadata in cache. On successive runs this is 2 ticks.
Using PrepareMethod to pre-cache a method call but the call costed to much, therefore for preformance gain, just call the method twice.
Run results
Source Code (can't use online .NET Fiddle for P/Invoke calls)
Analysis
P/Invoke PathRemoveExtension on 1st run is 'slow' due to overhead and loading metadata into cache. On successive runs, this drops down to 4 ticks.
Likewise, Path.GetExtension on initial load is 'slow' to load metadata in cache. On successive runs this is 2 ticks.
Using PrepareMethod to pre-cache a method call but the call costed to much, therefore for preformance gain, just call the method twice.
Run results
Built-in Path.IO Get File Extension - 1st run exe in ticks 14 Built-in Path.IO Get File Extension - 2nd run exe in ticks 2 P/Invoke PathFindExtension - DOES NOT WORK? C:\Windows\write.exe in ticks 1554 PrepareMethod Path.IO GetFileExtension Encapsulated Method - 1st run exe in ticks 3969 Call Path.IO GetFileExtension Encapsulated Method - 2nd run exe in ticks 4 P/Invoke PathRemoveExtension - 1st run exe in ticks 131 P/Invoke PathRemoveExtension - 2nd run exe in ticks 4 P/Invoke PathRemoveExtension - 3rd run exe in ticks 4
Source Code (can't use online .NET Fiddle for P/Invoke calls)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Reflection; namespace GetFileExtensionPInvoke { class Program { public static class MethodWarmerUper { public static void WarmUp(string methodName) { var handle = FindMethodWithName(methodName).MethodHandle; RuntimeHelpers.PrepareMethod(handle); } private static MethodInfo FindMethodWithName(string methodName) { return Assembly.GetExecutingAssembly() .GetTypes() .SelectMany(type => type.GetMethods(MethodBindingFlags)) .FirstOrDefault(method => method.Name == methodName); } private const BindingFlags MethodBindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; } public static class IOMethod { public static string GetFileExtension(string s) { return Path.GetExtension(s); } } // <summary> /// Removes the file name extension from a path, if one is present. /// </summary> /// <param name="pszFile">A pointer to a null-terminated string of length MAX_PATH from which to remove the extension.</param> [DllImport("shlwapi.dll", EntryPoint = "PathRemoveExtensionW", SetLastError = true, CharSet = CharSet.Unicode)] public static extern void PathRemoveExtension([MarshalAs(UnmanagedType.LPWStr)]System.Text.StringBuilder pszFile); //https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-pathfindextensionw // <summary> /// Returns the address of the "." that precedes the extension within pszPath if an extension is found, or the address of the terminating null character otherwise. /// </summary> /// <param name="pszFile">A pointer to a null-terminated string of length MAX_PATH from which to remove the extension.</param> [DllImport("shlwapi.dll", EntryPoint = "PathFindExtensionW", SetLastError = true, CharSet = CharSet.Unicode)] public static extern void PathFindExtension([MarshalAs(UnmanagedType.LPWStr)]System.Text.StringBuilder pszFile); static void Main(string[] args) { string filepath = @"C:\Windows\write.exe"; string ext = string.Empty; StringBuilder sbExt = new StringBuilder(filepath); Stopwatch sw = new Stopwatch(); Console.WriteLine("\nBuilt-in Path.IO Get File Extension - 1st run"); sw.Start(); ext = Path.GetExtension(filepath); sw.Stop(); ext = ext.TrimStart('.'); //add 1 tick Console.WriteLine(ext + " in ticks " + sw.ElapsedTicks); sw.Reset(); Console.WriteLine("Built-in Path.IO Get File Extension - 2nd run"); sw.Start(); ext = Path.GetExtension(filepath); sw.Stop(); ext = ext.TrimStart('.'); //add 1 tick Console.WriteLine(ext + " in ticks " + sw.ElapsedTicks); sw.Reset(); Console.WriteLine("\nP/Invoke PathFindExtension - DOES NOT WORK?"); sw.Start(); PathFindExtension(sbExt); //Console.Error.WriteLine("Get Win32 Error = "+ Marshal.GetLastWin32Error()); sw.Stop(); Console.WriteLine(sbExt.ToString() + " in ticks " + sw.ElapsedTicks); Console.WriteLine(""); sw.Reset(); Console.WriteLine("PrepareMethod Path.IO GetFileExtension Encapsulated Method - 1st run"); sw.Start(); MethodWarmerUper.WarmUp("GetFileExtension"); //very poor performance sw.Stop(); ext = ext.TrimStart('.'); //add 1 tick Console.WriteLine(ext + " in ticks " + sw.ElapsedTicks); sw.Reset(); Console.WriteLine("Call Path.IO GetFileExtension Encapsulated Method - 2nd run"); sw.Start(); ext = IOMethod.GetFileExtension(filepath); sw.Stop(); ext = ext.TrimStart('.'); //add 1 tick Console.WriteLine(ext + " in ticks " + sw.ElapsedTicks); sw.Reset(); Console.WriteLine(""); sbExt = new StringBuilder(filepath); Console.WriteLine("P/Invoke PathRemoveExtension - 1st run"); sw.Start(); PathRemoveExtension(sbExt); ext = sbExt.ToString(); ext = filepath.Replace(ext,""); ext = ext.TrimStart('.'); //add 1 tick sw.Stop(); Console.WriteLine(ext + " in ticks " + sw.ElapsedTicks); sw.Reset(); sbExt = new StringBuilder(filepath); Console.WriteLine("P/Invoke PathRemoveExtension - 2nd run"); sw.Start(); PathRemoveExtension(sbExt); ext = sbExt.ToString(); ext = filepath.Replace(ext, ""); ext = ext.TrimStart('.'); //add 1 tick sw.Stop(); Console.WriteLine(ext + " in ticks " + sw.ElapsedTicks); sw.Reset(); sbExt = new StringBuilder(filepath); Console.WriteLine("P/Invoke PathRemoveExtension - 3rd run"); sw.Start(); PathRemoveExtension(sbExt); ext = sbExt.ToString(); ext = filepath.Replace(ext, ""); ext = ext.TrimStart('.'); //add 1 tick sw.Stop(); Console.WriteLine(ext + " in ticks " + sw.ElapsedTicks); sw.Reset(); Console.ReadKey(); } } }
No comments:
Post a Comment