【问题标题】:How to improve efficiency of loop如何提高循环效率
【发布时间】:2020-03-02 08:04:46
【问题描述】:

我正在尝试将我的数据保存到 csv 文件。当速率很小(保存 2,000/s)时,它工作得很好。但是当增加到20,000 / s时,它的工作速度很慢。

class Channel 
{
    List<double> RawData { get; set; }
    ...
}

-----------------------------------

var channels = new List<Channel>();
// after fetch the data
var sw = new StreamWriter(FileStream, Encoding.Default);
for (i = 0; channels.First().RawData.Count; i ++)
{
    string line = DateTime.Now.ToString() + ",";
    line += string.Join(',', channels.Select(c => c.RawData[i]));
    sw.WriteLine(line);
    sw.Flush();
}

当每个channelRawData 数量达到20,000 时,应用程序将运行缓慢。有没有办法加快line的生成速度?

【问题讨论】:

  • 嗯,我建议使用 3 方库(如 CSV 助手)或至少使用一些引号或分隔符,在其他文化中不会是小数分隔符。并且不要忘记大数字可能会使用数千个分隔符
  • 性能 wize ,不清楚。如果必须为每一行完成DateTime.Now,我会建议使用计时器并像这个问题一样计算滴答声:Why are DateTime.Now DateTime.UtcNow so slow/expensive?。并避免在紧密循环中进行任何类型的字符串连接line +=,字符串构建器应该在那里更好。

标签: c# loops


【解决方案1】:

您可以采取一些措施来加快速度。

通常,I/O 操作代价高昂(最慢),因此请尽量避免执行大量操作,而是只编写一次或几次。

多次执行此操作时,修改字符串也很慢。因此,您应该使用 StringBuilder。

最后,在我看来,您使用的日期时间在许多情况下(但可能不是全部!)都是相同的值。如果是这种情况,那么您可以只调用一次,然后输出相同的值。

您还可以加快频道 Select 语句的速度(根据其他帖子)。

那么你就会有这样的代码:

StringBuilder sb = new StringBuilder();
string dtValue = DateTime.Now.ToString();

for (int i = 0; channels.First().RawData.Count; i++)
{
    sb.Append(dtValue).Append(",").Append(channels.First().RawData[i]).Append(Environment.NewLine);    
}

//Write just once.
using (var sw = new StreamWriter(FileStream, Encoding.Default))
{
    sw.Writeline(sb.ToString());
    sw.Close();
}

编辑:更新为根据 cmets 使用 channels.First().RawData[i]。

【讨论】:

  • 唯一的问题是这种方法将所有数据读取到内存中。第一个问题是它可能导致 OutOfMemory(对于大型数据集)。另一个 - 在读取最后一个元素之前它不会开始写入文件。
  • @oleksa 我同意你所说的。但是根据提供的信息,无法知道内存是否会出现问题,并且没有提到需要读取刚刚写入的数据。我确实在文中提到“写一次或几次”,因此 OP 需要相应地进行调整。只能解决所提出的问题。
  • 此代码将IEnumerable 提供给StringBuilder.Append。我相信您的意思是:Append(channels.First().RawData[i]).
  • @jason.kaisersmith 感谢您的帮助!我没有意识到 I/O 和字符串连接会占用太多时间。实际上,这个模块每一个周期重复一次,并且在每个周期之后都会处理数据。内存足够了。我检查了我的代码,我使用了太多未编译的代码,如下面的@oleksa 所说,例如channels.First()channel.RawData 确实是一个仅实现getter 以按比例转换原始数据的属性。最后,问题解决了。谢谢你的帮助!!
【解决方案2】:

您可以使用Write(string) 方法并且不要连接字符串。

另外,StreamWriter.Flush 方法不应在每次迭代中调用。

for (i = 0; channels.First().RawData.Count; i ++) 未编译且不应使用,因为string.Join(',', channels.Select(c =&gt; c.RawData[i])); 将遍历所有项目。

下面的示例代码不做串联,也不读取内存中的所有数据。它分别将每个通道的通道数据写入流。当然这需要一些时间,而且你拥有的数据越多,它的工作时间就越长。

var channels = new List<Channel>();
// after fetch the data
using (var sw = new StreamWriter(FileStream, Encoding.Default)) // you have to check that both stream writer and `FileStream` instances are disposed properly 
{
foreach (var ch in channels)
{
sw.Write(string.Format("{0:ddMMyyyy hh:mm:ss},", DateTime.Now)); // it writes date time for every channel data 
foreach (var data in ch.RawData)
{
    sw.Write(string.Format("{0},", data.ToString(Culture.InvariantCulture)); // double.ToString() is culture specific so you can use Culture.InvariantCulture when converting double to string
}
sw.WriteLine(""); // last line break but note that all channel data are written in one line separated with commas
}
}

主要思想不是将所有通道 RawData 列表项连接到字符串。 将 20000 个双精度数转换为一个字符串肯定需要额外的时间和内存。

它只是将 RawData 项一一写入文件并节省时间和内存(我希望)

【讨论】:

    猜你喜欢
    • 2015-10-08
    • 2019-12-05
    • 2019-11-06
    • 2018-04-18
    • 1970-01-01
    • 2012-01-04
    • 2019-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多