当我发现令人惊讶的事情(对我而言)时,我测试了生成时间戳的不同方法。
使用P / Invoke调用Windows的GetSystemTimeAsFileTime
比调用内部使用CLR的包装器DateTime.UtcNow
的{{1}}调用慢约3倍。
怎么可能?
这是DateTime.UtcNow
's implementation:
GetSystemTimeAsFileTime
核心CLR的wrapper for GetSystemTimeAsFileTime
:
public static DateTime UtcNow {
get {
long ticks = 0;
ticks = GetSystemTimeAsFileTime();
return new DateTime( ((UInt64)(ticks + FileTimeOffset)) | KindUtc);
}
}
[MethodImplAttribute(MethodImplOptions.InternalCall)] // Implemented by the CLR
internal static extern long GetSystemTimeAsFileTime();
我的测试代码使用BenchmarkDotNet:
FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime)
{
FCALL_CONTRACT;
INT64 timestamp;
::GetSystemTimeAsFileTime((FILETIME*)×tamp);
#if BIGENDIAN
timestamp = (INT64)(((UINT64)timestamp >> 32) | ((UINT64)timestamp << 32));
#endif
return timestamp;
}
FCIMPLEND;
结果:
public class Program
{
static void Main() => BenchmarkRunner.Run<Program>();
[Benchmark]
public DateTime UtcNow() => DateTime.UtcNow;
[Benchmark]
public long GetSystemTimeAsFileTime()
{
long fileTime;
GetSystemTimeAsFileTime(out fileTime);
return fileTime;
}
[DllImport("kernel32.dll")]
public static extern void GetSystemTimeAsFileTime(out long systemTimeAsFileTime);
}
答案 0 :(得分:4)
CLR几乎肯定会传递一个指向本地(自动堆栈)变量的指针来接收结果。堆栈不会被压缩或重新定位,因此不需要固定内存等,并且当使用本机编译器时,不支持这样的事情,因此没有开销来计算它们。
在C#中,p / invoke声明与传递生活在垃圾收集堆中的托管类实例的成员兼容。 P / invoke必须固定该实例,否则在OS函数写入期间/之前输出缓冲区移动的风险。即使您确实传递了存储在堆栈中的变量,p / invoke仍然必须测试并查看指针是否进入垃圾收集堆,然后才能分支固定代码,因此即使对于相同的情况也存在非零开销。
使用
可能会获得更好的结果[DllImport("kernel32.dll")]
public unsafe static extern void GetSystemTimeAsFileTime(long* pSystemTimeAsFileTime);
通过消除out
参数,p / invoke不再需要处理别名和堆压缩,现在完全负责设置指针的代码。
答案 1 :(得分:4)
当托管代码调用非托管代码时,会发生堆栈遍历,确保调用代码具有UnmanagedCode权限,从而实现此目的。
堆栈遍历是在运行时完成的,并且性能成本很高。
可以使用SuppressUnmanagedCodeSecurity
attribute删除运行时检查(还有JIT编译时检查):
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll")]
public static extern void GetSystemTimeAsFileTime(out long systemTimeAsFileTime);
这使我的实施大约只有CLR的一半:
Method | Median | StdDev |
------------------------ |---------- |---------- |
GetSystemTimeAsFileTime | 9.0569 ns | 0.7950 ns |
UtcNow | 5.0191 ns | 0.2682 ns |
请记住,尽管这样做可能会带来极其危险的安全性。
同样使用unsafe
作为Ben Voigt建议再次提出它:
Method | Median | StdDev |
------------------------ |---------- |---------- |
GetSystemTimeAsFileTime | 6.9114 ns | 0.5432 ns |
UtcNow | 5.0226 ns | 0.0906 ns |