【问题标题】:Logging the memory usage of an object记录对象的内存使用情况
【发布时间】:2012-01-13 08:00:47
【问题描述】:

可能有 5 或 6 个 SO 帖子与此相关,但没有一个真正回答这个问题。

我有一个 Dictionary 对象,我将其用作缓存来存储值。问题是我不知道它有多大——随着时间的推移,它可能会变大或不会变大,但我无法判断,因此我无法衡量它的有效性,也无法就用户如何使用该软件做出结论.因为这是一个将投入生产并在很长一段时间内监控某些东西的部分,所以附加内存分析器或任何类似的调试工具是没有意义的。

理想情况下,我只需在我的 Timer 中拨打一个电话,它会执行以下操作:

private void someTimer_Tick(object sender, EventArgs e)
{
   ...
   float mbMem = cacheMap.GetMemorySize();
   RecordInLog(DateTime.Now.ToString() + ": mbMem is using " + mbMem.ToString() + "MB of memory");
   ...
}

这可以在不附加一些调试工具的情况下完成,以便可以在部署场景中使用吗?

【问题讨论】:

  • 也许你已经看到了,但以防万一——你可以做序列化近似stackoverflow.com/questions/605621/…
  • 是否需要计算字节数?简单地计算键或唯一值的数量就足够了吗?
  • @oleksii 这可能是唯一的解决方案。我仍在尝试找到一种方法来解决您在序列化事物时对性能造成的重大影响(这是一个巨大的打击)。
  • 如果在生产中使用它,我会考虑使用性能计数器来跟踪大小(或至少是项目数)。
  • 听起来像是过早的优化。

标签: c# .net memory


【解决方案1】:

鉴于您最近的评论,该值是一个可变长度的字符串,计算字典中每个项目的大小应该很容易。我会考虑通过创建自己的缓存对象(可能只是包装字典)并在将项目添加到缓存中和从缓存中删除时跟踪总大小来节省时间和精力。这样,您可以随时通过查看您一直跟踪的值来了解缓存中值的总大小。

如果您需要缓存来公开完整的IDictionary 功能,您可以实现接口,将其委托给“真实”字典并修改AddRemove 操作中的累积大小值。如果您不需要缓存来公开完整的IDictionary 功能,只需定义一个精简的接口(可能只有AddContainsRemove 方法和CumulativeSize 属性。或者,您可能决定实现一个没有接口的缓存对象。如果是我,我会使用IDictionary 或定义一个接口,如ICache

因此,您的缓存可能看起来像这样(未编译且未经测试):

public interface ICacheWithCumulativeSize
{
  void Add(string key, string value);
  bool Contains(string key);
  void Remove(string key);
  int CumulativeSize { get; }
}

public class MyCache : ICacheWithCumulativeSize
{
  private IDictionary<string, string> dict = new Dictionary<string, string>();

  public void Add(string key, string value)
  {
    CumulativeSize += value.Length;
    dict[key] = value;
  }

  public bool Contains(string key)
  {
    return dict.ContainsKey(key);
  }

  public void Remove(string key)
  {
    string toRemove = dict[key];
    CumulativeSize -= value.Length;
    dict.Remove(key);
  }

  int CumulativeSize { public get; private set; }
}

这很粗糙。显然,它可以更高效、更健壮。我没有对AddRemove 进行任何检查以查看密钥是否已经存在等,但我认为您可能明白了。此外,作为值存储在字典中的字符串可能会在外部进行修改(可能不在您的程序中,但在理论上),因此当值是从 CumulativeSize 中减去字符串时的长度从缓存中删除的字符串可能与最初添加该字符串时的长度不同。如果这是一个问题,您可以考虑将值的副本存储在内部字典中。我对您的申请了解得不够多,无法判断这是否是个好主意。

为了完整性...这是一个粗略的实现,它简单地包装了一个字典,公开了 IDictionary 接口,并跟踪缓存中项目的总大小。它有更多的防御性代码,主要是为了保护大小累加器。我可能认为棘手的唯一部分是索引设置器...我的实现检查正在设置的索引是否已经存在。如果是这样,累积值会适当递减,然后根据输入值的大小递增。否则,我认为这很简单。

  public class MySpecialDictionary : IDictionary<string, string>
  {
    private IDictionary<string, string> dict = new Dictionary<string, string>();

    public int TotalSize { get; private set; }

    #region IDictionary<string,string> Members

    public void Add(string key, string value)
    {
      dict.Add(key, value);
      TotalSize += string.IsNullOrEmpty(value) ? 0 : value.Length;
    }

    public bool ContainsKey(string key)
    {
      return dict.ContainsKey(key);
    }

    public ICollection<string> Keys
    {
      get { return dict.Keys; }
    }

    public bool Remove(string key)
    {
      string value;
      if (dict.TryGetValue(key, out value))
      {
        TotalSize -= string.IsNullOrEmpty(value) ? 0 : value.Length;
      }
      return dict.Remove(key);
    }

    public bool TryGetValue(string key, out string value)
    {
      return dict.TryGetValue(key, out value);
    }

    public ICollection<string> Values
    {
      get { return dict.Values; }
    }

    public string this[string key]
    {
      get
      {
        return dict[key];
      }
      set
      {
        string v;
        if (dict.TryGetValue(key, out v))
        {
          TotalSize -= string.IsNullOrEmpty(v) ? 0 : v.Length;
        }
        dict[key] = value;
        TotalSize += string.IsNullOrEmpty(value) ? 0 : value.Length;
      }
    }

    #endregion

    #region ICollection<KeyValuePair<string,string>> Members

    public void Add(KeyValuePair<string, string> item)
    {
      dict.Add(item);
      TotalSize += string.IsNullOrEmpty(item.Value) ? 0 : item.Value.Length;
    }

    public void Clear()
    {
      dict.Clear();
      TotalSize = 0;
    }

    public bool Contains(KeyValuePair<string, string> item)
    {
      return dict.Contains(item);
    }

    public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
    {
      dict.CopyTo(array, arrayIndex);
    }

    public int Count
    {
      get { return dict.Count; }
    }

    public bool IsReadOnly
    {
      get { return dict.IsReadOnly; }
    }

    public bool Remove(KeyValuePair<string, string> item)
    {
      string v;
      if (dict.TryGetValue(item.Key, out v))
      {
        TotalSize -= string.IsNullOrEmpty(v) ? 0 : v.Length;
      }
      return dict.Remove(item);
    }

    #endregion

    #region IEnumerable<KeyValuePair<string,string>> Members

    public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
    {
      return dict.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return dict.GetEnumerator();
    }

    #endregion
  }

祝你好运!

【讨论】:

  • 我只是在将其标记为解决方案之前完成了您的第一段。无论实施如何,该解决方案都需要保持滚动总和才能发挥作用。如此清晰而简单的正确答案,我正在面对自己,以至于我自己都没有想到。再次感谢。
【解决方案2】:

框架中没有方法可以告诉您一个对象正在使用多少内存,因为对任何类型的对象执行此操作都会非常复杂。

如果您知道您的字典易于分析,即它不包含对同一项目的重复引用,并且可以轻松估算每个项目的内存使用量,您可以循环遍历字典并总结每个对象的近似大小。

【讨论】:

  • 是的,键是唯一的。这是我的后备方案,只是估计 k/v 对的大小并将该值缩放为集合的大小。我的主要问题是该对中的值是一个可变长度的字符串,一旦我打破 100 万条记录,该值中 +/-50% 的摆动空间可能会很大。
  • 对于字符串,内存使用通常很简单,长度 * 2 加上一些开销(12/20 字节取决于 32/64 位平台)。但是请注意,如果您使用 StringBuilder 创建字符串,则该字符串的末尾可能包含额外的未使用空间。
  • 这是一个想法......我会试试这个,看看有 1-2 百万个条目的事实是否会破坏性能交易。
猜你喜欢
  • 2018-05-04
  • 2010-10-08
  • 2014-07-30
  • 2010-11-26
  • 1970-01-01
  • 2022-01-12
  • 2012-08-27
  • 1970-01-01
相关资源
最近更新 更多