2013-03-14 66 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