2013-03-14 78 views
0

我正在尋找一種可以快速將非託管字符串轉換爲託管字符串的函數。我在尋找Marshal.PtrToStringAnsi,但它真的很慢。PtrToStrAnsi更快的替代?

我在.NET框架源代碼請參見以下定義:

public static String PtrToStringAnsi(IntPtr ptr) 
{ 
    if (Win32Native.NULL == ptr) { 
     return null; 
    } 
    else if (IsWin32Atom(ptr)) { 
     return null; 
    } 
    else { 
     int nb = Win32Native.lstrlenA(ptr); 
     if(nb == 0) { 
      return string.Empty; 
     } 
     else { 
      StringBuilder sb = new StringBuilder(nb); 
      Win32Native.CopyMemoryAnsi(sb, ptr, new IntPtr(1+nb)); 
      return sb.ToString(); 
     } 
    } 
} 

爲了提高我的應用程序的性能我創建的使用Marshal.PtrToStringAnsi(IntPtr的,int)方法下面的方法速度要快得多。

[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, EntryPoint = "lstrlenA")] 
[ResourceExposure(ResourceScope.None)] 
internal static extern int lstrlenA(IntPtr ptr); 

static public string PtrToString(IntPtr p) 
{ 
    if (p == IntPtr.Zero) 
     return null; 
    int len = lstrlenA(p); 
    if (len == 0) 
     return string.Empty; 
    return Marshal.PtrToStringAnsi(p, len); 
} 

這種方法似乎要快得多。微軟是不是首先編寫PtrToStringAnsi函數的原因是什麼?我可能錯過了一些重要的...

+0

這是不一樣的功能。第二種方法調用版本PtrToStringAnsi(IntPtr,int)而不是PtrToStringAnsi(IntPtr) – 2013-03-14 13:32:14

+0

你可以顯示基準程序 – 2013-03-14 13:32:45

+0

@DavidHeffernan:https://gist.github.com/anonymous/5161459 – 2013-03-14 13:51:06

回答

1

區別在於撥打IsWin32Atom。你的版本忽略了這一點。當你把它放回去時,你會發現你的版本可以和Marshal中的版本相媲美。即使您刪除對IsWin32Atom的呼叫,性能增益也可以忽略不計。

我的測試程序的版本是這樣的:

using System; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 
using System.Runtime.Versioning; 

namespace Test 
{ 
    internal class Program 
    { 
     [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, EntryPoint = "lstrlenA")] 
     [ResourceExposure(ResourceScope.None)] 
     internal static extern int lstrlenA(IntPtr ptr); 

     [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 
     private static extern System.IntPtr GetCommandLine(); 

     private static readonly IntPtr HIWORDMASK = unchecked(new IntPtr((long)0xffffffffffff0000L)); 

     private static bool IsWin32Atom(IntPtr ptr) 
     { 
      long num = (long)ptr; 
      return 0 == (num & (long)HIWORDMASK); 
     } 

     public static string PtrToString(IntPtr p) 
     { 
      if (p == IntPtr.Zero) 
       return null; 
      if (IsWin32Atom(p)) 
       return null; 
      int len = lstrlenA(p); 
      if (len == 0) 
       return String.Empty; 
      return Marshal.PtrToStringAnsi(p, len); 
     } 

     private static void Main(string[] args) 
     { 
      var p = Marshal.StringToHGlobalAnsi("Console.WriteLine(\"Marshal class: result={0} time={1}ms\", s, sw.ElapsedMilliseconds);"); 

      string s = ""; 
      Stopwatch sw = new Stopwatch(); 
      sw.Start(); 
      for (double i = 0; i < 5000000; i++) 
      { 
       s = Marshal.PtrToStringAnsi(p); 
      } 
      sw.Stop(); 
      Console.WriteLine("Marshal class: result={0} time={1}ms", s, sw.ElapsedMilliseconds); 
      sw.Restart(); 
      for (double i = 0; i < 5000000; i++) 
      { 
       s = Program.PtrToString(p); 
      } 
      sw.Stop(); 
      Console.WriteLine("My implementation: result={0} time={1}ms", s, sw.ElapsedMilliseconds); 
      Console.ReadLine(); 
     } 
    } 
} 

運行時從試驗運行變化的公平位。但這裏有一個典型的輸出:

 
Marshal class: result=Console.WriteLine("Marshal class: result={0} time={1}ms", 
    s, sw.ElapsedMilliseconds); time=1914ms 
My implementation: result=Console.WriteLine("Marshal class: result={0} time={1}ms", 
    s, sw.ElapsedMilliseconds); time=2065ms 

但是有時它會出現相反的情況。總之,兩者之間沒有什麼可選擇的。

當您刪除對IsWin32Atom的呼叫時,那麼您的版本往往會贏得更多。但不是太多。速度通常約有5%的差異。我不知道你爲什麼認爲Marshal.PtrToStringAnsi是「非常慢」。

我非常期待Marshal.PtrToStringAnsi的兩個參數版本本質上是你的代碼的else條款。

注意:我的測試環境是Win7 x64,VS2012,AnyCPU,Release。

+0

我剛剛完成了測試,這不是真的 – 2013-03-14 14:21:28

+0

+1感謝您對這一項的研究。我複製了你的代碼並驗證了你的結果。 .NET 4.5,Visual Studio 2012,發佈模式,不附加調試器。 – 2013-03-14 14:23:25

+0

@DavidHeffernan:奇怪,我運行完全相同的代碼,我得到版本1的56s和版本2的50s – 2013-03-14 14:28:55