DllImport或LoadLibrary哪个性能更好?

11

我有一个包含快速汇编代码的外部.DLL文件。调用这个.DLL文件中的函数以获得最佳性能的最佳方法是什么?


1
这些方法是被很少调用的大型方法,还是从托管代码中经常被调用的精简方法? - Oliver
3
我记得SharpDX的开发人员分析了DLLImport生成的代码,发现最大的性能问题是某种(不必要的)参数检查。因此,他们使用Reflection.Emit()来生成与DLLImport相同但没有这些检查的代码,从而提高了性能。我认为这是其中一位创作者的博客文章,但我目前找不到它。 - Oliver
@Olivier,例如这些方法填充1024字节的缓冲区。 - apocalypse
5个回答

24

您的DLL可能是用Python或C++编写的,无论哪种语言,请按照以下步骤操作:

这是您在C++中的DLL文件。

头文件:

extern "C" __declspec(dllexport) int MultiplyByTen(int numberToMultiply);

源代码文件

#include "DynamicDLLToCall.h"

int MultiplyByTen(int numberToMultiply)
{
    int returnValue = numberToMultiply * 10;
    return returnValue;
} 

请看下面的C#代码:

static class NativeMethods
{
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);
}

class Program
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int MultiplyByTen(int numberToMultiply);

    static void Main(string[] args)
    {
            IntPtr pDll = NativeMethods.LoadLibrary(@"PathToYourDll.DLL");
            //oh dear, error handling here
            //if (pDll == IntPtr.Zero)

            IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "MultiplyByTen");
            //oh dear, error handling here
            //if(pAddressOfFunctionToCall == IntPtr.Zero)

            MultiplyByTen multiplyByTen = (MultiplyByTen)Marshal.GetDelegateForFunctionPointer(
                                                                                    pAddressOfFunctionToCall,
                                                                                    typeof(MultiplyByTen));

            int theResult = multiplyByTen(10);

            bool result = NativeMethods.FreeLibrary(pDll);
            //remaining code here

            Console.WriteLine(theResult);
    }
} 

2
【来源和背景】(https://blogs.msdn.microsoft.com/jonathanswift/2006/10/03/dynamically-calling-an-unmanaged-dll-from-net-c/) - tm1

3
我认为DllImport和LoadLibrary有不同的目的。如果你使用本地.dll文件,应该使用DllImport。如果你使用.NET程序集,应该使用LoadAssembly。
实际上,你也可以动态加载本地程序集,看这个例子: dynamically-calling-an-unmanaged-dll-from-.net

实际上,您也可以动态加载本机程序集。@zgnilec 已经知道这一点了。 - David Heffernan
链接已损坏。 - Cinder Biscuits

3
假设您的目标平台与本地dll相同。您可以使用DLLImport调用LoadLibrary并使用LoadLibrary将本地dll加载到您的进程中。然后使用DllImport调用GetProcAddress。
接下来,您可以为您想要调用的所有导出方法定义委托。
然后,您可以使用Marshal.GetDelegateForFunctionPointer从GetProcAddress设置委托。
创建一个静态类,在构造函数中执行此操作一次。然后,您可以调用您的委托以调用dll中导出的原生函数,而不必在所有内容上使用DllImport。更加简洁,我相信它会更快,并且可能会完全绕过前面提到的参数检查。
因此,您将拥有缓慢的初始化,但是一旦加载,速度将非常快。我还没有测试过。
这是来自我的来源的博客。

http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx


2

回答这个问题的唯一方法是对两个选项进行计时,这是一个极为简单的任务。没有计时的性能预测是毫无意义的。

由于我们没有您的代码,只有您自己才能回答您的问题。


我会检查一下,也许我的问题没有表达清楚,我是在问如何使用外部DDL的不同方法。我只“知道”这两种方法。我在某个地方读到DllImport在内部执行了一些操作,例如为垃圾回收器固定托管对象等。我认为有一种特殊的方式来调用本地汇编方法。如果我制作一个提供函数y = x ^ 2(仅为示例)的dll,然后使用DllImport调用它,DllImport机制将占用99%的CPU,而我的汇编函数仅占用1%,那么使用外部dll就没有意义了 :/ - apocalypse

1

我进行了一个快速测试。向下滚动查看结论。

标题:

struct Vector2
{
public:
    float X;
    float Y;

    float GetMagnitude() const;
};

extern "C" __declspec(dllexport) float GetMagnitude(const Vector2& InVector);

来源:

#include <cmath>
float Vector2::GetMagnitude() const
{
    return sqrt((X * X) + (Y * Y));
}

管理:

// #define IMPORT // <-- comment/uncomment this to switch

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;

namespace InteropTest
{
    public struct Vector2
    {
        public Vector2(float x, float y)
        {
            (_x, _y) = (x, y);
        }
    
        private float _x;
        private float _y;
    }
    [SuppressUnmanagedCodeSecurity]
    internal class Program
    {
#if IMPORT        
        [DllImport("InteropLibrary", CallingConvention = CallingConvention.Cdecl,
            CharSet = CharSet.Ansi)]
        private static extern float GetMagnitude(ref Vector2 vector);

#else

        [DllImport("kernel32")]
        public static extern IntPtr LoadLibrary(
            string path);

        [DllImport("kernel32")]
        public static extern IntPtr GetProcAddress(
            IntPtr libraryHandle,
            string symbolName);

        [DllImport("kernel32")]
        public static extern bool FreeLibrary(
            IntPtr libraryHandle);

        private static IntPtr LibraryHandle;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl,
            CharSet = CharSet.Ansi)]
        private delegate float GetMagnitudeDelegate(ref Vector2 vector2);

        private static GetMagnitudeDelegate GetMagnitude;

#endif

        public static void Main(string[] args)
        {
#if !IMPORT
            LibraryHandle = LoadLibrary("./InteropLibrary.dll");
            IntPtr symbol = GetProcAddress(LibraryHandle, "GetMagnitude");
            GetMagnitude = Marshal.GetDelegateForFunctionPointer(
                symbol,
                typeof(GetMagnitudeDelegate)) as GetMagnitudeDelegate;
#endif

            var random = new Random(234);
            var sw = new Stopwatch();
            sw.Start();
            {
                for (var i = 0; i < 1000000; i++)
                {
                    var vector = new Vector2(random.Next(400), random.Next(400));
                    GetMagnitude(ref vector);
                }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            sw = null;
            random = null;
#if !IMPORT
            CloseLibrary(LibraryHandle);
            LibraryHandle = IntPtr.Zero;
            GetMagnitude = null;
#endif
        }
    }
}

结论

手动加载/卸载DLL的方式大约比DllImport慢20%。 在不同尝试中,DllImport需要约99-105毫秒。 在不同尝试中,Marshal.GetDelegateForFuncitonPointer需要约120-125毫秒。


我自己进行了一些测试,在我的情况下,手动加载方法和使用 DllImport 属性几乎没有什么区别。为了简单起见,我会继续使用 DllImport - baltermia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接