_BitScanForward in C#?
_BitScanForward in C#?
我正在将一个用C++编写的程序翻译成C#,我遇到了一个无法处理的内部函数。在C++中,这被称为:
unsigned char _BitScanForward(unsigned long * Index, unsigned long Mask);
如果我只知道内部函数所在的DLL(如果有的话),我就可以使用p/Invoke。由于我不知道,我在.NET框架中寻找替代方案,但我一无所获。
有人知道如何在_BitScanForward上使用p/Invoke,或者一个做同样事情的.NET方法吗?
感谢您的帮助。
内部函数不在任何库中,它们在CPU内部实现,编译器发出机器代码,CPU将其识别为引发这种特定行为。
它们是一种访问指令的方式,这些指令没有简单的C等价物。
在.NET优化器变得足够聪明来识别它们之前(例如,Mono JIT识别一些SIMD指令,这些指令在MSIL中编码为对特定类函数的调用,类似地,.NET JIT用浮点运算取代了对System.Math方法的调用),您的C#代码的运行速度注定会比原始C++慢一个数量级。
哇,似乎有一个关于C#的问题还没有得到最近的改进。
其他评论者已经恰当地注意到,像_BitScanForward这样的内部函数本身并不是函数,而是编译器将特定平台指令注入目标代码的标记。在高级语言中模仿内在是不可能的(除非你愿意付出抽象代价)。然而,好消息是,从.Net Core 3.0开始,JIT确实支持许多硬件平台的内部功能。
对于_BitScanForward,可以使用System.Runtime.Intrinsics.X86.Bmi1.TrailingZeroCount.
注意事项:在使用之前不要忘记检查Bmi1.IsSupported
,否则代码在运行时会失败。
你也可以通过使用他们的ffs内部函数在ARM(.Net 5.0+)上获得不错的执行速度:
public int ArmBitScanForward(int x)
=> 32 − System.Runtime.Intrinsics.Arm.ArmBase.LeadingZeroCount(x & −x);
public int ArmBitScanForward(long x)
=> 64 − System.Runtime.Intrinsics.Arm.ArmBase.Arm64.LeadingZeroCount(x & −x);
如果两个平台都不存在,你将不得不求助于像de Bruijun序列这样的小技巧:
for i from 0 to 31: table[ ( 0x077CB531 * ( 1 << i ) ) >> 27 ] ← i // table [0..31] initialized
function ctz5 (x)
return table[((x & -x) * 0x077CB531) >> 27]
(取自https://en.wikipedia.org/wiki/Find_firstronget)
根据任务限制,我会在运行时选择不同的算法选择策略。每次调用时分支可能会破坏所有效率。最有效的方法是在更高的级别上进行分支,即在运行时有三个版本的代码可供选择。自动化代码生成的一个简单方法是将代码从参数化为通用代码,并使用位处理类型:
public interface IBitScanner
{
int BitScanForward(int x);
}
public int MyFunction<T>(int[] data)
where T: new, IBitScanner
{
var s=0;
var scanner = new T();
foreach(var i in data)
s+= scanner.BitScanForward(i);
return s;
}
然后,我们定义了几个实现扫描仪的结构:
public struct BitScannerX86: IBitScanner
{
public int BitScanForward(int x)
=> unchecked((int)System.Runtime.Intrinsics.X86.Bmi1.TrailingZeroCount((uint)x));
}
public struct BitScannerArm: IBitScanner
{
public int BitScanForward(int x)
=> 32 − System.Runtime.Intrinsics.Arm.ArmBase.LeadingZeroCount(x & −x);
}
public struct BitScanner: IBitScanner
{
private static int[] _table = InitTable();
private static int[] InitTable()
{
var table = new int[32];
for(var i=0; i<table.Length; i++)
table[i] = ( 0x077CB531 * ( 1 << i ) ) >> 27;
return table;
}
public int BitScanForward(int x)
=> _table[((x & -x) * 0x077CB531) >> 27]
}
现在,每当我们需要特定于平台的MyFunction版本时,我们都可以通过CCD_ 2。作为结构体,type参数强制JIT为其生成特定的代码,而不是幻想虚拟调用的泛型代码。然后,正如JIT时已知的T一样,对BitScanForward的调用被内联,并最终在循环中注入适当的内部函数。根据MyFunction任务的大小,此版本的MyFunction可能保存到委托、接口的一部分或实现接口的结构的一部分,以在更高级别上重复该技巧。
请注意,最初的问题并没有涉及跨平台兼容性,因为_BitScanForward是英特尔独有的指令。在针对特定OS&HW组合;像Java/.Net这样的现代托管代码有机会在任何地方执行。
_BitScanForward
C++函数是一个内部编译器函数。它在字节序列中查找上的第一个,从最低阶位到最高阶位进行搜索,并返回该位的值。您可能可以在C#中使用位操作策略来实现类似的功能(尽管它永远不会达到相同的性能)。如果你对C++中的位操作感到满意,那么它在C#中基本上是一样的。
_BitScanForward
搜索整数中的第一个设置位,从最低有效位开始搜索最高有效位。它在x86平台上编译为bsf
指令。
这个"小技巧"页面包括一些在不同情况下都很出色的潜在替代算法。有一个O(N)函数(均匀分布输入的一半时间只需一次迭代即可返回)和一些亚线性选项,还有一些使用乘法步骤。挑选一个可能不是微不足道的,但任何一个都应该有效。
不可能p/Invoke_BitScanForward,因为它是编译器内部函数,而不是实际的库函数(它由Visual C++编译器转换为BSF x86机器指令)。据我所知,这个"查找第一集"操作没有MSIL指令。最简单的方法是编写自己的C++本机DLL,导出一个调用_BitScanForward()的函数,然后P/Invoke。
您也可以使用位操作直接在C#中编写它(请参阅维基百科中查找第一集的算法)。我不确定这会比P/Invoke快还是慢。测量并找出。
- netcat command in c++
- Difference in displaying cv2 Mat
- C++ MFC Libraries in Travis CI
- 如何在OpenSSL中从configuration.h.in获取configuration.h
- 创建具有 new in 函数和"this is nullptr"异常的对象
- IN, OUT, INOUT Parameters
- 应用程序崩溃并显示"symbol _ZdlPvm, version Qt_5 not defined in file libQt5Core.so.5 with link time reference"
- 有人安装"IITB Simplecpp in mac"吗?
- 从 C 样式字符串中删除子字符串 "in place" 在C++代码中
- 如何修复"error: ‘_1’ was not declared in this scope"?
- Softmax Implementation in C++
- 将 out/in out 参数与 if/switch 的 init 语句一起使用
- IF-nesting in c++
- Gurobi GRBModel and GRBmodel in C++
- Tensorflow Hub in C++
- Centos7 g++ "to_string is not in a member of std"
- InitializeCriticalSectionEx Not Located In KERNEL32.Dll
- 将 lambda 表达式传递给 std::function in C++
- @CPPFLAGS@在 Makefile.in 中意味着什么?
- std::async from std::async in windows xp