使用AsciiEncoding.GetBytes和Convert.FromBase64String解码字节并写入FileStream.Write时出现性能问题

4
我在使用FileStream.Write函数时遇到了性能问题。
我有一个控制台应用程序,使用StreamReader对象从文件中读取Base64字符串(大小约为400 KB)。我使用Convert.FromBase64String将该字符串转换为字节数组。然后,我使用FileStream对象将此字节数组写入文件。这里获得的字节数组长度为334991。
我测量了写入字节数组所需的时间,大约为0.116秒
仅仅出于好玩,我使用ASCIIEncoding.GetBytes函数从相同的Base64编码字符串获取字节数组(即使我知道这不会给出正确的解码输出-我只是想试一下)。我使用FileStream对象将此字节数组写入文件。这里获得的字节数组长度为458414。
我测量了使用这种方法写入字节数组所需的时间,大约为0.008秒
以下是示例代码:
class Program
{
    static void Main(string[] args)
    {
        Stopwatch stopWatch = new Stopwatch();
        TimeSpan executionTime;

        StreamReader sr = new StreamReader("foo.txt");
        string sampleString = sr.ReadToEnd();
        sr.Close();

        ////1. Convert to bytes using Base64 Decoder (The real output!)
        //byte[] binaryData = Convert.FromBase64String(sampleString);

        //2. Convert to bytes using AsciiEncoding (Just for Fun!)
        byte[] binaryData = new System.Text.ASCIIEncoding().GetBytes(sampleString);
        Console.WriteLine("Byte Length: " + binaryData.Length);

        stopWatch.Start();
        FileStream fs = new FileStream("bar.txt", FileMode.Create, FileAccess.Write);
        fs.Write(binaryData, 0, binaryData.Length);
        fs.Flush();
        fs.Close();
        stopWatch.Stop();

        executionTime = stopWatch.Elapsed;
        Console.WriteLine("FileStream Write - Total Execution Time: " + executionTime.TotalSeconds.ToString());
        Console.Read();
    }
}

我对大约5000个包含Base64编码字符串的文件进行了测试,使用 Convert.FromBase64String 函数获取的字节数组长度小于使用 ASCIIEncoding.GetBytes 函数获取的字节数组长度。在将这两种类型的字节数组写入文件时,时间差异几乎是10倍(使用真实解码写入字节数组的时间更长)。
我想知道,我所做的就是使用 FileStream 对象写入一堆字节,为什么在将字节数组写入磁盘时会有如此明显的性能差异(时间方面)?
或者我做错了什么吗?请给予建议。
4个回答

1

首先,DateTime 的分辨率很低(如果我没记错的话是 0.018 秒)。因此最好使用一个 Stopwatch 类。

现在这并不能完全解释差异,但你可能会得到一些更好的数字。


对的,Henk,我也注意到了,但是却被抖动问题困扰住了 ;) - RandomNickName42
谢谢Henk。我修改了测试代码,使用了StopWatch类,更新后的信息现在反映在原始帖子中。 - amit-agrawal

1

我给另一个问题提供了一些类似的建议,请查看来自MS Research的这些工具和参考资料。

它们将帮助您解决任何潜在的I/O问题,或者至少让您理解它们。

此外,您应该注意CLR 大对象堆周围的问题。特别是在使用数组时(任何超过~80kb的内容都会有次优的托管堆交互,如果您在同一进程中运行了5000次)。

然而,实际上,在再次查看后,我不认为这些与您的引理密切相关。我在分析器中运行了此代码,它只显示Convert.Base64正在消耗您所有的周期。

在编写测试代码时,你应该总是连续运行测试2次以上,这样抖动就有机会影响运行负载。这可能会导致执行时间的差异非常惊人。我认为你现在应该重新评估你的测试工具,尝试考虑抖动和可能的大对象堆效应。(将其中一个例程放在另一个例程之前...)。


1

我认为你的代码主要问题在于你试图比较卷心菜和胡萝卜(法语表达):

Convert.FromBase64String和ASCIIEncoding().GetBytes根本不是同一回事。

只需尝试使用任何文本文件作为输入到您的程序,它将在FromBase64上失败,而在ASCIIEncoding上运行良好。

现在解释一下性能损失的原因:

  • ASCIIEncoding().GetBytes只是从您的文件中取一个字符并将其转换为字节(这相当直接:没有什么可做的)。例如,它将'A'翻译成0x41,'Z'翻译成0x5A...

  • 对于Convert.FromBase64String,情况就不同了。它实际上是将“base64编码的字符串”转换为字节数组。 base64字符串是二进制数据的“可打印表示形式”。更好的是,它是二进制数据的“文本”表示形式,允许将其发送到互联网线路上。邮件中的图像是base64编码的,因为邮件协议是基于文本的。因此,将base64转换为字节的过程并不简单;因此会影响性能。

顺便提一下,一个base64字符串应该长这样:

SABlAGwAbABvAHcAIABXAG8AcgBsAGQAIQA=

它翻译成“Hello World!”并不是立即的,对吧?

关于base64格式的信息如下:http://en.wikipedia.org/wiki/Base64

希望这有所帮助。


嗨,Odalet,如果你注意到上面的话,时间测量并不包括将base64字符串转换为字节数组-所以我不确定你对性能影响的解释是否正确。而且我已经提到我正在比较卷心菜和胡萝卜,只是为了突出一个性能相关的问题。 - amit-agrawal
非常抱歉,我犯了一个错误:忘记了它,事实上我确实没有仔细阅读你的问题... - odalet

0

你可能希望查看Jon Skeet最近关于此问题编写的一系列文章(以及相应的源项目)

这里这里

具体来说,他正在比较缓冲与流传输,但使用不同的文件大小和线程计数也能得到有趣的结果。


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