数据结构:图结构_数据结构:做出正确的选择

数据结构:图结构

追踪问题

性能问题影响了负责处理媒体会话的服务器产品的新版本。 工程部门使用的自动测试系统与以前的版本相比,性能下降了50%。 幸运的是,该部门采用的流程使他们能够清楚地确定对代码库的所有更改。 具有讽刺意味的是,大多数更改都是性能错误修复。 对变更的审查将候选人的数量减少到两个,然后在每个变更回滚的情况下重复进行性能测试。 这确定了造成性能下降的原因。 幸运的是,这不是性能修复,而是对类的equals方法的简单修改。

在详细说明问题之前,值得一提的是,该部门可能已使用概要分析工具来确定性能问题的根源。 实际上,这是在引入性能问题很长时间后才发现性能问题的唯一选择。 日常性能测试方法的好处显而易见。

有问题的equals方法

清单1突出显示了对equals方法所做的更改。 如您所见,唯一的区别是对其他String成员变量的求值。 但这真的是造成如此大的性能下降的原因吗?

清单1. equals方法
public boolean equals(Object a_Object)
{
    if (a_Object instanceof DialogId)
    {
        final DialogId d = (DialogId) a_Object;
            
        if (m_CallID.equals(d.m_CallID) 
            && compareDestination(m_Destination, d.m_Destination) 
            && compareSource(m_Source, d.m_Source)
            && (m_Version == d.m_Version)
            && (m_Identifier.equals(d.m_Identifier)) // NEW
            )
        {
            return true;
        }
    }
    
    return false;
}

表1显示了被比较的标识符字符串的结构,以及一个示例。

表1.标识符结构
z9hG4bK IP地址(十六进制) 港口(十六进制) 时间(16位十六进制) 唯一整数
z9hG4bK C1C334A7 13CE 000000FC83FAE31 11

在这种情况下,有两个因素会影响性能:字符串的长度以及前35个字符始终相同的事实。 字符串越长,执行相等性检查所花费的时间就越多,尤其是当字符串仅在结尾附近不同时。 如果更改结构,使序列号移到最前面或缩短字符串,则将获得较小的性能优势。 但是,在这种情况下,这些更改没有帮助。

改进的equals方法本身不应对性能显着降低负责。 取而代之的是采用equals方法的方法。

谁使用equals方法?

DialogController类管理Dialog类的实例。 DialogController类间接使用equals方法。 在正常程序流程中,将Dialog实例插入临时数据结构中,并在对其进行处理后将其移至另一个完整的数据结构中。 DialogController是一个单例,通常可以管理6,000多个并发Dialog实例。 清单2显示了DialogController类的相关部分。

清单2.创建连接的七个方法
public class DialogController
{
    private final Map m_Provisional = new HashMap();

    private final List m_Completed = new LinkedList();


    public boolean handleProvisional(Message a_Message)
    {
        DialogId d = new Dialog(a_Message);

        synchronized(m_SyncMaps)
        {
            if (m_Completed.containsKey(d))
            {
                ...
            }
            else if(m_Provisional.contains(d))
            {
                return true;
            }
            else
            {
                m_Provisional.add(d);
                return false;
            }
        }
        
        return true;
    }

    public boolean handleCompleted(Message a_Message)
    {
        Dialog d = new Dialog(a_Message);

        synchronized(m_SyncMaps)
        {

            if (m_Provisional.contains(d))
            {
                m_Provisional.remove(d);

                m_Completed.put(d, new Object());
                return true;
            }
        }
 
        return false;
    }
}

显然,对两个数据结构的访问是同步的。 在某些程序中,争用锁可能成为性能瓶颈,但在此之前的版本中存在同步。 最明显的选择是使用LinkedList来保存临时Dialog实例,考虑到列表中条目的数量,这可能是一个糟糕的选择。 通常,列表在运行时具有数千个条目。 还要注意,完成的实例存储在HashMap

性能不佳和equals方法之间的联系在于LinkedList实现执行contains操作(以及remove操作)的方式。 两者都涉及遍历列表节点,并且针对遇到的每个列表节点执行Dialog equals方法。 在最坏的情况下,必须检查整个列表。 平均而言,只检查了一半的列表。 有数千个条目,程序在equals方法中比较新的String成员所花费的累积时间直接导致性能问题,并说明了无害的更改为何会带来严重的后果。

所有这些活动都发生在同步块内的事实也很重要。 在以前的版本中,使用同步块使程序线程安全不会引起问题。 但是在新版本中,增强的equals方法显然突出了此领域作为性能瓶颈。 如果更改了新版本,使其不再是线程安全的,则可以提高性能,尽管是以数据完整性为代价的!

正确的选择

在这种特定情况下,更好的选择是基于散列的集合,它使对象的存储和检索快得多。 此外,Java通过hashCode方法直接在对象模型中支持哈希,该方法由基于哈希的集合使用。

基于哈希的集合旨在提供有效的插入和查找操作。 为了满足这些要求,做出了一些让步。 不需要对集合中的项目进行特定排序。 另外,有效地从容器中移走物品的能力不是主要目标。

现在您已经选择了一个基于哈希的集合,至关重要的是Dialog类的hashCode方法生成正确的哈希码。 如果太多的对象共享相同的哈希码,则收集类将诉诸于基于等式的评估。 良好的哈希函数可避免冲突,易于将键均匀分布在数组中,并且计算简便快捷。

相反,单链接列表是所有链接数据结构中最基本的。 它只是一系列动态分配的对象,每个对象都引用其在列表中的后继对象。 添加到头端和尾端既快速又便宜,插入中间位置则更加快捷。 这也有助于分类。 但是,检索列表项可能很昂贵,因为必须遍历和评估每个节点。

使用HashSet而不是LinkedList来存储临时Dialog使性能提高205%。

最后,您已经看到临时Dialog是如何存储在LinkedList 。 但是,我之前提到了为完成的对话框选择HashMap 。 目前尚不清楚是否由于性能原因做出了这一选择。 之所以选择它,是因为需要在Dialog旁边存储其他数据。

结论

选择数据结构时,开发人员很容易变得自满或犯下真正的错误。 临时Dialog的数量可能会随着时间而改变,并且没有重新考虑LinkedList的适用性。 无论出于何种原因,我都说明了正确或错误数据结构之间的巨大差异。 弄错它会对您的应用程序性能产生可怕的影响。 现在您可以正确处理。


翻译自: https://www.ibm.com/developerworks/web/library/wa-datastruc2/index.html

数据结构:图结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值