我使用这里的实现对.NET和C#中的StringReader类进行了一些研究:https://referencesource.microsoft.com/#mscorlib/system/io/stringreader.cs
我制作了一个小类,我认为它使用了相同的基本实现来读取字符串,但令我惊讶的是,我的代码速度是.NET StringReader的两倍多。
这是我的课:
public class DataReader
{
private String source;
private int pos;
private int length;
public DataReader(string data)
{
source = data;
length = source.Length;
}
public int Peek()
{
if (pos == length) return -1;
return source[pos];
}
public int Read()
{
if (pos == length) return -1;
return source[pos++];
}
}
这是我的测试代码:
using System;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var s = new String('x', 10000000);
StringReaderTest(s);
DataReaderTest(s);
}
private static void StringReaderTest(string s)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var reader = new StringReader(s);
while (reader.Peek() > -1)
{
reader.Read();
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
private static void DataReaderTest(string s)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var reader = new DataReader(s);
while (reader.Peek() > -1)
{
reader.Read();
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
}
这是整个事情的一个.NET版本。https://dotnetfiddle.net/MqbU5q
这是Fiddle的输出。我的实现速度慢了一倍。
77
159
我一定错过了什么,有人能解释一下吗?
因此,首先,Stopwatch
不是一个合法的基准测试工具,它不合适的原因有很多。
您应该使用BenchmarkDotNet或类似的工具,预预热,pre-JIT的在每次运行前收集垃圾,多次运行测试,并在调试时提醒您。
以下是如何生成更可靠基准的示例。
免责声明:在一个好的基准测试中,还有更多的事情需要推理,比如使用更真实的实际数据和用例表示
测试代码
[SimpleJob(RuntimeMoniker.Net60, baseline: true)]
public class ReaderTest
{
private const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private string _s;
public static Random R = new(42);
[Params(1000, 10000, 100000)] public int N;
[GlobalSetup]
public void Setup() => _s = new string(Enumerable.Repeat(Chars, N).Select(s => s[R.Next(s.Length)]).ToArray());
public static void Main() => BenchmarkRunner.Run<ReaderTest>();
[Benchmark]
public int StringReader()
{
var stringReader = new StringReader(_s);
var result = 0;
while (stringReader.Peek() > -1)
result ^= stringReader.Read();
return result;
}
[Benchmark]
public int DataReader()
{
var dataReader = new DataReader(_s);
var result = 0;
while (dataReader.Peek() > -1)
result ^= dataReader.Read();
return result;
}
}
基准
环境
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1348 (21H2)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100
[Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT [AttachedDebugger]
.NET 6.0 : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
Job=.NET 6.0 Runtime=.NET 6.0
结果
方法 | 平均值 | |||||||
---|---|---|---|---|---|---|---|---|
StringReader | 1000 | 3.424 us | >td style="text align:right||||||
1000 | 1.459 us | 0.0126 us | 0.0098 us | >StringReader | 1000 | 34.157 us | >td style="text align:right||
DataReader | 1000 | 0.0577 us | StringReader | 100000 | 348.650 us | 6.7857 us | >7.2606 us||
DataReader | 100000 | 146.257 us |