不安全的C#技巧以提高速度

13

我不习惯使用指针(例如C ++),也不熟悉不安全的编程:只用“安全”的C#。 现在,我想在 .Net Micro Framework 中实现一个函数,其中紧凑性和性能非常重要。 基本上,我想要收集四个short类型的值,并将它们存入缓冲区(例如字节数组)。 假设每个样本都是这样的:

struct MyStruct
{
    public short An1;
    public short An2;
    public short An3;
    public short An4;
}

每个样本都是通过计时器事件收集的,所以我不能使用循环(有几个原因)。 我尝试了许多有效的方法来做到这一点,但似乎性能最好的是这个:

unsafe struct MyStruct2
{
    public fixed byte Buffer[Program.BufferSize];
}


unsafe class Program
{
    public const int BufferSize = 0x1000;
    public const int ArraySize = BufferSize / 8;

    static MyStruct2 _struct2 = new MyStruct2();
    static MyStruct* _structPtr;


    unsafe static void Main(string[] args)
    {
        int iter = 5000;  //just for simulate many cycles

        for (int i = 0; i < iter; i++)
        {
            //let's make a trick!
            fixed (byte* ptr = _struct2.Buffer)
                _structPtr = (MyStruct*)ptr;

            _structIndex = 0;
            do
            {
                Test5();
            } while (++_structIndex < ArraySize);
        }


        Console.ReadKey();
    }


    unsafe static void Test5()
    {
        _structPtr->An1 = (short)An1();
        _structPtr->An2 = (short)An2();
        _structPtr->An3 = (short)An3();
        _structPtr->An4 = (short)An4();
        _structPtr++;
    }


    //simulations of ADC reading
    static int An1()
    {
        return 0x1111;
    }

    static int An2()
    {
        return 0x2222;
    }

    static int An3()
    {
        return 0x3333;
    }

    static int An4()
    {
        return 0x4444;
    }
}

相对于以下更加安全的方式,改进并不是很大(177ms对比224ms),但仍然是显著的。

    static MyStruct Test3()
    {
        var data = new MyStruct();
        data.An1 = (short)An1();
        data.An2 = (short)An2();
        data.An3 = (short)An3();
        data.An4 = (short)An4();
        return data;
    }

注意:我已经删除了一些代码,但我认为它足够清楚。

我的问题是:通过将“fixed”指针复制到另一个未固定的指针所做的“技巧”是否可靠?...但您可以假设所有数据都是静态分配的,因此应该已固定。 提前致谢。 干杯


2
你有没有在 JIT 编译一次的情况下进行测量?我的意思是,你是否尝试运行托管函数一次,然后在循环中对其进行测量?JIT 编译只会发生一次,因此如果将其包含在时间总和中,则与不安全的方式进行比较会产生不良影响。 - jdehaan
1
为什么AnX方法返回int?不能让它们返回short并跳过强制转换吗?这样会提高性能吗?此外,从fixed语句中取出托管缓冲区的指针似乎是一个非常糟糕的想法。我不确定,但如果GC妨碍了它可能会有问题。 - R. Martinho Fernandes
4
@jde - 这里不存在抖动,IL被解释执行。放弃对效率的所有假设。 - Hans Passant
@Mario:fixed 开始一个语句块。你的“块”只包含一个语句,即指针的赋值。该块的目的是为整个块固定托管内存。之后,托管内存可以被 GC 移动。我对微型框架一无所知,因此在那个环境中可能并不重要。 - Tergiver
这很清楚,但是......假设我从未销毁我的_struct2对象,那么什么时候可以进行垃圾回收呢?我猜编译器无法保证它被固定,但实际上它确实被固定了。关于微框架,这些事情没有任何区别。 - Mario Vernari
显示剩余7条评论
3个回答

1

我认为这段代码不安全。在固定范围内的_structPtr = (MyStruct*)ptr之后,您继续假设_struct2不会移动并将数据放入_structPtr中。虽然您正确地指出它不会被GC回收,但这并不意味着GC不会在内存压缩期间移动它。.NET Compact Framework仍然进行垃圾回收,我认为它会压缩内存而不是使其碎片化。

例如,如果在_struct2之前在堆上分配的短暂(非静态)对象被GC删除,则该结构使用的内存可能会移动到该短暂对象使用的空闲空间中。此时,_structPtr指向未使用的内存。

将Test3()修改为采用ref MyStruct data是否有帮助?

此外,请查看[StructLayout(LayoutKind.Explicit)][FieldOffset(...)],这将允许您拥有一个单一的结构体,其中包含多种访问相同数据的方式。在您的情况下,可以作为4个字节或1个int或(可能)1个由4个字节组成的数组。


"我认为代码不安全"。双重否定是否有意是不明确的。 - Chris Pitman
谢谢Chris。太晚了,写作有些困难。接下来会进行编辑。 - Josh Gallagher
好主意:我没有尝试使用“ref”。另一个想法是使用“long”。我不能使用“StructLayout”,因为它在微框架库中不可用。无论如何,谢谢。 - Mario Vernari
StructLayout是Micro Framework的一部分。http://msdn.microsoft.com/zh-cn/library/ee434399.aspx - Ritch Melton

0

我认为你的“技巧”不是问题。没有人关心你如何索引内存,或者你使用多少偏移量来完成它。但我认为你需要听取他人的建议,并确保你使用StructLayout.Sequential或.Explicit来控制结构体的布局。同时注意Size和Pack选项。

另一个问题是,你需要在fixed块中完成所有工作,这一点也已经被提到过了。

fixed (byte* ptr = struct2.Buffer)
{
    var structPtr = (MyStruct*)ptr;
    var structIndex = 0;
    do
    {
        Test5(structPtr);
    } while (++structIndex < ArraySize);
}

个人认为,您已经陷入了一些微观优化的困境,最好使用安全的C#代码。根据您提供的数字,(224x10^-3 - 177x10^-3)得出47ms,除以5000次迭代,每次迭代净得9.4us(假设Windows此时没有执行其他任务)。


1
微型框架是标准框架的一个微小子集,专门为小型设备(时钟慢、RAM/ROM较少)而设计。我展示的时间是在台式电脑上测量的(相当快),但它只是性能比例的指示性数据。我不能像你建议的那样使用闭环,因为我的CPU太慢了。我想唯一合理的答案就是创建一个C++本地自定义驱动程序。 - Mario Vernari
我不知道你所说的闭环是什么意思,但如果你有如此严格的实时要求(在 ~1ms 以下),我怀疑 'C++' 并不能帮助你太多。祝你好运。 - Ritch Melton

0
看看这个示例,它来自不同的帖子,关于我广泛使用的一些代码。你的想法是正确的,只需要设置FieldOffset()值,然后在访问数据时使用fixed()关键字。
这种方法非常可靠,但并不真正更快。我使用它的原因是,当我有很多字段要作为数组访问时,我想要一种快速的方式。

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