提问人:Yurii Palkovskii 提问时间:1/17/2023 最后编辑:Yurii Palkovskii 更新时间:1/18/2023 访问量:355
C# 性能 - 指向热循环中跨度的指针
C# performance - pointer to span in a hot loop
问:
我正在寻找BitConverter的更快替代品:
但!在“热循环”内部:
//i_k_size = 8 bytes
while (fs.Read(ba_buf, 0, ba_buf.Length) > 0 && dcm_buf_read_ctr < i_buf_reads)
{
Span<byte> sp_data = ba_buf.AsSpan();
for (int i = 0; i < ba_buf.Length; i += i_k_size)
{
UInt64 k = BitConverter.ToUInt64(sp_data.Slice(i, i_k_size));
}
}
我努力将指针与转换集成 - 使性能变得更糟。指针可以用来更快地使用跨度吗?
以下是基准测试:指针 2 数组快 2 倍
实际上,我希望使用此代码而不是BitConverter:
public static int l_1gb = 1073741824;
static unsafe void Main(string[] args)
{
Random rnd = new Random();
Stopwatch sw1 = new();
sw1.Start();
byte[] k = new byte[8];
fixed (byte* a2rr = &k[0])
{
for (int i = 0; i < 1000000000; i++)
{
rnd.NextBytes(k);
//UInt64 p1 = BitConverter.ToUInt64(k);
//time: 10203.824
//time: 10508.981
//time: 10246.784
//time: 10285.889
//UInt64* uint64ptr = (UInt64*)a2rr;
//x2 performance !
UInt64 p2 = *(UInt64*)a2rr;
//time: 4609.814
//time: 4588.157
//time: 4634.494
}
}
Console.WriteLine($"time: {Math.Round(sw1.Elapsed.TotalMilliseconds, 3)}");
}
答:
5赞
Blindy
1/17/2023
#1
假设是一个 ,运行循环的一种非常简单和有效的方法是:ba_buf
byte[]
foreach(var value in MemoryMarshal.Cast<byte, ulong>(ba_buf))
// work with value here
如果您需要对缓冲区进行微调(例如,切断其中的一部分),请先使用它。AsSpan(start, count)
评论
0赞
Yurii Palkovskii
1/18/2023
1073741824转换比 BitConverter.ToUInt64 快 4 秒(我的测试设置)。仍然 - 我不确定这是否比指针跨度更快(如果可能的话)
0赞
Yurii Palkovskii
1/18/2023
我添加了一个 BM 代码来支持我的 oppinion。
2赞
Blindy
1/18/2023
不需要意见,可以直接检查代码。这是最快的。任何其他性能问题都来自有问题的决策,例如对非常大的文件使用流,尤其是对于微小的读取。
0赞
Yurii Palkovskii
1/18/2023
哇!在线disasm太棒了!以前从未见过!谢谢你的链接!
2赞
Matthew Watson
1/18/2023
#2
你可以通过在读取循环之外初始化一些跨度,然后直接读入一个并通过一个访问数据来优化这一点,如下所示:Span<byte>
Span<ulong>
int buf_bytes = sizeof(ulong) * 1024; // Or whatever buffer size you need.
var ba_buf = new byte[buf_bytes];
var span_buf = ba_buf.AsSpan();
var data_span = MemoryMarshal.Cast<byte, ulong>(span_buf);
while (true)
{
int count = fs.Read(span_buf) / sizeof(ulong);
if (count == 0)
break;
for (int i = 0; i < count; i++)
{
// Do something with data_span[i]
Console.WriteLine(data_span[i]); // Put your own processing here.
}
}
这样可以尽可能避免内存分配。当它的数据用完时,它会终止读取循环,如果返回的字节数不是它的倍数,则忽略多余的字节。sizeof(ulong)
它将始终读取所有可用数据,但如果您想提前终止它,您可以添加代码来执行此操作。
例如,考虑以下代码,它将 2,000 个 ulong 值写入文件,然后使用上面的代码将它们读回:
using (var output = File.OpenWrite("x"))
{
for (ulong i = 0; i < 2000; ++i)
{
output.Write(BitConverter.GetBytes(i));
}
}
using var fs = File.OpenRead("x");
int buf_bytes = sizeof(ulong) * 1024; // Or whatever buffer size you need.
var ba_buf = new byte[buf_bytes];
var span_buf = ba_buf.AsSpan();
var data_span = MemoryMarshal.Cast<byte, ulong>(span_buf);
while (true)
{
int count = fs.Read(span_buf) / sizeof(ulong);
if (count == 0)
break;
for (int i = 0; i < count; i++)
{
// Do something with data_span[i]
Console.WriteLine(data_span[i]); // Put your own processing here.
}
}
评论
1赞
Yurii Palkovskii
1/18/2023
对不起 - 起初,我错过了您代码中最重要的一点 - data_span + MemoryMarshal.Cast 我已经测试并编辑了您的方法 - 它是迄今为止2147483648迭代中性能最高的 bm: 1. 我的 BitConverter 代码 = 192 秒 2.foreach(var value in MemoryMarshal.Cast<byte, ulong>(ba_buf)) = 187 秒 3.MemoryMarshal.Cast + 外部缓冲区跨度(您的代码) 183 秒 非常感谢!
评论
while
for
fs.Read
0
ba_buf.Length