尝试更新类型为 Dictionary<IComparable、int 中的值时防止双哈希操作>



我正在开发一款用于科学研究的软件,该软件主要涉及化学配方。我使用内部Dictionary<Isotope, int>来跟踪化学式的内容,其中Isotope是一个像"碳-13"、"氮-14"这样的物体,而int代表化学式中这些同位素的数量。所以公式C2H3NO是这样存在的:

{"C12", 2
"H1", 3
"N14", 1
"O16", 1}  

这一切都很好,但当我想把两个化学公式加在一起时,我最终不得不计算两次Isotope的哈希函数来更新一个值,请参阅下面的代码示例。

public class ChemicalFormula {
    internal Dictionary<Isotope, int> _isotopes = new Dictionary<Isotope, int>();
    public void Add(Isotope isotope, int count)
    {
        if (count != 0)
        {
            int curValue = 0;
            if (_isotopes.TryGetValue(isotope, out curValue))
            {
                int newValue = curValue + count;
                if (newValue == 0)
                {
                    _isotopes.Remove(isotope);
                }
                else
                {
                    _isotopes[isotope] = newValue;
                }
            }
            else
            {
                _isotopes.Add(isotope, count);
            }
            _isDirty = true;
        }
    }
}

虽然这看起来不会很慢,但当我们将数十亿个化学配方添加在一起时,这种方法始终是程序中最慢的部分(>45%的运行时间)。我正在处理像"H5921C3759N1023O1201S21"这样的大型化学配方,这些化学配方一直被添加到较小的化学配方中。

我的问题是,有没有更好的数据结构来存储这样的数据?我尝试创建一个包含int的简单IsotopeCount对象,这样我就可以访问引用类型(而不是值类型)中的值,以避免双重哈希函数。然而,这似乎并没有什么好处。

编辑Isotope是不可变的,在程序的生命周期内不应该更改,所以我应该能够缓存哈希代码。

我已经链接到源代码,这样你就可以更深入地看到这些类,而不是我在这里复制粘贴它们。

我支持Isotope应该使用预先计算的哈希来实现不可变的观点。这会让一切变得简单得多。

(事实上,面向功能的编程更适合这种类型的计算,并且它处理不可变的对象)

我尝试创建一个包含int的简单同位素计数对象,这样我就可以访问引用类型(而不是值类型)中的值,以避免双重哈希函数。然而,这似乎并没有什么好处。

好吧,这会停止双重哈希。。。但很明显,在空间方面情况更糟。您注意到了什么性能差异

如果你经常这样做,你应该强烈考虑的另一个选项是在Isotope类中缓存哈希,假设它是不可变的。(如果不是,那么将其用作字典关键字至少有点令人担忧。)

如果您可能使用大多数Isotope值作为字典键(或候选者),那么在初始化期间计算散列可能是值得的。否则,选择一个特别不可能的哈希值(在理想情况下,它可以是任何值),并将其用作"未缓存"值,然后懒洋洋地计算它。

如果你在GetHashCode中有45%的运行时间,你有没有考虑过优化它?问题究竟是GetHashCode还是Equals?(你说的是"散列",但我怀疑你的意思是"一般的散列查找"。)

如果你能发布Isotope类型的相关比特,我们可能会提供更多帮助。

EDIT:如果您使用.NET 4,考虑的另一个选项是ConcurrentDictionary及其AddOrUpdate方法。你会这样使用它:

public void Add(Isotope isotope, int count)
{
    // I prefer early exit to lots of nesting :)
    if (count == 0)
    {
        return 0;
    }
    int newCount = _isotopes.AddOrUpdate(isotope, count, 
                                         (key, oldCount) => oldCount + count);
    if (newCount == 0)
    {
        _isotopes.Remove(isotope);
    }
    _isDirty = true;
}

您实际上需要按类型随机访问同位素计数吗?还是使用字典将键与值关联起来?

我猜是后者。

我给你的建议是不要使用字典,而是使用同位素元组的排序数组(或列表),比如:

class IsotopeTuple{
   Isotope i;
   int count;
}

按同位素的名称排序。

为什么要排序?

因为,当你想把两种同位素"相加"在一起时,你可以在线性时间内通过遍历两个数组来做到这一点(希望这一点很清楚,如果需要,我可以详细说明)。不需要哈希计算,只需要超快速的顺序比较。

当处理维度为单词的向量乘法时,这是一种经典的方法。广泛用于文本挖掘。

当然,代价是初始向量的构造是(n)log(n),但我怀疑你是否会感受到这种影响。

如果同位素数量有限且没有内存问题,您可以想到的另一个解决方案:

public struct Formula
{
   public int C12;
   public int H1;
   public int N14;
   public int O16;
}

我猜你在研究有机化学,所以你可能不需要处理那么多同位素,如果查找是个问题,这个会很快。。。

最新更新