C# 无分配枚举和处理

C# 无分配枚举和处理,c#,ref,stackalloc,stack-allocation,C#,Ref,Stackalloc,Stack Allocation,我想解决c#应用程序的巨大分配成本。 应用程序本身可以由底部的TickUser类表示,我想知道如何实现TickStream对象和DoWork和ProcessTick方法来使用无分配数据 using System; using System.Collections.Generic; namespace Ticks { public interface ITick { double Price { get; } double Bid { get; }

我想解决c#应用程序的巨大分配成本。 应用程序本身可以由底部的
TickUser
类表示,我想知道如何实现
TickStream
对象和
DoWork
ProcessTick
方法来使用无分配数据

using System;
using System.Collections.Generic;

namespace Ticks {

    public interface ITick {
        double Price { get; }
        double Bid { get; }
        double Ask { get; }
        double Volume { get; }
        DateTime TimeStamp { get; }
    }
    /// <summary>
    /// Allows allocation-free creation of an <see cref="ITick"/>, 
    /// but is inefficient to pass from method to method because it is large.
    /// </summary>
    public readonly struct Tick : ITick {
        public double Price { get; }
        public double Bid { get; }
        public double Ask { get; }
        public double Volume { get; }
        public DateTime TimeStamp { get; }
        public Tick(double price, double bid, double ask, double volume, DateTime timeStamp) {
            Price = price;
            Bid = bid;
            Ask = ask;
            Volume = volume;
            TimeStamp = timeStamp;
        }
    }
    /// <summary>
    /// Also allows allocation-free creation of an <see cref="ITick"/>,
    /// but this is efficient to pass from method to method because it is small,
    /// containing only a single pointer.
    /// It requires a <see cref="Tick"/> to be created once and guaranteed "pinned" to a location on the stack.
    /// </summary>
    public unsafe readonly struct TickPointer : ITick {

        readonly Tick* Ptr;

        public double Price => Ptr->Price;
        public double Bid => Ptr->Bid;
        public double Ask => Ptr->Ask;
        public double Volume => Ptr->Volume;
        public DateTime TimeStamp => Ptr->TimeStamp;

        public TickPointer(Tick* ptr) {
            Ptr = ptr;
        }
    }
    /// <summary>
    /// Generates the data stream.
    /// </summary>
    public class TickStream {

        /// <summary>
        /// Typically returns hundreds of millions of <see cref="ITick"/> values.
        /// To avoid massive allocation/deallocation costs, we want to yield objects that
        /// can only exist on the stack.
        /// </summary>
        public IEnumerable<ITick> GetTicks() {
            // How to implement??
        }
    }

    public class TickUser {

        public void DoWork() {
            var stream = new TickStream();
            foreach (var tick in stream.GetTicks()) {
                ProcessTick(tick);
            }
        }

        void ProcessTick(ITick tick) {

        }
    }
}
使用系统;
使用System.Collections.Generic;
名称空间标记{
公共接口ITick{
双倍价格{get;}
双重出价{get;}
双重询问{get;}
双卷{get;}
日期时间时间戳{get;}
}
/// 
///允许自由分配创建,
///但是从一个方法传递到另一个方法是低效的,因为它很大。
/// 
公共只读结构勾选:ITick{
公共双价{get;}
公开双标{get;}
公共双询问{get;}
公共双卷{get;}
公共日期时间时间戳{get;}
公开报价(双倍价格、双倍出价、双倍询问、双倍数量、日期时间戳){
价格=价格;
投标=投标;
问=问;
体积=体积;
时间戳=时间戳;
}
}
/// 
///还允许自由分配创建,
///但是这种方法很有效,因为它很小,
///只包含一个指针。
///它需要创建一次并保证“固定”到堆栈上的某个位置。
/// 
公共不安全只读结构滴答指针:ITick{
只读勾选*Ptr;
公开双价=>Ptr->价格;
公开双标=>Ptr->Bid;
公共双询问=>Ptr->Ask;
公共双卷=>Ptr->卷;
公共日期时间戳=>Ptr->TimeStamp;
公共滴答指针(滴答*ptr){
Ptr=Ptr;
}
}
/// 
///生成数据流。
/// 
公共类流{
/// 
///通常返回数亿个值。
///为了避免巨大的分配/解除分配成本,我们希望生成
///只能存在于堆栈上。
/// 
公共IEnumerable GetTicks(){
//如何实施??
}
}
公共类用户{
公共工作{
var stream=新的TickStream();
foreach(stream.GetTicks()中的变量tick){
进程勾号(勾号);
}
}
无效进程勾选(ITick勾选){
}
}
}
TickStream
类中,我可以删除
IEnumerable GetTicks()
方法,并将其替换为
MoveNext()
/
Current{get;}
/
Current()
模式


也许
ref readonly TickPointer Current()

好的,我找到了它,并用许多备选方案进行了测试。 事实证明,如果总是小心地使用
in
参数传递勾号,那么将勾号创建为
ref readonly struct
在所有方面都能提供最佳性能

基本代码

using System;

namespace Ticks {

    /// <summary>
    /// Allows allocation-free creation of an <see cref="ITick"/>, but is inefficient to pass from method to method because it is large.
    /// </summary>
    public readonly ref struct Tick {
        public static Tick Empty => new Tick(0, 0, 0, 0, DateTime.MinValue);
        public bool IsEmpty => Price == 0;
        public double Price { get; }
        public double Bid { get; }
        public double Ask { get; }
        public double Volume { get; }
        public DateTime TimeStamp { get; }
        public Tick(double price, double bid, double ask, double volume, DateTime timeStamp) {
            Price = price;
            Bid = bid;
            Ask = ask;
            Volume = volume;
            TimeStamp = timeStamp;
        }
    }
    /// <summary>
    /// Also allows allocation-free creation of an <see cref="ITick"/>, but this is efficient to pass from method to method because it is small,
    /// containing only a single pointer.
    /// It requires a <see cref="Tick"/> to be created once and guaranteed "pinned" to a location on the stack.
    /// </summary>
    public unsafe readonly ref struct TickPointer {

        readonly Tick* Ptr;

        public bool IsEmpty => Ptr->IsEmpty;
        public double Price => Ptr->Price;
        public double Bid => Ptr->Bid;
        public double Ask => Ptr->Ask;
        public double Volume => Ptr->Volume;
        public DateTime TimeStamp => Ptr->TimeStamp;

        public TickPointer(Tick* ptr) {
            Ptr = ptr;
        }
    }

    /// <summary>
    /// A reference-type implementation of the tick data for tests involving allocations.
    /// </summary>
    sealed class AllocatedTick {
        public readonly double Price;
        public readonly double Bid;
        public readonly double Ask;
        public readonly double Volume;
        public readonly DateTime TimeStamp;
        public AllocatedTick(double price, double bid, double ask, double volume, DateTime timeStamp) {
            Price = price;
            Bid = bid;
            Ask = ask;
            Volume = volume;
            TimeStamp = timeStamp;
        }
    }
}
sealed class TestTickStream {

    public readonly int NumTicks;

    readonly DateTime DummyTime = DateTime.MinValue;

    int count = 0;

    public TestTickStream(int numTicks) {
        NumTicks = numTicks;
    }

    public bool MoveNext(ref Tick tick) {
        if (++count > NumTicks) return false;
        tick = new Tick(count, count, count, count, DummyTime);
        return true;
    }
}

/// <summary>
/// A stream that returns <see cref="AllocatedTick"/> ticks for tests involving allocations
/// </summary>
sealed class AllocatedTickStream {

    public readonly int NumTicks;

    readonly DateTime DummyTime = DateTime.MinValue;

    int count = 0;

    public AllocatedTickStream(int numTicks) {
        NumTicks = numTicks;
    }

    public AllocatedTick MoveNext() {
        if (++count > NumTicks) return null;
        return new AllocatedTick(count, count, count, count, DummyTime);
    }
}
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using Ticks;

namespace TIcks.Benchmarks {

    [MemoryDiagnoser]
    public class AllocationAndPassingBenchmarks {

        /// <summary>
        /// The number of ticks a stream should generate.
        /// </summary>
        [Params(1000, 100000, 10000000)]
        public int NumTicks { get; set; }

        /// <summary>
        /// The depth of methods that each tick should be processed through. To simulate methods calling methods.
        /// </summary>
        [Params(1, 10, 100)]
        public int MethodPassingDepth { get; set; }

        [Benchmark]
        public void TickPassedWithInParameter() {
            var stream = new TestTickStream(NumTicks);
            var tick = Tick.Empty;
            while (stream.MoveNext(ref tick)) {
                var x = Process(tick, 0);
            }

            Tick Process(in Tick tick, int depth) {
                return depth == MethodPassingDepth ? tick : Process(tick, depth + 1);
            }
        }

        [Benchmark]
        public void TickPassedWithNothing() {
            var stream = new TestTickStream(NumTicks);
            var tick = Tick.Empty;
            while (stream.MoveNext(ref tick)) {
                var x = Process(tick, 0);
            }

            Tick Process(Tick tick, int depth) {
                return depth == MethodPassingDepth ? tick : Process(tick, depth + 1);
            }
        }

        [Benchmark]
        public unsafe void TickPassedAsPointer() {
            var stream = new TestTickStream(NumTicks);
            var tick = Tick.Empty;
            while (stream.MoveNext(ref tick)) {
                var tickPointer = new TickPointer(&tick);
                var x = Process(tickPointer, 0);
            }

            TickPointer Process(TickPointer tickPointer, int depth) {
                return depth == MethodPassingDepth ? tickPointer : Process(tickPointer, depth + 1);
            }
        }

        [Benchmark]
        public void AllocatedTicks() {
            var stream = new AllocatedTickStream(NumTicks);
            for (var tick = stream.MoveNext(); tick is object; tick = stream.MoveNext()) {
                var x = Process(tick, 0);
            }

            AllocatedTick Process(AllocatedTick tick, int depth) {
                return depth == MethodPassingDepth ? tick : ProcessSlowTick(tick, depth + 1);
            }
        }
    }

    [MemoryDiagnoser]
    public class MemberAccessBenchmarks {

        [Params(100, 1000)]
        public int AccessCount { get; set; }

        [Benchmark]
        public void AccessTickMembers() {
            double price, bid, ask, volume;
            DateTime time;
            var tick = new Tick(1, 1, 1, 1, DateTime.Now);
            for (var i = 0; i < AccessCount; i++) {
                price = tick.Price;
                bid = tick.Bid;
                ask = tick.Ask;
                volume = tick.Volume;
                time = tick.TimeStamp;
            }
        }

        [Benchmark]
        public unsafe void AccessTickPointerMembers() {
            double price, bid, ask, volume;
            DateTime time;
            var tick = new Tick(1, 1, 1, 1, DateTime.Now);
            var tickPointer = new TickPointer(&tick);
            for (var i = 0; i < AccessCount; i++) {
                price = tickPointer.Price;
                bid = tickPointer.Bid;
                ask = tickPointer.Ask;
                volume = tickPointer.Volume;
                time = tickPointer.TimeStamp;
            }
        }
    }

    class Program {
        static void Main(string[] args) {
            var summary1 = BenchmarkRunner.Run<AllocationAndPassingBenchmarks>();
            var summary2 = BenchmarkRunner.Run<MemberAccessBenchmarks>();
        }
    }

}
分配和传递结果(用于确定从一个方法到另一个方法传递滴答声的最佳方式,以及在没有高分配的情况下从流中快速消耗数亿滴答声)


总而言之,以下是我找到的对勾号进行编码的最佳方法:

public readonly ref struct Tick {
    public double Price { get; }
    public double Bid { get; }
    public double Ask { get; }
    public double Volume { get; }
    public DateTime TimeStamp { get; }
    public Tick(double price, double bid, double ask, double volume, DateTime timeStamp) {
        Price = price;
        Bid = bid;
        Ask = ask;
        Volume = volume;
        TimeStamp = timeStamp;
    }
}
以下是我找到的对流进行编码的最佳方法:

sealed class TestTickStream {

    public readonly int NumTicks;

    readonly DateTime DummyTime = DateTime.MinValue;

    int count = 0;

    public TestTickStream(int numTicks) {
        NumTicks = numTicks;
    }

    public bool MoveNext(ref Tick tick) {
        if (++count > NumTicks) return false;
        tick = new Tick(count, count, count, count, DummyTime);
        return true;
    }
}
public void ProcessAllTicks() {
    var stream = new TestTickStream(NumTicks);
    var tick = new Tick();
    while (stream.MoveNext(ref tick)) {
        Process(tick);
    }

    void Process(in Tick tick) {
        // do semething
    }
}
以下是我发现的使用流的最佳方式:

sealed class TestTickStream {

    public readonly int NumTicks;

    readonly DateTime DummyTime = DateTime.MinValue;

    int count = 0;

    public TestTickStream(int numTicks) {
        NumTicks = numTicks;
    }

    public bool MoveNext(ref Tick tick) {
        if (++count > NumTicks) return false;
        tick = new Tick(count, count, count, count, DummyTime);
        return true;
    }
}
public void ProcessAllTicks() {
    var stream = new TestTickStream(NumTicks);
    var tick = new Tick();
    while (stream.MoveNext(ref tick)) {
        Process(tick);
    }

    void Process(in Tick tick) {
        // do semething
    }
}

如果你能展示一些改进代码的方法,请分享

拳击应该预防。。。我可能需要去掉
ITick
接口?你可能想看看
Span
和ref结构,我只是不知道如何在没有编译器问题的情况下到达那里,因此这里的问题。非常感谢。
sealed class TestTickStream {

    public readonly int NumTicks;

    readonly DateTime DummyTime = DateTime.MinValue;

    int count = 0;

    public TestTickStream(int numTicks) {
        NumTicks = numTicks;
    }

    public bool MoveNext(ref Tick tick) {
        if (++count > NumTicks) return false;
        tick = new Tick(count, count, count, count, DummyTime);
        return true;
    }
}
public void ProcessAllTicks() {
    var stream = new TestTickStream(NumTicks);
    var tick = new Tick();
    while (stream.MoveNext(ref tick)) {
        Process(tick);
    }

    void Process(in Tick tick) {
        // do semething
    }
}