C# 无分配枚举和处理
我想解决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; }
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
}
}